diff options
Diffstat (limited to 'internal')
40 files changed, 26191 insertions, 0 deletions
diff --git a/internal/pkg/client/client.go b/internal/pkg/client/client.go new file mode 100644 index 00000000..28adfd62 --- /dev/null +++ b/internal/pkg/client/client.go @@ -0,0 +1,849 @@ +// 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 client provides a wrapper for GoBGP's gRPC API +package client + +import ( + "fmt" + "io" + "net" + "strconv" + "time" + + "golang.org/x/net/context" + "google.golang.org/grpc" + + api "github.com/osrg/gobgp/api" + "github.com/osrg/gobgp/internal/pkg/config" + "github.com/osrg/gobgp/pkg/packet/bgp" + "github.com/osrg/gobgp/pkg/server" +) + +type Client struct { + conn *grpc.ClientConn + cli api.GobgpApiClient +} + +func defaultGRPCOptions() []grpc.DialOption { + return []grpc.DialOption{grpc.WithTimeout(time.Second), grpc.WithBlock(), grpc.WithInsecure()} +} + +// New returns a new Client using the given target and options for dialing +// to the grpc server. If an error occurs during dialing it will be returned and +// Client will be nil. +func New(target string, opts ...grpc.DialOption) (*Client, error) { + return NewWith(context.Background(), target, opts...) +} + +// NewWith is like New, but uses the given ctx to cancel or expire the current +// attempt to connect if it becomes Done before the connection succeeds. +func NewWith(ctx context.Context, target string, opts ...grpc.DialOption) (*Client, error) { + if target == "" { + target = ":50051" + } + if len(opts) == 0 { + opts = defaultGRPCOptions() + } + conn, err := grpc.DialContext(ctx, target, opts...) + if err != nil { + return nil, err + } + cli := api.NewGobgpApiClient(conn) + return &Client{conn: conn, cli: cli}, nil +} + +// NewFrom returns a new Client, using the given conn and cli for the +// underlying connection. The given grpc.ClientConn connection is expected to be +// initialized and paired with the api client. See New to have the connection +// dialed for you. +func NewFrom(conn *grpc.ClientConn, cli api.GobgpApiClient) *Client { + return &Client{conn: conn, cli: cli} +} + +func (cli *Client) Close() error { + return cli.conn.Close() +} + +func (cli *Client) StartServer(c *config.Global) error { + _, err := cli.cli.StartServer(context.Background(), &api.StartServerRequest{ + Global: &api.Global{ + As: c.Config.As, + RouterId: c.Config.RouterId, + ListenPort: c.Config.Port, + ListenAddresses: c.Config.LocalAddressList, + UseMultiplePaths: c.UseMultiplePaths.Config.Enabled, + }, + }) + return err +} + +func (cli *Client) StopServer() error { + _, err := cli.cli.StopServer(context.Background(), &api.StopServerRequest{}) + return err +} + +func (cli *Client) GetServer() (*config.Global, error) { + ret, err := cli.cli.GetServer(context.Background(), &api.GetServerRequest{}) + if err != nil { + return nil, err + } + return &config.Global{ + Config: config.GlobalConfig{ + As: ret.Global.As, + RouterId: ret.Global.RouterId, + Port: ret.Global.ListenPort, + LocalAddressList: ret.Global.ListenAddresses, + }, + UseMultiplePaths: config.UseMultiplePaths{ + Config: config.UseMultiplePathsConfig{ + Enabled: ret.Global.UseMultiplePaths, + }, + }, + }, nil +} + +func (cli *Client) EnableZebra(c *config.Zebra) error { + req := &api.EnableZebraRequest{ + Url: c.Config.Url, + Version: uint32(c.Config.Version), + NexthopTriggerEnable: c.Config.NexthopTriggerEnable, + NexthopTriggerDelay: uint32(c.Config.NexthopTriggerDelay), + } + for _, t := range c.Config.RedistributeRouteTypeList { + req.RouteTypes = append(req.RouteTypes, string(t)) + } + _, err := cli.cli.EnableZebra(context.Background(), req) + return err +} + +func (cli *Client) getNeighbor(name string, afi int, vrf string, enableAdvertised bool) ([]*config.Neighbor, error) { + ret, err := cli.cli.GetNeighbor(context.Background(), &api.GetNeighborRequest{EnableAdvertised: enableAdvertised, Address: name}) + if err != nil { + return nil, err + } + + neighbors := make([]*config.Neighbor, 0, len(ret.Peers)) + + for _, p := range ret.Peers { + if name != "" && name != p.Info.NeighborAddress && name != p.Conf.NeighborInterface { + continue + } + if vrf != "" && name != p.Conf.Vrf { + continue + } + if afi > 0 { + v6 := net.ParseIP(p.Info.NeighborAddress).To4() == nil + if afi == bgp.AFI_IP && v6 || afi == bgp.AFI_IP6 && !v6 { + continue + } + } + n, err := server.NewNeighborFromAPIStruct(p) + if err != nil { + return nil, err + } + neighbors = append(neighbors, n) + } + return neighbors, nil +} + +func (cli *Client) ListNeighbor() ([]*config.Neighbor, error) { + return cli.getNeighbor("", 0, "", false) +} + +func (cli *Client) ListNeighborByTransport(afi int) ([]*config.Neighbor, error) { + return cli.getNeighbor("", afi, "", false) +} + +func (cli *Client) ListNeighborByVRF(vrf string) ([]*config.Neighbor, error) { + return cli.getNeighbor("", 0, vrf, false) +} + +func (cli *Client) GetNeighbor(name string, options ...bool) (*config.Neighbor, error) { + enableAdvertised := false + if len(options) > 0 && options[0] { + enableAdvertised = true + } + ns, err := cli.getNeighbor(name, 0, "", enableAdvertised) + if err != nil { + return nil, err + } + if len(ns) == 0 { + return nil, fmt.Errorf("not found neighbor %s", name) + } + return ns[0], nil +} + +func (cli *Client) AddNeighbor(c *config.Neighbor) error { + peer := server.NewPeerFromConfigStruct(c) + _, err := cli.cli.AddNeighbor(context.Background(), &api.AddNeighborRequest{Peer: peer}) + return err +} + +func (cli *Client) DeleteNeighbor(c *config.Neighbor) error { + peer := server.NewPeerFromConfigStruct(c) + _, err := cli.cli.DeleteNeighbor(context.Background(), &api.DeleteNeighborRequest{Peer: peer}) + return err +} + +func (cli *Client) UpdateNeighbor(c *config.Neighbor, doSoftResetIn bool) (bool, error) { + peer := server.NewPeerFromConfigStruct(c) + response, err := cli.cli.UpdateNeighbor(context.Background(), &api.UpdateNeighborRequest{Peer: peer, DoSoftResetIn: doSoftResetIn}) + return response.NeedsSoftResetIn, err +} + +func (cli *Client) ShutdownNeighbor(addr, communication string) error { + _, err := cli.cli.ShutdownNeighbor(context.Background(), &api.ShutdownNeighborRequest{Address: addr, Communication: communication}) + return err +} + +func (cli *Client) ResetNeighbor(addr, communication string) error { + _, err := cli.cli.ResetNeighbor(context.Background(), &api.ResetNeighborRequest{Address: addr, Communication: communication}) + return err +} + +func (cli *Client) EnableNeighbor(addr string) error { + _, err := cli.cli.EnableNeighbor(context.Background(), &api.EnableNeighborRequest{Address: addr}) + return err +} + +func (cli *Client) DisableNeighbor(addr, communication string) error { + _, err := cli.cli.DisableNeighbor(context.Background(), &api.DisableNeighborRequest{Address: addr, Communication: communication}) + return err +} + +func (cli *Client) softreset(addr string, family bgp.RouteFamily, dir api.SoftResetNeighborRequest_SoftResetDirection) error { + _, err := cli.cli.SoftResetNeighbor(context.Background(), &api.SoftResetNeighborRequest{ + Address: addr, + Direction: dir, + }) + return err +} + +func (cli *Client) SoftResetIn(addr string, family bgp.RouteFamily) error { + return cli.softreset(addr, family, api.SoftResetNeighborRequest_IN) +} + +func (cli *Client) SoftResetOut(addr string, family bgp.RouteFamily) error { + return cli.softreset(addr, family, api.SoftResetNeighborRequest_OUT) +} + +func (cli *Client) SoftReset(addr string, family bgp.RouteFamily) error { + return cli.softreset(addr, family, api.SoftResetNeighborRequest_BOTH) +} + +func (cli *Client) getRIB(resource api.Resource, name string, family bgp.RouteFamily, prefixes []*api.TableLookupPrefix) (*api.Table, error) { + stream, err := cli.cli.GetPath(context.Background(), &api.GetPathRequest{ + Type: resource, + Family: uint32(family), + Name: name, + Prefixes: prefixes, + }) + if err != nil { + return nil, err + } + pathMap := make(map[string][]*api.Path) + for { + p, err := stream.Recv() + if err != nil { + if err == io.EOF { + break + } + return nil, err + } + nlri, err := p.GetNativeNlri() + if err != nil { + return nil, err + } + nlriStr := nlri.String() + pathMap[nlriStr] = append(pathMap[nlriStr], p) + } + dstList := make([]*api.Destination, 0, len(pathMap)) + for _, pathList := range pathMap { + nlri, _ := pathList[0].GetNativeNlri() + dstList = append(dstList, &api.Destination{ + Prefix: nlri.String(), + Paths: pathList, + }) + } + return &api.Table{ + Family: uint32(family), + Destinations: dstList, + }, nil +} + +func (cli *Client) GetRIB(family bgp.RouteFamily, prefixes []*api.TableLookupPrefix) (*api.Table, error) { + return cli.getRIB(api.Resource_GLOBAL, "", family, prefixes) +} + +func (cli *Client) GetLocalRIB(name string, family bgp.RouteFamily, prefixes []*api.TableLookupPrefix) (*api.Table, error) { + return cli.getRIB(api.Resource_LOCAL, name, family, prefixes) +} + +func (cli *Client) GetAdjRIBIn(name string, family bgp.RouteFamily, prefixes []*api.TableLookupPrefix) (*api.Table, error) { + return cli.getRIB(api.Resource_ADJ_IN, name, family, prefixes) +} + +func (cli *Client) GetAdjRIBOut(name string, family bgp.RouteFamily, prefixes []*api.TableLookupPrefix) (*api.Table, error) { + return cli.getRIB(api.Resource_ADJ_OUT, name, family, prefixes) +} + +func (cli *Client) GetVRFRIB(name string, family bgp.RouteFamily, prefixes []*api.TableLookupPrefix) (*api.Table, error) { + return cli.getRIB(api.Resource_VRF, name, family, prefixes) +} + +func (cli *Client) getRIBInfo(resource api.Resource, name string, family bgp.RouteFamily) (*api.TableInfo, error) { + r, err := cli.cli.GetRibInfo(context.Background(), &api.GetRibInfoRequest{ + Info: &api.TableInfo{ + Type: resource, + Name: name, + Family: uint32(family), + }, + }) + if err != nil { + return nil, err + } else { + return r.Info, nil + } +} + +func (cli *Client) GetRIBInfo(family bgp.RouteFamily) (*api.TableInfo, error) { + return cli.getRIBInfo(api.Resource_GLOBAL, "", family) +} + +func (cli *Client) GetLocalRIBInfo(name string, family bgp.RouteFamily) (*api.TableInfo, error) { + return cli.getRIBInfo(api.Resource_LOCAL, name, family) +} + +func (cli *Client) GetAdjRIBInInfo(name string, family bgp.RouteFamily) (*api.TableInfo, error) { + return cli.getRIBInfo(api.Resource_ADJ_IN, name, family) +} + +func (cli *Client) GetAdjRIBOutInfo(name string, family bgp.RouteFamily) (*api.TableInfo, error) { + return cli.getRIBInfo(api.Resource_ADJ_OUT, name, family) +} + +type AddPathByStreamClient struct { + stream api.GobgpApi_InjectMrtClient +} + +func (c *AddPathByStreamClient) Send(paths ...*api.Path) error { + return c.stream.Send(&api.InjectMrtRequest{ + Resource: api.Resource_GLOBAL, + Paths: paths, + }) +} + +func (c *AddPathByStreamClient) Close() error { + _, err := c.stream.CloseAndRecv() + return err +} + +func (cli *Client) AddPathByStream() (*AddPathByStreamClient, error) { + stream, err := cli.cli.InjectMrt(context.Background()) + if err != nil { + return nil, err + } + return &AddPathByStreamClient{stream}, nil +} + +func (cli *Client) addPath(vrfID string, pathList []*api.Path) ([]byte, error) { + resource := api.Resource_GLOBAL + if vrfID != "" { + resource = api.Resource_VRF + } + var uuid []byte + for _, path := range pathList { + r, err := cli.cli.AddPath(context.Background(), &api.AddPathRequest{ + Resource: resource, + VrfId: vrfID, + Path: path, + }) + if err != nil { + return nil, err + } + uuid = r.Uuid + } + return uuid, nil +} + +func (cli *Client) AddPath(pathList []*api.Path) ([]byte, error) { + return cli.addPath("", pathList) +} + +func (cli *Client) AddVRFPath(vrfID string, pathList []*api.Path) ([]byte, error) { + if vrfID == "" { + return nil, fmt.Errorf("VRF ID is empty") + } + return cli.addPath(vrfID, pathList) +} + +func (cli *Client) deletePath(uuid []byte, f bgp.RouteFamily, vrfID string, pathList []*api.Path) error { + var reqs []*api.DeletePathRequest + + resource := api.Resource_GLOBAL + if vrfID != "" { + resource = api.Resource_VRF + } + switch { + case len(pathList) != 0: + for _, path := range pathList { + reqs = append(reqs, &api.DeletePathRequest{ + Resource: resource, + VrfId: vrfID, + Path: path, + }) + } + default: + reqs = append(reqs, &api.DeletePathRequest{ + Resource: resource, + VrfId: vrfID, + Uuid: uuid, + Family: uint32(f), + }) + } + + for _, req := range reqs { + if _, err := cli.cli.DeletePath(context.Background(), req); err != nil { + return err + } + } + return nil +} + +func (cli *Client) DeletePath(pathList []*api.Path) error { + return cli.deletePath(nil, bgp.RouteFamily(0), "", pathList) +} + +func (cli *Client) DeleteVRFPath(vrfID string, pathList []*api.Path) error { + if vrfID == "" { + return fmt.Errorf("VRF ID is empty") + } + return cli.deletePath(nil, bgp.RouteFamily(0), vrfID, pathList) +} + +func (cli *Client) DeletePathByUUID(uuid []byte) error { + return cli.deletePath(uuid, bgp.RouteFamily(0), "", nil) +} + +func (cli *Client) DeletePathByFamily(family bgp.RouteFamily) error { + return cli.deletePath(nil, family, "", nil) +} + +func (cli *Client) GetVRF() ([]*api.Vrf, error) { + ret, err := cli.cli.GetVrf(context.Background(), &api.GetVrfRequest{}) + if err != nil { + return nil, err + } + return ret.Vrfs, nil +} + +func (cli *Client) AddVRF(name string, id int, rd bgp.RouteDistinguisherInterface, im, ex []bgp.ExtendedCommunityInterface) error { + arg := &api.AddVrfRequest{ + Vrf: &api.Vrf{ + Name: name, + Rd: api.MarshalRD(rd), + Id: uint32(id), + ImportRt: api.MarshalRTs(im), + ExportRt: api.MarshalRTs(ex), + }, + } + _, err := cli.cli.AddVrf(context.Background(), arg) + return err +} + +func (cli *Client) DeleteVRF(name string) error { + arg := &api.DeleteVrfRequest{ + Vrf: &api.Vrf{ + Name: name, + }, + } + _, err := cli.cli.DeleteVrf(context.Background(), arg) + return err +} + +func (cli *Client) getDefinedSet(typ api.DefinedType, name string) ([]*api.DefinedSet, error) { + ret, err := cli.cli.GetDefinedSet(context.Background(), &api.GetDefinedSetRequest{ + Type: api.DefinedType(typ), + Name: name, + }) + if err != nil { + return nil, err + } + return ret.Sets, nil +} + +func (cli *Client) GetDefinedSet(typ api.DefinedType) ([]*api.DefinedSet, error) { + return cli.getDefinedSet(typ, "") +} + +func (cli *Client) GetDefinedSetByName(typ api.DefinedType, name string) (*api.DefinedSet, error) { + sets, err := cli.getDefinedSet(typ, name) + if err != nil { + return nil, err + } + if len(sets) == 0 { + return nil, fmt.Errorf("not found defined set: %s", name) + } else if len(sets) > 1 { + return nil, fmt.Errorf("invalid response for GetDefinedSetByName") + } + return sets[0], nil +} + +func (cli *Client) AddDefinedSet(d *api.DefinedSet) error { + _, err := cli.cli.AddDefinedSet(context.Background(), &api.AddDefinedSetRequest{ + Set: d, + }) + return err +} + +func (cli *Client) DeleteDefinedSet(d *api.DefinedSet, all bool) error { + _, err := cli.cli.DeleteDefinedSet(context.Background(), &api.DeleteDefinedSetRequest{ + Set: d, + All: all, + }) + return err +} + +func (cli *Client) ReplaceDefinedSet(d *api.DefinedSet) error { + _, err := cli.cli.ReplaceDefinedSet(context.Background(), &api.ReplaceDefinedSetRequest{ + Set: d, + }) + return err +} + +func (cli *Client) GetStatement() ([]*api.Statement, error) { + ret, err := cli.cli.GetStatement(context.Background(), &api.GetStatementRequest{}) + if err != nil { + return nil, err + } + return ret.Statements, nil +} + +func (cli *Client) AddStatement(t *api.Statement) error { + _, err := cli.cli.AddStatement(context.Background(), &api.AddStatementRequest{ + Statement: t, + }) + return err +} + +func (cli *Client) DeleteStatement(t *api.Statement, all bool) error { + _, err := cli.cli.DeleteStatement(context.Background(), &api.DeleteStatementRequest{ + Statement: t, + All: all, + }) + return err +} + +func (cli *Client) ReplaceStatement(t *api.Statement) error { + _, err := cli.cli.ReplaceStatement(context.Background(), &api.ReplaceStatementRequest{ + Statement: t, + }) + return err +} + +func (cli *Client) GetPolicy() ([]*api.Policy, error) { + ret, err := cli.cli.GetPolicy(context.Background(), &api.GetPolicyRequest{}) + if err != nil { + return nil, err + } + return ret.Policies, nil +} + +func (cli *Client) AddPolicy(a *api.Policy, refer bool) error { + _, err := cli.cli.AddPolicy(context.Background(), &api.AddPolicyRequest{ + Policy: a, + ReferExistingStatements: refer, + }) + return err +} + +func (cli *Client) DeletePolicy(a *api.Policy, all, preserve bool) error { + _, err := cli.cli.DeletePolicy(context.Background(), &api.DeletePolicyRequest{ + Policy: a, + All: all, + PreserveStatements: preserve, + }) + return err +} + +func (cli *Client) ReplacePolicy(a *api.Policy, refer, preserve bool) error { + _, err := cli.cli.ReplacePolicy(context.Background(), &api.ReplacePolicyRequest{ + Policy: a, + ReferExistingStatements: refer, + PreserveStatements: preserve, + }) + return err +} + +func (cli *Client) getPolicyAssignment(name string, typ api.PolicyType) (*api.PolicyAssignment, error) { + resource := api.Resource_GLOBAL + if name != "" { + resource = api.Resource_LOCAL + } + + ret, err := cli.cli.GetPolicyAssignment(context.Background(), &api.GetPolicyAssignmentRequest{ + Assignment: &api.PolicyAssignment{ + Name: name, + Resource: resource, + Type: typ, + }, + }) + if err != nil { + return nil, err + } + + return &api.PolicyAssignment{ + Name: name, + Type: typ, + Policies: ret.Assignment.Policies, + Default: ret.Assignment.Default, + }, nil +} + +func (cli *Client) GetImportPolicy() (*api.PolicyAssignment, error) { + return cli.getPolicyAssignment("", api.PolicyType_IMPORT) +} + +func (cli *Client) GetExportPolicy() (*api.PolicyAssignment, error) { + return cli.getPolicyAssignment("", api.PolicyType_EXPORT) +} + +func (cli *Client) GetRouteServerImportPolicy(name string) (*api.PolicyAssignment, error) { + return cli.getPolicyAssignment(name, api.PolicyType_IMPORT) +} + +func (cli *Client) GetRouteServerExportPolicy(name string) (*api.PolicyAssignment, error) { + return cli.getPolicyAssignment(name, api.PolicyType_EXPORT) +} + +func (cli *Client) AddPolicyAssignment(assignment *api.PolicyAssignment) error { + _, err := cli.cli.AddPolicyAssignment(context.Background(), &api.AddPolicyAssignmentRequest{ + Assignment: assignment, + }) + return err +} + +func (cli *Client) DeletePolicyAssignment(assignment *api.PolicyAssignment, all bool) error { + _, err := cli.cli.DeletePolicyAssignment(context.Background(), &api.DeletePolicyAssignmentRequest{ + Assignment: assignment, + All: all}) + return err +} + +func (cli *Client) ReplacePolicyAssignment(assignment *api.PolicyAssignment) error { + _, err := cli.cli.ReplacePolicyAssignment(context.Background(), &api.ReplacePolicyAssignmentRequest{ + Assignment: assignment, + }) + return err +} + +//func (cli *Client) EnableMrt(c *config.MrtConfig) error { +//} +// +//func (cli *Client) DisableMrt(c *config.MrtConfig) error { +//} +// + +func (cli *Client) GetRPKI() ([]*config.RpkiServer, error) { + rsp, err := cli.cli.GetRpki(context.Background(), &api.GetRpkiRequest{}) + if err != nil { + return nil, err + } + servers := make([]*config.RpkiServer, 0, len(rsp.Servers)) + for _, s := range rsp.Servers { + // Note: RpkiServerConfig.Port is uint32 type, but the TCP/UDP port is + // 16-bit length. + port, err := strconv.ParseUint(s.Conf.RemotePort, 10, 16) + if err != nil { + return nil, err + } + server := &config.RpkiServer{ + Config: config.RpkiServerConfig{ + Address: s.Conf.Address, + Port: uint32(port), + }, + State: config.RpkiServerState{ + Up: s.State.Up, + SerialNumber: s.State.Serial, + RecordsV4: s.State.RecordIpv4, + RecordsV6: s.State.RecordIpv6, + PrefixesV4: s.State.PrefixIpv4, + PrefixesV6: s.State.PrefixIpv6, + Uptime: s.State.Uptime, + Downtime: s.State.Downtime, + RpkiMessages: config.RpkiMessages{ + RpkiReceived: config.RpkiReceived{ + SerialNotify: s.State.SerialNotify, + CacheReset: s.State.CacheReset, + CacheResponse: s.State.CacheResponse, + Ipv4Prefix: s.State.ReceivedIpv4, + Ipv6Prefix: s.State.ReceivedIpv6, + EndOfData: s.State.EndOfData, + Error: s.State.Error, + }, + RpkiSent: config.RpkiSent{ + SerialQuery: s.State.SerialQuery, + ResetQuery: s.State.ResetQuery, + }, + }, + }, + } + servers = append(servers, server) + } + return servers, nil +} + +func (cli *Client) GetROA(family bgp.RouteFamily) ([]*api.Roa, error) { + rsp, err := cli.cli.GetRoa(context.Background(), &api.GetRoaRequest{ + Family: uint32(family), + }) + if err != nil { + return nil, err + } + return rsp.Roas, nil +} + +func (cli *Client) AddRPKIServer(address string, port, lifetime int) error { + _, err := cli.cli.AddRpki(context.Background(), &api.AddRpkiRequest{ + Address: address, + Port: uint32(port), + Lifetime: int64(lifetime), + }) + return err +} + +func (cli *Client) DeleteRPKIServer(address string) error { + _, err := cli.cli.DeleteRpki(context.Background(), &api.DeleteRpkiRequest{ + Address: address, + }) + return err +} + +func (cli *Client) EnableRPKIServer(address string) error { + _, err := cli.cli.EnableRpki(context.Background(), &api.EnableRpkiRequest{ + Address: address, + }) + return err +} + +func (cli *Client) DisableRPKIServer(address string) error { + _, err := cli.cli.DisableRpki(context.Background(), &api.DisableRpkiRequest{ + Address: address, + }) + return err +} + +func (cli *Client) ResetRPKIServer(address string) error { + _, err := cli.cli.ResetRpki(context.Background(), &api.ResetRpkiRequest{ + Address: address, + }) + return err +} + +func (cli *Client) SoftResetRPKIServer(address string) error { + _, err := cli.cli.SoftResetRpki(context.Background(), &api.SoftResetRpkiRequest{ + Address: address, + }) + return err +} + +func (cli *Client) ValidateRIBWithRPKI(prefixes ...string) error { + req := &api.ValidateRibRequest{} + if len(prefixes) > 1 { + return fmt.Errorf("too many prefixes: %d", len(prefixes)) + } else if len(prefixes) == 1 { + req.Prefix = prefixes[0] + } + _, err := cli.cli.ValidateRib(context.Background(), req) + return err +} + +func (cli *Client) AddBMP(c *config.BmpServerConfig) error { + _, err := cli.cli.AddBmp(context.Background(), &api.AddBmpRequest{ + Address: c.Address, + Port: c.Port, + Type: api.AddBmpRequest_MonitoringPolicy(c.RouteMonitoringPolicy.ToInt()), + }) + return err +} + +func (cli *Client) DeleteBMP(c *config.BmpServerConfig) error { + _, err := cli.cli.DeleteBmp(context.Background(), &api.DeleteBmpRequest{ + Address: c.Address, + Port: c.Port, + }) + return err +} + +type MonitorRIBClient struct { + stream api.GobgpApi_MonitorRibClient +} + +func (c *MonitorRIBClient) Recv() (*api.Destination, error) { + return c.stream.Recv() +} + +func (cli *Client) MonitorRIB(family bgp.RouteFamily, current bool) (*MonitorRIBClient, error) { + stream, err := cli.cli.MonitorRib(context.Background(), &api.MonitorRibRequest{ + Table: &api.Table{ + Type: api.Resource_GLOBAL, + Family: uint32(family), + }, + Current: current, + }) + if err != nil { + return nil, err + } + return &MonitorRIBClient{stream}, nil +} + +func (cli *Client) MonitorAdjRIBIn(name string, family bgp.RouteFamily, current bool) (*MonitorRIBClient, error) { + stream, err := cli.cli.MonitorRib(context.Background(), &api.MonitorRibRequest{ + Table: &api.Table{ + Type: api.Resource_ADJ_IN, + Name: name, + Family: uint32(family), + }, + Current: current, + }) + if err != nil { + return nil, err + } + return &MonitorRIBClient{stream}, nil +} + +type MonitorNeighborStateClient struct { + stream api.GobgpApi_MonitorPeerStateClient +} + +func (c *MonitorNeighborStateClient) Recv() (*config.Neighbor, error) { + p, err := c.stream.Recv() + if err != nil { + return nil, err + } + return server.NewNeighborFromAPIStruct(p) +} + +func (cli *Client) MonitorNeighborState(name string, current bool) (*MonitorNeighborStateClient, error) { + stream, err := cli.cli.MonitorPeerState(context.Background(), &api.Arguments{ + Name: name, + Current: current, + }) + if err != nil { + return nil, err + } + return &MonitorNeighborStateClient{stream}, nil +} diff --git a/internal/pkg/client/client_test.go b/internal/pkg/client/client_test.go new file mode 100644 index 00000000..a737172d --- /dev/null +++ b/internal/pkg/client/client_test.go @@ -0,0 +1,55 @@ +// 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 client + +import ( + "testing" + "time" + + "github.com/osrg/gobgp/internal/pkg/config" + "github.com/osrg/gobgp/pkg/server" + "github.com/stretchr/testify/assert" +) + +func TestGetNeighbor(test *testing.T) { + assert := assert.New(test) + s := server.NewBgpServer() + go s.Serve() + g := server.NewGrpcServer(s, ":50051") + go g.Serve() + time.Sleep(time.Second) + cli, err := New("") + assert.Nil(err) + err = cli.StartServer(&config.Global{ + Config: config.GlobalConfig{ + As: 1, + RouterId: "1.1.1.1", + Port: 1790, + }, + }) + assert.Nil(err) + err = cli.AddNeighbor(&config.Neighbor{ + Config: config.NeighborConfig{ + NeighborAddress: "10.0.0.1", + PeerAs: 2, + }, + }) + assert.Nil(err) + _, err = cli.GetNeighbor("10.0.0.1") + assert.Nil(err) + _, err = cli.GetNeighbor("10.0.0.2") + assert.Equal(err.Error(), "not found neighbor 10.0.0.2") +} diff --git a/internal/pkg/config/bgp_configs.go b/internal/pkg/config/bgp_configs.go new file mode 100644 index 00000000..5c3cff97 --- /dev/null +++ b/internal/pkg/config/bgp_configs.go @@ -0,0 +1,6335 @@ +// DO NOT EDIT +// generated by pyang using OpenConfig https://github.com/openconfig/public +// +// 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 config + +import ( + "fmt" + + "github.com/osrg/gobgp/pkg/packet/bgp" +) + +func mapkey(index int, name string) string { + if name != "" { + return name + } + return fmt.Sprintf("%v", index) +} + +// typedef for typedef openconfig-types:std-regexp. +type StdRegexp string + +// typedef for typedef openconfig-types:percentage. +type Percentage uint8 + +// typedef for typedef bgp-types:rr-cluster-id-type. +type RrClusterIdType string + +// typedef for identity bgp-types:remove-private-as-option. +// set of options for configuring how private AS path numbers +// are removed from advertisements. +type RemovePrivateAsOption string + +const ( + REMOVE_PRIVATE_AS_OPTION_ALL RemovePrivateAsOption = "all" + REMOVE_PRIVATE_AS_OPTION_REPLACE RemovePrivateAsOption = "replace" +) + +var RemovePrivateAsOptionToIntMap = map[RemovePrivateAsOption]int{ + REMOVE_PRIVATE_AS_OPTION_ALL: 0, + REMOVE_PRIVATE_AS_OPTION_REPLACE: 1, +} + +func (v RemovePrivateAsOption) ToInt() int { + i, ok := RemovePrivateAsOptionToIntMap[v] + if !ok { + return -1 + } + return i +} + +var IntToRemovePrivateAsOptionMap = map[int]RemovePrivateAsOption{ + 0: REMOVE_PRIVATE_AS_OPTION_ALL, + 1: REMOVE_PRIVATE_AS_OPTION_REPLACE, +} + +func (v RemovePrivateAsOption) Validate() error { + if _, ok := RemovePrivateAsOptionToIntMap[v]; !ok { + return fmt.Errorf("invalid RemovePrivateAsOption: %s", v) + } + return nil +} + +// typedef for typedef bgp-types:bgp-community-regexp-type. +type BgpCommunityRegexpType StdRegexp + +// typedef for identity bgp-types:community-type. +// type describing variations of community attributes: +// STANDARD: standard BGP community [rfc1997] +// EXTENDED: extended BGP community [rfc4360] +// BOTH: both standard and extended community. +type CommunityType string + +const ( + COMMUNITY_TYPE_STANDARD CommunityType = "standard" + COMMUNITY_TYPE_EXTENDED CommunityType = "extended" + COMMUNITY_TYPE_BOTH CommunityType = "both" + COMMUNITY_TYPE_NONE CommunityType = "none" +) + +var CommunityTypeToIntMap = map[CommunityType]int{ + COMMUNITY_TYPE_STANDARD: 0, + COMMUNITY_TYPE_EXTENDED: 1, + COMMUNITY_TYPE_BOTH: 2, + COMMUNITY_TYPE_NONE: 3, +} + +func (v CommunityType) ToInt() int { + i, ok := CommunityTypeToIntMap[v] + if !ok { + return -1 + } + return i +} + +var IntToCommunityTypeMap = map[int]CommunityType{ + 0: COMMUNITY_TYPE_STANDARD, + 1: COMMUNITY_TYPE_EXTENDED, + 2: COMMUNITY_TYPE_BOTH, + 3: COMMUNITY_TYPE_NONE, +} + +func (v CommunityType) Validate() error { + if _, ok := CommunityTypeToIntMap[v]; !ok { + return fmt.Errorf("invalid CommunityType: %s", v) + } + return nil +} + +// typedef for typedef bgp-types:bgp-ext-community-type. +type BgpExtCommunityType string + +// typedef for typedef bgp-types:bgp-std-community-type. +type BgpStdCommunityType string + +// typedef for identity bgp-types:peer-type. +// labels a peer or peer group as explicitly internal or +// external. +type PeerType string + +const ( + PEER_TYPE_INTERNAL PeerType = "internal" + PEER_TYPE_EXTERNAL PeerType = "external" +) + +var PeerTypeToIntMap = map[PeerType]int{ + PEER_TYPE_INTERNAL: 0, + PEER_TYPE_EXTERNAL: 1, +} + +func (v PeerType) ToInt() int { + i, ok := PeerTypeToIntMap[v] + if !ok { + return -1 + } + return i +} + +var IntToPeerTypeMap = map[int]PeerType{ + 0: PEER_TYPE_INTERNAL, + 1: PEER_TYPE_EXTERNAL, +} + +func (v PeerType) Validate() error { + if _, ok := PeerTypeToIntMap[v]; !ok { + return fmt.Errorf("invalid PeerType: %s", v) + } + return nil +} + +// typedef for identity bgp-types:bgp-session-direction. +// Type to describe the direction of NLRI transmission. +type BgpSessionDirection string + +const ( + BGP_SESSION_DIRECTION_INBOUND BgpSessionDirection = "inbound" + BGP_SESSION_DIRECTION_OUTBOUND BgpSessionDirection = "outbound" +) + +var BgpSessionDirectionToIntMap = map[BgpSessionDirection]int{ + BGP_SESSION_DIRECTION_INBOUND: 0, + BGP_SESSION_DIRECTION_OUTBOUND: 1, +} + +func (v BgpSessionDirection) ToInt() int { + i, ok := BgpSessionDirectionToIntMap[v] + if !ok { + return -1 + } + return i +} + +var IntToBgpSessionDirectionMap = map[int]BgpSessionDirection{ + 0: BGP_SESSION_DIRECTION_INBOUND, + 1: BGP_SESSION_DIRECTION_OUTBOUND, +} + +func (v BgpSessionDirection) Validate() error { + if _, ok := BgpSessionDirectionToIntMap[v]; !ok { + return fmt.Errorf("invalid BgpSessionDirection: %s", v) + } + return nil +} + +// typedef for identity bgp-types:bgp-origin-attr-type. +// Type definition for standard BGP origin attribute. +type BgpOriginAttrType string + +const ( + BGP_ORIGIN_ATTR_TYPE_IGP BgpOriginAttrType = "igp" + BGP_ORIGIN_ATTR_TYPE_EGP BgpOriginAttrType = "egp" + BGP_ORIGIN_ATTR_TYPE_INCOMPLETE BgpOriginAttrType = "incomplete" +) + +var BgpOriginAttrTypeToIntMap = map[BgpOriginAttrType]int{ + BGP_ORIGIN_ATTR_TYPE_IGP: 0, + BGP_ORIGIN_ATTR_TYPE_EGP: 1, + BGP_ORIGIN_ATTR_TYPE_INCOMPLETE: 2, +} + +func (v BgpOriginAttrType) ToInt() int { + i, ok := BgpOriginAttrTypeToIntMap[v] + if !ok { + return -1 + } + return i +} + +var IntToBgpOriginAttrTypeMap = map[int]BgpOriginAttrType{ + 0: BGP_ORIGIN_ATTR_TYPE_IGP, + 1: BGP_ORIGIN_ATTR_TYPE_EGP, + 2: BGP_ORIGIN_ATTR_TYPE_INCOMPLETE, +} + +func (v BgpOriginAttrType) Validate() error { + if _, ok := BgpOriginAttrTypeToIntMap[v]; !ok { + return fmt.Errorf("invalid BgpOriginAttrType: %s", v) + } + return nil +} + +// typedef for identity bgp-types:afi-safi-type. +// Base identity type for AFI,SAFI tuples for BGP-4. +type AfiSafiType string + +const ( + AFI_SAFI_TYPE_IPV4_UNICAST AfiSafiType = "ipv4-unicast" + AFI_SAFI_TYPE_IPV6_UNICAST AfiSafiType = "ipv6-unicast" + AFI_SAFI_TYPE_IPV4_LABELLED_UNICAST AfiSafiType = "ipv4-labelled-unicast" + AFI_SAFI_TYPE_IPV6_LABELLED_UNICAST AfiSafiType = "ipv6-labelled-unicast" + AFI_SAFI_TYPE_L3VPN_IPV4_UNICAST AfiSafiType = "l3vpn-ipv4-unicast" + AFI_SAFI_TYPE_L3VPN_IPV6_UNICAST AfiSafiType = "l3vpn-ipv6-unicast" + AFI_SAFI_TYPE_L3VPN_IPV4_MULTICAST AfiSafiType = "l3vpn-ipv4-multicast" + AFI_SAFI_TYPE_L3VPN_IPV6_MULTICAST AfiSafiType = "l3vpn-ipv6-multicast" + AFI_SAFI_TYPE_L2VPN_VPLS AfiSafiType = "l2vpn-vpls" + AFI_SAFI_TYPE_L2VPN_EVPN AfiSafiType = "l2vpn-evpn" + AFI_SAFI_TYPE_IPV4_MULTICAST AfiSafiType = "ipv4-multicast" + AFI_SAFI_TYPE_IPV6_MULTICAST AfiSafiType = "ipv6-multicast" + AFI_SAFI_TYPE_RTC AfiSafiType = "rtc" + AFI_SAFI_TYPE_IPV4_ENCAP AfiSafiType = "ipv4-encap" + AFI_SAFI_TYPE_IPV6_ENCAP AfiSafiType = "ipv6-encap" + AFI_SAFI_TYPE_IPV4_FLOWSPEC AfiSafiType = "ipv4-flowspec" + AFI_SAFI_TYPE_L3VPN_IPV4_FLOWSPEC AfiSafiType = "l3vpn-ipv4-flowspec" + AFI_SAFI_TYPE_IPV6_FLOWSPEC AfiSafiType = "ipv6-flowspec" + AFI_SAFI_TYPE_L3VPN_IPV6_FLOWSPEC AfiSafiType = "l3vpn-ipv6-flowspec" + AFI_SAFI_TYPE_L2VPN_FLOWSPEC AfiSafiType = "l2vpn-flowspec" + AFI_SAFI_TYPE_OPAQUE AfiSafiType = "opaque" +) + +var AfiSafiTypeToIntMap = map[AfiSafiType]int{ + AFI_SAFI_TYPE_IPV4_UNICAST: 0, + AFI_SAFI_TYPE_IPV6_UNICAST: 1, + AFI_SAFI_TYPE_IPV4_LABELLED_UNICAST: 2, + AFI_SAFI_TYPE_IPV6_LABELLED_UNICAST: 3, + AFI_SAFI_TYPE_L3VPN_IPV4_UNICAST: 4, + AFI_SAFI_TYPE_L3VPN_IPV6_UNICAST: 5, + AFI_SAFI_TYPE_L3VPN_IPV4_MULTICAST: 6, + AFI_SAFI_TYPE_L3VPN_IPV6_MULTICAST: 7, + AFI_SAFI_TYPE_L2VPN_VPLS: 8, + AFI_SAFI_TYPE_L2VPN_EVPN: 9, + AFI_SAFI_TYPE_IPV4_MULTICAST: 10, + AFI_SAFI_TYPE_IPV6_MULTICAST: 11, + AFI_SAFI_TYPE_RTC: 12, + AFI_SAFI_TYPE_IPV4_ENCAP: 13, + AFI_SAFI_TYPE_IPV6_ENCAP: 14, + AFI_SAFI_TYPE_IPV4_FLOWSPEC: 15, + AFI_SAFI_TYPE_L3VPN_IPV4_FLOWSPEC: 16, + AFI_SAFI_TYPE_IPV6_FLOWSPEC: 17, + AFI_SAFI_TYPE_L3VPN_IPV6_FLOWSPEC: 18, + AFI_SAFI_TYPE_L2VPN_FLOWSPEC: 19, + AFI_SAFI_TYPE_OPAQUE: 20, +} + +func (v AfiSafiType) ToInt() int { + i, ok := AfiSafiTypeToIntMap[v] + if !ok { + return -1 + } + return i +} + +var IntToAfiSafiTypeMap = map[int]AfiSafiType{ + 0: AFI_SAFI_TYPE_IPV4_UNICAST, + 1: AFI_SAFI_TYPE_IPV6_UNICAST, + 2: AFI_SAFI_TYPE_IPV4_LABELLED_UNICAST, + 3: AFI_SAFI_TYPE_IPV6_LABELLED_UNICAST, + 4: AFI_SAFI_TYPE_L3VPN_IPV4_UNICAST, + 5: AFI_SAFI_TYPE_L3VPN_IPV6_UNICAST, + 6: AFI_SAFI_TYPE_L3VPN_IPV4_MULTICAST, + 7: AFI_SAFI_TYPE_L3VPN_IPV6_MULTICAST, + 8: AFI_SAFI_TYPE_L2VPN_VPLS, + 9: AFI_SAFI_TYPE_L2VPN_EVPN, + 10: AFI_SAFI_TYPE_IPV4_MULTICAST, + 11: AFI_SAFI_TYPE_IPV6_MULTICAST, + 12: AFI_SAFI_TYPE_RTC, + 13: AFI_SAFI_TYPE_IPV4_ENCAP, + 14: AFI_SAFI_TYPE_IPV6_ENCAP, + 15: AFI_SAFI_TYPE_IPV4_FLOWSPEC, + 16: AFI_SAFI_TYPE_L3VPN_IPV4_FLOWSPEC, + 17: AFI_SAFI_TYPE_IPV6_FLOWSPEC, + 18: AFI_SAFI_TYPE_L3VPN_IPV6_FLOWSPEC, + 19: AFI_SAFI_TYPE_L2VPN_FLOWSPEC, + 20: AFI_SAFI_TYPE_OPAQUE, +} + +func (v AfiSafiType) Validate() error { + if _, ok := AfiSafiTypeToIntMap[v]; !ok { + return fmt.Errorf("invalid AfiSafiType: %s", v) + } + return nil +} + +// typedef for identity bgp-types:bgp-capability. +// Base identity for a BGP capability. +type BgpCapability string + +const ( + BGP_CAPABILITY_MPBGP BgpCapability = "mpbgp" + BGP_CAPABILITY_ROUTE_REFRESH BgpCapability = "route-refresh" + BGP_CAPABILITY_ASN32 BgpCapability = "asn32" + BGP_CAPABILITY_GRACEFUL_RESTART BgpCapability = "graceful-restart" + BGP_CAPABILITY_ADD_PATHS BgpCapability = "add-paths" +) + +var BgpCapabilityToIntMap = map[BgpCapability]int{ + BGP_CAPABILITY_MPBGP: 0, + BGP_CAPABILITY_ROUTE_REFRESH: 1, + BGP_CAPABILITY_ASN32: 2, + BGP_CAPABILITY_GRACEFUL_RESTART: 3, + BGP_CAPABILITY_ADD_PATHS: 4, +} + +func (v BgpCapability) ToInt() int { + i, ok := BgpCapabilityToIntMap[v] + if !ok { + return -1 + } + return i +} + +var IntToBgpCapabilityMap = map[int]BgpCapability{ + 0: BGP_CAPABILITY_MPBGP, + 1: BGP_CAPABILITY_ROUTE_REFRESH, + 2: BGP_CAPABILITY_ASN32, + 3: BGP_CAPABILITY_GRACEFUL_RESTART, + 4: BGP_CAPABILITY_ADD_PATHS, +} + +func (v BgpCapability) Validate() error { + if _, ok := BgpCapabilityToIntMap[v]; !ok { + return fmt.Errorf("invalid BgpCapability: %s", v) + } + return nil +} + +// typedef for identity bgp-types:bgp-well-known-std-community. +// Reserved communities within the standard community space +// defined by RFC1997. These communities must fall within the +// range 0x00000000 to 0xFFFFFFFF. +type BgpWellKnownStdCommunity string + +const ( + BGP_WELL_KNOWN_STD_COMMUNITY_NO_EXPORT BgpWellKnownStdCommunity = "no_export" + BGP_WELL_KNOWN_STD_COMMUNITY_NO_ADVERTISE BgpWellKnownStdCommunity = "no_advertise" + BGP_WELL_KNOWN_STD_COMMUNITY_NO_EXPORT_SUBCONFED BgpWellKnownStdCommunity = "no_export_subconfed" + BGP_WELL_KNOWN_STD_COMMUNITY_NOPEER BgpWellKnownStdCommunity = "nopeer" +) + +var BgpWellKnownStdCommunityToIntMap = map[BgpWellKnownStdCommunity]int{ + BGP_WELL_KNOWN_STD_COMMUNITY_NO_EXPORT: 0, + BGP_WELL_KNOWN_STD_COMMUNITY_NO_ADVERTISE: 1, + BGP_WELL_KNOWN_STD_COMMUNITY_NO_EXPORT_SUBCONFED: 2, + BGP_WELL_KNOWN_STD_COMMUNITY_NOPEER: 3, +} + +func (v BgpWellKnownStdCommunity) ToInt() int { + i, ok := BgpWellKnownStdCommunityToIntMap[v] + if !ok { + return -1 + } + return i +} + +var IntToBgpWellKnownStdCommunityMap = map[int]BgpWellKnownStdCommunity{ + 0: BGP_WELL_KNOWN_STD_COMMUNITY_NO_EXPORT, + 1: BGP_WELL_KNOWN_STD_COMMUNITY_NO_ADVERTISE, + 2: BGP_WELL_KNOWN_STD_COMMUNITY_NO_EXPORT_SUBCONFED, + 3: BGP_WELL_KNOWN_STD_COMMUNITY_NOPEER, +} + +func (v BgpWellKnownStdCommunity) Validate() error { + if _, ok := BgpWellKnownStdCommunityToIntMap[v]; !ok { + return fmt.Errorf("invalid BgpWellKnownStdCommunity: %s", v) + } + return nil +} + +// typedef for identity ptypes:match-set-options-restricted-type. +// Options that govern the behavior of a match statement. The +// default behavior is ANY, i.e., the given value matches any +// of the members of the defined set. Note this type is a +// restricted version of the match-set-options-type. +type MatchSetOptionsRestrictedType string + +const ( + MATCH_SET_OPTIONS_RESTRICTED_TYPE_ANY MatchSetOptionsRestrictedType = "any" + MATCH_SET_OPTIONS_RESTRICTED_TYPE_INVERT MatchSetOptionsRestrictedType = "invert" +) + +var MatchSetOptionsRestrictedTypeToIntMap = map[MatchSetOptionsRestrictedType]int{ + MATCH_SET_OPTIONS_RESTRICTED_TYPE_ANY: 0, + MATCH_SET_OPTIONS_RESTRICTED_TYPE_INVERT: 1, +} + +func (v MatchSetOptionsRestrictedType) ToInt() int { + i, ok := MatchSetOptionsRestrictedTypeToIntMap[v] + if !ok { + return -1 + } + return i +} + +var IntToMatchSetOptionsRestrictedTypeMap = map[int]MatchSetOptionsRestrictedType{ + 0: MATCH_SET_OPTIONS_RESTRICTED_TYPE_ANY, + 1: MATCH_SET_OPTIONS_RESTRICTED_TYPE_INVERT, +} + +func (v MatchSetOptionsRestrictedType) Validate() error { + if _, ok := MatchSetOptionsRestrictedTypeToIntMap[v]; !ok { + return fmt.Errorf("invalid MatchSetOptionsRestrictedType: %s", v) + } + return nil +} + +func (v MatchSetOptionsRestrictedType) Default() MatchSetOptionsRestrictedType { + return MATCH_SET_OPTIONS_RESTRICTED_TYPE_ANY +} + +func (v MatchSetOptionsRestrictedType) DefaultAsNeeded() MatchSetOptionsRestrictedType { + if string(v) == "" { + return v.Default() + } + return v +} + +// typedef for identity ptypes:match-set-options-type. +// Options that govern the behavior of a match statement. The +// default behavior is ANY, i.e., the given value matches any +// of the members of the defined set. +type MatchSetOptionsType string + +const ( + MATCH_SET_OPTIONS_TYPE_ANY MatchSetOptionsType = "any" + MATCH_SET_OPTIONS_TYPE_ALL MatchSetOptionsType = "all" + MATCH_SET_OPTIONS_TYPE_INVERT MatchSetOptionsType = "invert" +) + +var MatchSetOptionsTypeToIntMap = map[MatchSetOptionsType]int{ + MATCH_SET_OPTIONS_TYPE_ANY: 0, + MATCH_SET_OPTIONS_TYPE_ALL: 1, + MATCH_SET_OPTIONS_TYPE_INVERT: 2, +} + +func (v MatchSetOptionsType) ToInt() int { + i, ok := MatchSetOptionsTypeToIntMap[v] + if !ok { + return -1 + } + return i +} + +var IntToMatchSetOptionsTypeMap = map[int]MatchSetOptionsType{ + 0: MATCH_SET_OPTIONS_TYPE_ANY, + 1: MATCH_SET_OPTIONS_TYPE_ALL, + 2: MATCH_SET_OPTIONS_TYPE_INVERT, +} + +func (v MatchSetOptionsType) Validate() error { + if _, ok := MatchSetOptionsTypeToIntMap[v]; !ok { + return fmt.Errorf("invalid MatchSetOptionsType: %s", v) + } + return nil +} + +func (v MatchSetOptionsType) Default() MatchSetOptionsType { + return MATCH_SET_OPTIONS_TYPE_ANY +} + +func (v MatchSetOptionsType) DefaultAsNeeded() MatchSetOptionsType { + if string(v) == "" { + return v.Default() + } + return v +} + +// typedef for typedef ptypes:tag-type. +type TagType string + +// typedef for identity ptypes:install-protocol-type. +// Base type for protocols which can install prefixes into the +// RIB. +type InstallProtocolType string + +const ( + INSTALL_PROTOCOL_TYPE_BGP InstallProtocolType = "bgp" + INSTALL_PROTOCOL_TYPE_ISIS InstallProtocolType = "isis" + INSTALL_PROTOCOL_TYPE_OSPF InstallProtocolType = "ospf" + INSTALL_PROTOCOL_TYPE_OSPF3 InstallProtocolType = "ospf3" + INSTALL_PROTOCOL_TYPE_STATIC InstallProtocolType = "static" + INSTALL_PROTOCOL_TYPE_DIRECTLY_CONNECTED InstallProtocolType = "directly-connected" + INSTALL_PROTOCOL_TYPE_LOCAL_AGGREGATE InstallProtocolType = "local-aggregate" +) + +var InstallProtocolTypeToIntMap = map[InstallProtocolType]int{ + INSTALL_PROTOCOL_TYPE_BGP: 0, + INSTALL_PROTOCOL_TYPE_ISIS: 1, + INSTALL_PROTOCOL_TYPE_OSPF: 2, + INSTALL_PROTOCOL_TYPE_OSPF3: 3, + INSTALL_PROTOCOL_TYPE_STATIC: 4, + INSTALL_PROTOCOL_TYPE_DIRECTLY_CONNECTED: 5, + INSTALL_PROTOCOL_TYPE_LOCAL_AGGREGATE: 6, +} + +func (v InstallProtocolType) ToInt() int { + i, ok := InstallProtocolTypeToIntMap[v] + if !ok { + return -1 + } + return i +} + +var IntToInstallProtocolTypeMap = map[int]InstallProtocolType{ + 0: INSTALL_PROTOCOL_TYPE_BGP, + 1: INSTALL_PROTOCOL_TYPE_ISIS, + 2: INSTALL_PROTOCOL_TYPE_OSPF, + 3: INSTALL_PROTOCOL_TYPE_OSPF3, + 4: INSTALL_PROTOCOL_TYPE_STATIC, + 5: INSTALL_PROTOCOL_TYPE_DIRECTLY_CONNECTED, + 6: INSTALL_PROTOCOL_TYPE_LOCAL_AGGREGATE, +} + +func (v InstallProtocolType) Validate() error { + if _, ok := InstallProtocolTypeToIntMap[v]; !ok { + return fmt.Errorf("invalid InstallProtocolType: %s", v) + } + return nil +} + +// typedef for identity ptypes:attribute-comparison. +// base type for supported comparison operators on route +// attributes. +type AttributeComparison string + +const ( + ATTRIBUTE_COMPARISON_ATTRIBUTE_EQ AttributeComparison = "attribute-eq" + ATTRIBUTE_COMPARISON_ATTRIBUTE_GE AttributeComparison = "attribute-ge" + ATTRIBUTE_COMPARISON_ATTRIBUTE_LE AttributeComparison = "attribute-le" + ATTRIBUTE_COMPARISON_EQ AttributeComparison = "eq" + ATTRIBUTE_COMPARISON_GE AttributeComparison = "ge" + ATTRIBUTE_COMPARISON_LE AttributeComparison = "le" +) + +var AttributeComparisonToIntMap = map[AttributeComparison]int{ + ATTRIBUTE_COMPARISON_ATTRIBUTE_EQ: 0, + ATTRIBUTE_COMPARISON_ATTRIBUTE_GE: 1, + ATTRIBUTE_COMPARISON_ATTRIBUTE_LE: 2, + ATTRIBUTE_COMPARISON_EQ: 3, + ATTRIBUTE_COMPARISON_GE: 4, + ATTRIBUTE_COMPARISON_LE: 5, +} + +func (v AttributeComparison) ToInt() int { + i, ok := AttributeComparisonToIntMap[v] + if !ok { + return -1 + } + return i +} + +var IntToAttributeComparisonMap = map[int]AttributeComparison{ + 0: ATTRIBUTE_COMPARISON_ATTRIBUTE_EQ, + 1: ATTRIBUTE_COMPARISON_ATTRIBUTE_GE, + 2: ATTRIBUTE_COMPARISON_ATTRIBUTE_LE, + 3: ATTRIBUTE_COMPARISON_EQ, + 4: ATTRIBUTE_COMPARISON_GE, + 5: ATTRIBUTE_COMPARISON_LE, +} + +func (v AttributeComparison) Validate() error { + if _, ok := AttributeComparisonToIntMap[v]; !ok { + return fmt.Errorf("invalid AttributeComparison: %s", v) + } + return nil +} + +// typedef for identity rpol:route-disposition. +// Select the final disposition for the route, either +// accept or reject. +type RouteDisposition string + +const ( + ROUTE_DISPOSITION_NONE RouteDisposition = "none" + ROUTE_DISPOSITION_ACCEPT_ROUTE RouteDisposition = "accept-route" + ROUTE_DISPOSITION_REJECT_ROUTE RouteDisposition = "reject-route" +) + +var RouteDispositionToIntMap = map[RouteDisposition]int{ + ROUTE_DISPOSITION_NONE: 0, + ROUTE_DISPOSITION_ACCEPT_ROUTE: 1, + ROUTE_DISPOSITION_REJECT_ROUTE: 2, +} + +func (v RouteDisposition) ToInt() int { + i, ok := RouteDispositionToIntMap[v] + if !ok { + return -1 + } + return i +} + +var IntToRouteDispositionMap = map[int]RouteDisposition{ + 0: ROUTE_DISPOSITION_NONE, + 1: ROUTE_DISPOSITION_ACCEPT_ROUTE, + 2: ROUTE_DISPOSITION_REJECT_ROUTE, +} + +func (v RouteDisposition) Validate() error { + if _, ok := RouteDispositionToIntMap[v]; !ok { + return fmt.Errorf("invalid RouteDisposition: %s", v) + } + return nil +} + +// typedef for identity rpol:route-type. +// Condition to check the route type in the route update. +type RouteType string + +const ( + ROUTE_TYPE_NONE RouteType = "none" + ROUTE_TYPE_INTERNAL RouteType = "internal" + ROUTE_TYPE_EXTERNAL RouteType = "external" + ROUTE_TYPE_LOCAL RouteType = "local" +) + +var RouteTypeToIntMap = map[RouteType]int{ + ROUTE_TYPE_NONE: 0, + ROUTE_TYPE_INTERNAL: 1, + ROUTE_TYPE_EXTERNAL: 2, + ROUTE_TYPE_LOCAL: 3, +} + +func (v RouteType) ToInt() int { + i, ok := RouteTypeToIntMap[v] + if !ok { + return -1 + } + return i +} + +var IntToRouteTypeMap = map[int]RouteType{ + 0: ROUTE_TYPE_NONE, + 1: ROUTE_TYPE_INTERNAL, + 2: ROUTE_TYPE_EXTERNAL, + 3: ROUTE_TYPE_LOCAL, +} + +func (v RouteType) Validate() error { + if _, ok := RouteTypeToIntMap[v]; !ok { + return fmt.Errorf("invalid RouteType: %s", v) + } + return nil +} + +// typedef for identity rpol:default-policy-type. +// type used to specify default route disposition in +// a policy chain. +type DefaultPolicyType string + +const ( + DEFAULT_POLICY_TYPE_ACCEPT_ROUTE DefaultPolicyType = "accept-route" + DEFAULT_POLICY_TYPE_REJECT_ROUTE DefaultPolicyType = "reject-route" +) + +var DefaultPolicyTypeToIntMap = map[DefaultPolicyType]int{ + DEFAULT_POLICY_TYPE_ACCEPT_ROUTE: 0, + DEFAULT_POLICY_TYPE_REJECT_ROUTE: 1, +} + +func (v DefaultPolicyType) ToInt() int { + i, ok := DefaultPolicyTypeToIntMap[v] + if !ok { + return -1 + } + return i +} + +var IntToDefaultPolicyTypeMap = map[int]DefaultPolicyType{ + 0: DEFAULT_POLICY_TYPE_ACCEPT_ROUTE, + 1: DEFAULT_POLICY_TYPE_REJECT_ROUTE, +} + +func (v DefaultPolicyType) Validate() error { + if _, ok := DefaultPolicyTypeToIntMap[v]; !ok { + return fmt.Errorf("invalid DefaultPolicyType: %s", v) + } + return nil +} + +// typedef for identity bgp:session-state. +// Operational state of the BGP peer. +type SessionState string + +const ( + SESSION_STATE_IDLE SessionState = "idle" + SESSION_STATE_CONNECT SessionState = "connect" + SESSION_STATE_ACTIVE SessionState = "active" + SESSION_STATE_OPENSENT SessionState = "opensent" + SESSION_STATE_OPENCONFIRM SessionState = "openconfirm" + SESSION_STATE_ESTABLISHED SessionState = "established" +) + +var SessionStateToIntMap = map[SessionState]int{ + SESSION_STATE_IDLE: 0, + SESSION_STATE_CONNECT: 1, + SESSION_STATE_ACTIVE: 2, + SESSION_STATE_OPENSENT: 3, + SESSION_STATE_OPENCONFIRM: 4, + SESSION_STATE_ESTABLISHED: 5, +} + +func (v SessionState) ToInt() int { + i, ok := SessionStateToIntMap[v] + if !ok { + return -1 + } + return i +} + +var IntToSessionStateMap = map[int]SessionState{ + 0: SESSION_STATE_IDLE, + 1: SESSION_STATE_CONNECT, + 2: SESSION_STATE_ACTIVE, + 3: SESSION_STATE_OPENSENT, + 4: SESSION_STATE_OPENCONFIRM, + 5: SESSION_STATE_ESTABLISHED, +} + +func (v SessionState) Validate() error { + if _, ok := SessionStateToIntMap[v]; !ok { + return fmt.Errorf("invalid SessionState: %s", v) + } + return nil +} + +// typedef for identity bgp:admin-state. +type AdminState string + +const ( + ADMIN_STATE_UP AdminState = "up" + ADMIN_STATE_DOWN AdminState = "down" + ADMIN_STATE_PFX_CT AdminState = "pfx_ct" +) + +var AdminStateToIntMap = map[AdminState]int{ + ADMIN_STATE_UP: 0, + ADMIN_STATE_DOWN: 1, + ADMIN_STATE_PFX_CT: 2, +} + +func (v AdminState) ToInt() int { + i, ok := AdminStateToIntMap[v] + if !ok { + return -1 + } + return i +} + +var IntToAdminStateMap = map[int]AdminState{ + 0: ADMIN_STATE_UP, + 1: ADMIN_STATE_DOWN, + 2: ADMIN_STATE_PFX_CT, +} + +func (v AdminState) Validate() error { + if _, ok := AdminStateToIntMap[v]; !ok { + return fmt.Errorf("invalid AdminState: %s", v) + } + return nil +} + +// typedef for identity bgp:mode. +// Ths leaf indicates the mode of operation of BGP graceful +// restart with the peer. +type Mode string + +const ( + MODE_HELPER_ONLY Mode = "helper-only" + MODE_BILATERAL Mode = "bilateral" + MODE_REMOTE_HELPER Mode = "remote-helper" +) + +var ModeToIntMap = map[Mode]int{ + MODE_HELPER_ONLY: 0, + MODE_BILATERAL: 1, + MODE_REMOTE_HELPER: 2, +} + +func (v Mode) ToInt() int { + i, ok := ModeToIntMap[v] + if !ok { + return -1 + } + return i +} + +var IntToModeMap = map[int]Mode{ + 0: MODE_HELPER_ONLY, + 1: MODE_BILATERAL, + 2: MODE_REMOTE_HELPER, +} + +func (v Mode) Validate() error { + if _, ok := ModeToIntMap[v]; !ok { + return fmt.Errorf("invalid Mode: %s", v) + } + return nil +} + +// typedef for typedef bgp-pol:bgp-next-hop-type. +type BgpNextHopType string + +// typedef for typedef bgp-pol:bgp-as-path-prepend-repeat. +type BgpAsPathPrependRepeat uint8 + +// typedef for typedef bgp-pol:bgp-set-med-type. +type BgpSetMedType string + +// typedef for identity bgp-pol:bgp-set-community-option-type. +// Type definition for options when setting the community +// attribute in a policy action. +type BgpSetCommunityOptionType string + +const ( + BGP_SET_COMMUNITY_OPTION_TYPE_ADD BgpSetCommunityOptionType = "add" + BGP_SET_COMMUNITY_OPTION_TYPE_REMOVE BgpSetCommunityOptionType = "remove" + BGP_SET_COMMUNITY_OPTION_TYPE_REPLACE BgpSetCommunityOptionType = "replace" +) + +var BgpSetCommunityOptionTypeToIntMap = map[BgpSetCommunityOptionType]int{ + BGP_SET_COMMUNITY_OPTION_TYPE_ADD: 0, + BGP_SET_COMMUNITY_OPTION_TYPE_REMOVE: 1, + BGP_SET_COMMUNITY_OPTION_TYPE_REPLACE: 2, +} + +func (v BgpSetCommunityOptionType) ToInt() int { + i, ok := BgpSetCommunityOptionTypeToIntMap[v] + if !ok { + return -1 + } + return i +} + +var IntToBgpSetCommunityOptionTypeMap = map[int]BgpSetCommunityOptionType{ + 0: BGP_SET_COMMUNITY_OPTION_TYPE_ADD, + 1: BGP_SET_COMMUNITY_OPTION_TYPE_REMOVE, + 2: BGP_SET_COMMUNITY_OPTION_TYPE_REPLACE, +} + +func (v BgpSetCommunityOptionType) Validate() error { + if _, ok := BgpSetCommunityOptionTypeToIntMap[v]; !ok { + return fmt.Errorf("invalid BgpSetCommunityOptionType: %s", v) + } + return nil +} + +// typedef for identity gobgp:bmp-route-monitoring-policy-type. +type BmpRouteMonitoringPolicyType string + +const ( + BMP_ROUTE_MONITORING_POLICY_TYPE_PRE_POLICY BmpRouteMonitoringPolicyType = "pre-policy" + BMP_ROUTE_MONITORING_POLICY_TYPE_POST_POLICY BmpRouteMonitoringPolicyType = "post-policy" + BMP_ROUTE_MONITORING_POLICY_TYPE_BOTH BmpRouteMonitoringPolicyType = "both" + BMP_ROUTE_MONITORING_POLICY_TYPE_LOCAL_RIB BmpRouteMonitoringPolicyType = "local-rib" + BMP_ROUTE_MONITORING_POLICY_TYPE_ALL BmpRouteMonitoringPolicyType = "all" +) + +var BmpRouteMonitoringPolicyTypeToIntMap = map[BmpRouteMonitoringPolicyType]int{ + BMP_ROUTE_MONITORING_POLICY_TYPE_PRE_POLICY: 0, + BMP_ROUTE_MONITORING_POLICY_TYPE_POST_POLICY: 1, + BMP_ROUTE_MONITORING_POLICY_TYPE_BOTH: 2, + BMP_ROUTE_MONITORING_POLICY_TYPE_LOCAL_RIB: 3, + BMP_ROUTE_MONITORING_POLICY_TYPE_ALL: 4, +} + +func (v BmpRouteMonitoringPolicyType) ToInt() int { + i, ok := BmpRouteMonitoringPolicyTypeToIntMap[v] + if !ok { + return -1 + } + return i +} + +var IntToBmpRouteMonitoringPolicyTypeMap = map[int]BmpRouteMonitoringPolicyType{ + 0: BMP_ROUTE_MONITORING_POLICY_TYPE_PRE_POLICY, + 1: BMP_ROUTE_MONITORING_POLICY_TYPE_POST_POLICY, + 2: BMP_ROUTE_MONITORING_POLICY_TYPE_BOTH, + 3: BMP_ROUTE_MONITORING_POLICY_TYPE_LOCAL_RIB, + 4: BMP_ROUTE_MONITORING_POLICY_TYPE_ALL, +} + +func (v BmpRouteMonitoringPolicyType) Validate() error { + if _, ok := BmpRouteMonitoringPolicyTypeToIntMap[v]; !ok { + return fmt.Errorf("invalid BmpRouteMonitoringPolicyType: %s", v) + } + return nil +} + +// typedef for identity gobgp:mrt-type. +type MrtType string + +const ( + MRT_TYPE_UPDATES MrtType = "updates" + MRT_TYPE_TABLE MrtType = "table" +) + +var MrtTypeToIntMap = map[MrtType]int{ + MRT_TYPE_UPDATES: 0, + MRT_TYPE_TABLE: 1, +} + +func (v MrtType) ToInt() int { + i, ok := MrtTypeToIntMap[v] + if !ok { + return -1 + } + return i +} + +var IntToMrtTypeMap = map[int]MrtType{ + 0: MRT_TYPE_UPDATES, + 1: MRT_TYPE_TABLE, +} + +func (v MrtType) Validate() error { + if _, ok := MrtTypeToIntMap[v]; !ok { + return fmt.Errorf("invalid MrtType: %s", v) + } + return nil +} + +// typedef for identity gobgp:rpki-validation-result-type. +// indicate the validation result of RPKI based on ROA. +type RpkiValidationResultType string + +const ( + RPKI_VALIDATION_RESULT_TYPE_NONE RpkiValidationResultType = "none" + RPKI_VALIDATION_RESULT_TYPE_NOT_FOUND RpkiValidationResultType = "not-found" + RPKI_VALIDATION_RESULT_TYPE_VALID RpkiValidationResultType = "valid" + RPKI_VALIDATION_RESULT_TYPE_INVALID RpkiValidationResultType = "invalid" +) + +var RpkiValidationResultTypeToIntMap = map[RpkiValidationResultType]int{ + RPKI_VALIDATION_RESULT_TYPE_NONE: 0, + RPKI_VALIDATION_RESULT_TYPE_NOT_FOUND: 1, + RPKI_VALIDATION_RESULT_TYPE_VALID: 2, + RPKI_VALIDATION_RESULT_TYPE_INVALID: 3, +} + +func (v RpkiValidationResultType) ToInt() int { + i, ok := RpkiValidationResultTypeToIntMap[v] + if !ok { + return -1 + } + return i +} + +var IntToRpkiValidationResultTypeMap = map[int]RpkiValidationResultType{ + 0: RPKI_VALIDATION_RESULT_TYPE_NONE, + 1: RPKI_VALIDATION_RESULT_TYPE_NOT_FOUND, + 2: RPKI_VALIDATION_RESULT_TYPE_VALID, + 3: RPKI_VALIDATION_RESULT_TYPE_INVALID, +} + +func (v RpkiValidationResultType) Validate() error { + if _, ok := RpkiValidationResultTypeToIntMap[v]; !ok { + return fmt.Errorf("invalid RpkiValidationResultType: %s", v) + } + return nil +} + +// struct for container gobgp:state. +type DynamicNeighborState struct { + // original -> gobgp:prefix + Prefix string `mapstructure:"prefix" json:"prefix,omitempty"` + // original -> gobgp:peer-group + PeerGroup string `mapstructure:"peer-group" json:"peer-group,omitempty"` +} + +// struct for container gobgp:config. +type DynamicNeighborConfig struct { + // original -> gobgp:prefix + Prefix string `mapstructure:"prefix" json:"prefix,omitempty"` + // original -> gobgp:peer-group + PeerGroup string `mapstructure:"peer-group" json:"peer-group,omitempty"` +} + +func (lhs *DynamicNeighborConfig) Equal(rhs *DynamicNeighborConfig) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.Prefix != rhs.Prefix { + return false + } + if lhs.PeerGroup != rhs.PeerGroup { + return false + } + return true +} + +// struct for container gobgp:dynamic-neighbor. +type DynamicNeighbor struct { + // original -> gobgp:prefix + // original -> gobgp:dynamic-neighbor-config + Config DynamicNeighborConfig `mapstructure:"config" json:"config,omitempty"` + // original -> gobgp:dynamic-neighbor-state + State DynamicNeighborState `mapstructure:"state" json:"state,omitempty"` +} + +func (lhs *DynamicNeighbor) Equal(rhs *DynamicNeighbor) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.Config.Equal(&(rhs.Config)) { + return false + } + return true +} + +// struct for container gobgp:state. +type CollectorState struct { + // original -> gobgp:url + Url string `mapstructure:"url" json:"url,omitempty"` + // original -> gobgp:db-name + DbName string `mapstructure:"db-name" json:"db-name,omitempty"` + // original -> gobgp:table-dump-interval + TableDumpInterval uint64 `mapstructure:"table-dump-interval" json:"table-dump-interval,omitempty"` +} + +// struct for container gobgp:config. +type CollectorConfig struct { + // original -> gobgp:url + Url string `mapstructure:"url" json:"url,omitempty"` + // original -> gobgp:db-name + DbName string `mapstructure:"db-name" json:"db-name,omitempty"` + // original -> gobgp:table-dump-interval + TableDumpInterval uint64 `mapstructure:"table-dump-interval" json:"table-dump-interval,omitempty"` +} + +func (lhs *CollectorConfig) Equal(rhs *CollectorConfig) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.Url != rhs.Url { + return false + } + if lhs.DbName != rhs.DbName { + return false + } + if lhs.TableDumpInterval != rhs.TableDumpInterval { + return false + } + return true +} + +// struct for container gobgp:collector. +type Collector struct { + // original -> gobgp:collector-config + Config CollectorConfig `mapstructure:"config" json:"config,omitempty"` + // original -> gobgp:collector-state + State CollectorState `mapstructure:"state" json:"state,omitempty"` +} + +func (lhs *Collector) Equal(rhs *Collector) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.Config.Equal(&(rhs.Config)) { + return false + } + return true +} + +// struct for container gobgp:state. +type ZebraState struct { + // original -> gobgp:enabled + // gobgp:enabled's original type is boolean. + // Configure enabling to connect to zebra. + Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` + // original -> gobgp:url + // Configure url for zebra. + Url string `mapstructure:"url" json:"url,omitempty"` + // original -> gobgp:redistribute-route-type + RedistributeRouteTypeList []string `mapstructure:"redistribute-route-type-list" json:"redistribute-route-type-list,omitempty"` + // original -> gobgp:version + // Configure version of zebra protocol. Default is 2. Supported up to 3. + Version uint8 `mapstructure:"version" json:"version,omitempty"` + // original -> gobgp:nexthop-trigger-enable + // gobgp:nexthop-trigger-enable's original type is boolean. + NexthopTriggerEnable bool `mapstructure:"nexthop-trigger-enable" json:"nexthop-trigger-enable,omitempty"` + // original -> gobgp:nexthop-trigger-delay + NexthopTriggerDelay uint8 `mapstructure:"nexthop-trigger-delay" json:"nexthop-trigger-delay,omitempty"` +} + +// struct for container gobgp:config. +type ZebraConfig struct { + // original -> gobgp:enabled + // gobgp:enabled's original type is boolean. + // Configure enabling to connect to zebra. + Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` + // original -> gobgp:url + // Configure url for zebra. + Url string `mapstructure:"url" json:"url,omitempty"` + // original -> gobgp:redistribute-route-type + RedistributeRouteTypeList []string `mapstructure:"redistribute-route-type-list" json:"redistribute-route-type-list,omitempty"` + // original -> gobgp:version + // Configure version of zebra protocol. Default is 2. Supported up to 3. + Version uint8 `mapstructure:"version" json:"version,omitempty"` + // original -> gobgp:nexthop-trigger-enable + // gobgp:nexthop-trigger-enable's original type is boolean. + NexthopTriggerEnable bool `mapstructure:"nexthop-trigger-enable" json:"nexthop-trigger-enable,omitempty"` + // original -> gobgp:nexthop-trigger-delay + NexthopTriggerDelay uint8 `mapstructure:"nexthop-trigger-delay" json:"nexthop-trigger-delay,omitempty"` +} + +func (lhs *ZebraConfig) Equal(rhs *ZebraConfig) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.Enabled != rhs.Enabled { + return false + } + if lhs.Url != rhs.Url { + return false + } + if len(lhs.RedistributeRouteTypeList) != len(rhs.RedistributeRouteTypeList) { + return false + } + for idx, l := range lhs.RedistributeRouteTypeList { + if l != rhs.RedistributeRouteTypeList[idx] { + return false + } + } + if lhs.Version != rhs.Version { + return false + } + if lhs.NexthopTriggerEnable != rhs.NexthopTriggerEnable { + return false + } + if lhs.NexthopTriggerDelay != rhs.NexthopTriggerDelay { + return false + } + return true +} + +// struct for container gobgp:zebra. +type Zebra struct { + // original -> gobgp:zebra-config + Config ZebraConfig `mapstructure:"config" json:"config,omitempty"` + // original -> gobgp:zebra-state + State ZebraState `mapstructure:"state" json:"state,omitempty"` +} + +func (lhs *Zebra) Equal(rhs *Zebra) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.Config.Equal(&(rhs.Config)) { + return false + } + return true +} + +// struct for container gobgp:config. +type MrtConfig struct { + // original -> gobgp:dump-type + DumpType MrtType `mapstructure:"dump-type" json:"dump-type,omitempty"` + // original -> gobgp:file-name + // Configures a file name to be written. + FileName string `mapstructure:"file-name" json:"file-name,omitempty"` + // original -> gobgp:table-name + // specify the table name with route server setup. + TableName string `mapstructure:"table-name" json:"table-name,omitempty"` + // original -> gobgp:dump-interval + DumpInterval uint64 `mapstructure:"dump-interval" json:"dump-interval,omitempty"` + // original -> gobgp:rotation-interval + RotationInterval uint64 `mapstructure:"rotation-interval" json:"rotation-interval,omitempty"` +} + +func (lhs *MrtConfig) Equal(rhs *MrtConfig) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.DumpType != rhs.DumpType { + return false + } + if lhs.FileName != rhs.FileName { + return false + } + if lhs.TableName != rhs.TableName { + return false + } + if lhs.DumpInterval != rhs.DumpInterval { + return false + } + if lhs.RotationInterval != rhs.RotationInterval { + return false + } + return true +} + +// struct for container gobgp:mrt. +type Mrt struct { + // original -> gobgp:file-name + // original -> gobgp:mrt-config + Config MrtConfig `mapstructure:"config" json:"config,omitempty"` +} + +func (lhs *Mrt) Equal(rhs *Mrt) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.Config.Equal(&(rhs.Config)) { + return false + } + return true +} + +// struct for container gobgp:state. +// Configured states of VRF. +type VrfState struct { + // original -> gobgp:name + // Unique name among all VRF instances. + Name string `mapstructure:"name" json:"name,omitempty"` + // original -> gobgp:id + // Unique identifier among all VRF instances. + Id uint32 `mapstructure:"id" json:"id,omitempty"` + // original -> gobgp:rd + // Route Distinguisher for this VRF. + Rd string `mapstructure:"rd" json:"rd,omitempty"` + // original -> gobgp:import-rt + // List of import Route Targets for this VRF. + ImportRtList []string `mapstructure:"import-rt-list" json:"import-rt-list,omitempty"` + // original -> gobgp:export-rt + // List of export Route Targets for this VRF. + ExportRtList []string `mapstructure:"export-rt-list" json:"export-rt-list,omitempty"` +} + +// struct for container gobgp:config. +// Configuration parameters for VRF. +type VrfConfig struct { + // original -> gobgp:name + // Unique name among all VRF instances. + Name string `mapstructure:"name" json:"name,omitempty"` + // original -> gobgp:id + // Unique identifier among all VRF instances. + Id uint32 `mapstructure:"id" json:"id,omitempty"` + // original -> gobgp:rd + // Route Distinguisher for this VRF. + Rd string `mapstructure:"rd" json:"rd,omitempty"` + // original -> gobgp:import-rt + // List of import Route Targets for this VRF. + ImportRtList []string `mapstructure:"import-rt-list" json:"import-rt-list,omitempty"` + // original -> gobgp:export-rt + // List of export Route Targets for this VRF. + ExportRtList []string `mapstructure:"export-rt-list" json:"export-rt-list,omitempty"` + // original -> gobgp:both-rt + // List of both import and export Route Targets for this VRF. Each + // configuration for import and export Route Targets will be preferred. + BothRtList []string `mapstructure:"both-rt-list" json:"both-rt-list,omitempty"` +} + +func (lhs *VrfConfig) Equal(rhs *VrfConfig) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.Name != rhs.Name { + return false + } + if lhs.Id != rhs.Id { + return false + } + if lhs.Rd != rhs.Rd { + return false + } + if len(lhs.ImportRtList) != len(rhs.ImportRtList) { + return false + } + for idx, l := range lhs.ImportRtList { + if l != rhs.ImportRtList[idx] { + return false + } + } + if len(lhs.ExportRtList) != len(rhs.ExportRtList) { + return false + } + for idx, l := range lhs.ExportRtList { + if l != rhs.ExportRtList[idx] { + return false + } + } + if len(lhs.BothRtList) != len(rhs.BothRtList) { + return false + } + for idx, l := range lhs.BothRtList { + if l != rhs.BothRtList[idx] { + return false + } + } + return true +} + +// struct for container gobgp:vrf. +// VRF instance configurations on the local system. +type Vrf struct { + // original -> gobgp:name + // original -> gobgp:vrf-config + // Configuration parameters for VRF. + Config VrfConfig `mapstructure:"config" json:"config,omitempty"` + // original -> gobgp:vrf-state + // Configured states of VRF. + State VrfState `mapstructure:"state" json:"state,omitempty"` +} + +func (lhs *Vrf) Equal(rhs *Vrf) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.Config.Equal(&(rhs.Config)) { + return false + } + return true +} + +// struct for container gobgp:state. +// Configuration parameters relating to BMP server. +type BmpServerState struct { + // original -> gobgp:address + // gobgp:address's original type is inet:ip-address. + // Reference to the address of the BMP server used as + // a key in the BMP server list. + Address string `mapstructure:"address" json:"address,omitempty"` + // original -> gobgp:port + // Reference to the port of the BMP server. + Port uint32 `mapstructure:"port" json:"port,omitempty"` + // original -> gobgp:route-monitoring-policy + RouteMonitoringPolicy BmpRouteMonitoringPolicyType `mapstructure:"route-monitoring-policy" json:"route-monitoring-policy,omitempty"` + // original -> gobgp:statistics-timeout + // Interval seconds of statistics messages sent to BMP server. + StatisticsTimeout uint16 `mapstructure:"statistics-timeout" json:"statistics-timeout,omitempty"` + // original -> gobgp:route-mirroring-enabled + // gobgp:route-mirroring-enabled's original type is boolean. + // Enable feature for mirroring of received BGP messages + // mainly for debugging purpose. + RouteMirroringEnabled bool `mapstructure:"route-mirroring-enabled" json:"route-mirroring-enabled,omitempty"` +} + +// struct for container gobgp:config. +// Configuration parameters relating to BMP server. +type BmpServerConfig struct { + // original -> gobgp:address + // gobgp:address's original type is inet:ip-address. + // Reference to the address of the BMP server used as + // a key in the BMP server list. + Address string `mapstructure:"address" json:"address,omitempty"` + // original -> gobgp:port + // Reference to the port of the BMP server. + Port uint32 `mapstructure:"port" json:"port,omitempty"` + // original -> gobgp:route-monitoring-policy + RouteMonitoringPolicy BmpRouteMonitoringPolicyType `mapstructure:"route-monitoring-policy" json:"route-monitoring-policy,omitempty"` + // original -> gobgp:statistics-timeout + // Interval seconds of statistics messages sent to BMP server. + StatisticsTimeout uint16 `mapstructure:"statistics-timeout" json:"statistics-timeout,omitempty"` + // original -> gobgp:route-mirroring-enabled + // gobgp:route-mirroring-enabled's original type is boolean. + // Enable feature for mirroring of received BGP messages + // mainly for debugging purpose. + RouteMirroringEnabled bool `mapstructure:"route-mirroring-enabled" json:"route-mirroring-enabled,omitempty"` +} + +func (lhs *BmpServerConfig) Equal(rhs *BmpServerConfig) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.Address != rhs.Address { + return false + } + if lhs.Port != rhs.Port { + return false + } + if lhs.RouteMonitoringPolicy != rhs.RouteMonitoringPolicy { + return false + } + if lhs.StatisticsTimeout != rhs.StatisticsTimeout { + return false + } + if lhs.RouteMirroringEnabled != rhs.RouteMirroringEnabled { + return false + } + return true +} + +// struct for container gobgp:bmp-server. +// List of BMP servers configured on the local system. +type BmpServer struct { + // original -> gobgp:address + // original -> gobgp:bmp-server-config + // Configuration parameters relating to BMP server. + Config BmpServerConfig `mapstructure:"config" json:"config,omitempty"` + // original -> gobgp:bmp-server-state + // Configuration parameters relating to BMP server. + State BmpServerState `mapstructure:"state" json:"state,omitempty"` +} + +func (lhs *BmpServer) Equal(rhs *BmpServer) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.Config.Equal(&(rhs.Config)) { + return false + } + return true +} + +// struct for container gobgp:rpki-received. +// Counters for reception RPKI Message types. +type RpkiReceived struct { + // original -> gobgp:serial-notify + // Number of serial notify message received from RPKI server. + SerialNotify int64 `mapstructure:"serial-notify" json:"serial-notify,omitempty"` + // original -> gobgp:cache-reset + // Number of cache reset message received from RPKI server. + CacheReset int64 `mapstructure:"cache-reset" json:"cache-reset,omitempty"` + // original -> gobgp:cache-response + // Number of cache response message received from RPKI server. + CacheResponse int64 `mapstructure:"cache-response" json:"cache-response,omitempty"` + // original -> gobgp:ipv4-prefix + // Number of ipv4 prefix message received from RPKI server. + Ipv4Prefix int64 `mapstructure:"ipv4-prefix" json:"ipv4-prefix,omitempty"` + // original -> gobgp:ipv6-prefix + // Number of ipv6 prefix message received from RPKI server. + Ipv6Prefix int64 `mapstructure:"ipv6-prefix" json:"ipv6-prefix,omitempty"` + // original -> gobgp:end-of-data + // Number of end of data message received from RPKI server. + EndOfData int64 `mapstructure:"end-of-data" json:"end-of-data,omitempty"` + // original -> gobgp:error + // Number of error message received from RPKI server. + Error int64 `mapstructure:"error" json:"error,omitempty"` +} + +func (lhs *RpkiReceived) Equal(rhs *RpkiReceived) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.SerialNotify != rhs.SerialNotify { + return false + } + if lhs.CacheReset != rhs.CacheReset { + return false + } + if lhs.CacheResponse != rhs.CacheResponse { + return false + } + if lhs.Ipv4Prefix != rhs.Ipv4Prefix { + return false + } + if lhs.Ipv6Prefix != rhs.Ipv6Prefix { + return false + } + if lhs.EndOfData != rhs.EndOfData { + return false + } + if lhs.Error != rhs.Error { + return false + } + return true +} + +// struct for container gobgp:rpki-sent. +// Counters for transmission RPKI Message types. +type RpkiSent struct { + // original -> gobgp:serial-query + // Number of serial query message sent to RPKI server. + SerialQuery int64 `mapstructure:"serial-query" json:"serial-query,omitempty"` + // original -> gobgp:reset-query + // Number of reset query message sent to RPKI server. + ResetQuery int64 `mapstructure:"reset-query" json:"reset-query,omitempty"` + // original -> gobgp:error + // Number of error message sent to RPKI server. + Error int64 `mapstructure:"error" json:"error,omitempty"` +} + +func (lhs *RpkiSent) Equal(rhs *RpkiSent) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.SerialQuery != rhs.SerialQuery { + return false + } + if lhs.ResetQuery != rhs.ResetQuery { + return false + } + if lhs.Error != rhs.Error { + return false + } + return true +} + +// struct for container gobgp:rpki-messages. +// Counters for transmission and reception RPKI Message types. +type RpkiMessages struct { + // original -> gobgp:rpki-sent + // Counters for transmission RPKI Message types. + RpkiSent RpkiSent `mapstructure:"rpki-sent" json:"rpki-sent,omitempty"` + // original -> gobgp:rpki-received + // Counters for reception RPKI Message types. + RpkiReceived RpkiReceived `mapstructure:"rpki-received" json:"rpki-received,omitempty"` +} + +func (lhs *RpkiMessages) Equal(rhs *RpkiMessages) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.RpkiSent.Equal(&(rhs.RpkiSent)) { + return false + } + if !lhs.RpkiReceived.Equal(&(rhs.RpkiReceived)) { + return false + } + return true +} + +// struct for container gobgp:state. +// State information relating to RPKI server. +type RpkiServerState struct { + // original -> gobgp:up + // gobgp:up's original type is boolean. + Up bool `mapstructure:"up" json:"up,omitempty"` + // original -> gobgp:serial-number + SerialNumber uint32 `mapstructure:"serial-number" json:"serial-number,omitempty"` + // original -> gobgp:records-v4 + RecordsV4 uint32 `mapstructure:"records-v4" json:"records-v4,omitempty"` + // original -> gobgp:records-v6 + RecordsV6 uint32 `mapstructure:"records-v6" json:"records-v6,omitempty"` + // original -> gobgp:prefixes-v4 + PrefixesV4 uint32 `mapstructure:"prefixes-v4" json:"prefixes-v4,omitempty"` + // original -> gobgp:prefixes-v6 + PrefixesV6 uint32 `mapstructure:"prefixes-v6" json:"prefixes-v6,omitempty"` + // original -> gobgp:uptime + // This timer determines the amount of time since the + // RPKI last transitioned in of the Established state. + Uptime int64 `mapstructure:"uptime" json:"uptime,omitempty"` + // original -> gobgp:downtime + // This timer determines the amount of time since the + // RPKI last transitioned out of the Established state. + Downtime int64 `mapstructure:"downtime" json:"downtime,omitempty"` + // original -> gobgp:last-pdu-recv-time + // last time the received an pdu message from RPKI server. + LastPduRecvTime int64 `mapstructure:"last-pdu-recv-time" json:"last-pdu-recv-time,omitempty"` + // original -> gobgp:rpki-messages + // Counters for transmission and reception RPKI Message types. + RpkiMessages RpkiMessages `mapstructure:"rpki-messages" json:"rpki-messages,omitempty"` +} + +// struct for container gobgp:config. +// Configuration parameters relating to RPKI server. +type RpkiServerConfig struct { + // original -> gobgp:address + // gobgp:address's original type is inet:ip-address. + // Reference to the address of the RPKI server used as + // a key in the RPKI server list. + Address string `mapstructure:"address" json:"address,omitempty"` + // original -> gobgp:port + // Reference to the port of the RPKI server. + Port uint32 `mapstructure:"port" json:"port,omitempty"` + // original -> gobgp:refresh-time + // Check interval for a configured RPKI server. + RefreshTime int64 `mapstructure:"refresh-time" json:"refresh-time,omitempty"` + // original -> gobgp:hold-time + // Specify the length of time in seconds that the session between + // the router and RPKI server is to be considered operational + // without any activity. + HoldTime int64 `mapstructure:"hold-time" json:"hold-time,omitempty"` + // original -> gobgp:record-lifetime + // Indicate the expiration date of the route validation recode + // received from RPKI server. + RecordLifetime int64 `mapstructure:"record-lifetime" json:"record-lifetime,omitempty"` + // original -> gobgp:preference + // RPKI server has a static preference. + // Higher the preference values indicates a higher priority RPKI server. + Preference uint8 `mapstructure:"preference" json:"preference,omitempty"` +} + +func (lhs *RpkiServerConfig) Equal(rhs *RpkiServerConfig) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.Address != rhs.Address { + return false + } + if lhs.Port != rhs.Port { + return false + } + if lhs.RefreshTime != rhs.RefreshTime { + return false + } + if lhs.HoldTime != rhs.HoldTime { + return false + } + if lhs.RecordLifetime != rhs.RecordLifetime { + return false + } + if lhs.Preference != rhs.Preference { + return false + } + return true +} + +// struct for container gobgp:rpki-server. +// List of RPKI servers configured on the local system. +type RpkiServer struct { + // original -> gobgp:address + // original -> gobgp:rpki-server-config + // Configuration parameters relating to RPKI server. + Config RpkiServerConfig `mapstructure:"config" json:"config,omitempty"` + // original -> gobgp:rpki-server-state + // State information relating to RPKI server. + State RpkiServerState `mapstructure:"state" json:"state,omitempty"` +} + +func (lhs *RpkiServer) Equal(rhs *RpkiServer) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.Config.Equal(&(rhs.Config)) { + return false + } + return true +} + +// struct for container bgp:state. +// State information relating to the BGP neighbor or group. +type PeerGroupState struct { + // original -> bgp:peer-as + // bgp:peer-as's original type is inet:as-number. + // AS number of the peer. + PeerAs uint32 `mapstructure:"peer-as" json:"peer-as,omitempty"` + // original -> bgp:local-as + // bgp:local-as's original type is inet:as-number. + // The local autonomous system number that is to be used + // when establishing sessions with the remote peer or peer + // group, if this differs from the global BGP router + // autonomous system number. + LocalAs uint32 `mapstructure:"local-as" json:"local-as,omitempty"` + // original -> bgp:peer-type + // Explicitly designate the peer or peer group as internal + // (iBGP) or external (eBGP). + PeerType PeerType `mapstructure:"peer-type" json:"peer-type,omitempty"` + // original -> bgp:auth-password + // Configures an MD5 authentication password for use with + // neighboring devices. + AuthPassword string `mapstructure:"auth-password" json:"auth-password,omitempty"` + // original -> bgp:remove-private-as + // Remove private AS numbers from updates sent to peers. + RemovePrivateAs RemovePrivateAsOption `mapstructure:"remove-private-as" json:"remove-private-as,omitempty"` + // original -> bgp:route-flap-damping + // bgp:route-flap-damping's original type is boolean. + // Enable route flap damping. + RouteFlapDamping bool `mapstructure:"route-flap-damping" json:"route-flap-damping,omitempty"` + // original -> bgp:send-community + // Specify which types of community should be sent to the + // neighbor or group. The default is to not send the + // community attribute. + SendCommunity CommunityType `mapstructure:"send-community" json:"send-community,omitempty"` + // original -> bgp:description + // An optional textual description (intended primarily for use + // with a peer or group. + Description string `mapstructure:"description" json:"description,omitempty"` + // original -> bgp:peer-group-name + // Name of the BGP peer-group. + PeerGroupName string `mapstructure:"peer-group-name" json:"peer-group-name,omitempty"` + // original -> bgp-op:total-paths + // Total number of BGP paths within the context. + TotalPaths uint32 `mapstructure:"total-paths" json:"total-paths,omitempty"` + // original -> bgp-op:total-prefixes + // . + TotalPrefixes uint32 `mapstructure:"total-prefixes" json:"total-prefixes,omitempty"` +} + +// struct for container bgp:config. +// Configuration parameters relating to the BGP neighbor or +// group. +type PeerGroupConfig struct { + // original -> bgp:peer-as + // bgp:peer-as's original type is inet:as-number. + // AS number of the peer. + PeerAs uint32 `mapstructure:"peer-as" json:"peer-as,omitempty"` + // original -> bgp:local-as + // bgp:local-as's original type is inet:as-number. + // The local autonomous system number that is to be used + // when establishing sessions with the remote peer or peer + // group, if this differs from the global BGP router + // autonomous system number. + LocalAs uint32 `mapstructure:"local-as" json:"local-as,omitempty"` + // original -> bgp:peer-type + // Explicitly designate the peer or peer group as internal + // (iBGP) or external (eBGP). + PeerType PeerType `mapstructure:"peer-type" json:"peer-type,omitempty"` + // original -> bgp:auth-password + // Configures an MD5 authentication password for use with + // neighboring devices. + AuthPassword string `mapstructure:"auth-password" json:"auth-password,omitempty"` + // original -> bgp:remove-private-as + // Remove private AS numbers from updates sent to peers. + RemovePrivateAs RemovePrivateAsOption `mapstructure:"remove-private-as" json:"remove-private-as,omitempty"` + // original -> bgp:route-flap-damping + // bgp:route-flap-damping's original type is boolean. + // Enable route flap damping. + RouteFlapDamping bool `mapstructure:"route-flap-damping" json:"route-flap-damping,omitempty"` + // original -> bgp:send-community + // Specify which types of community should be sent to the + // neighbor or group. The default is to not send the + // community attribute. + SendCommunity CommunityType `mapstructure:"send-community" json:"send-community,omitempty"` + // original -> bgp:description + // An optional textual description (intended primarily for use + // with a peer or group. + Description string `mapstructure:"description" json:"description,omitempty"` + // original -> bgp:peer-group-name + // Name of the BGP peer-group. + PeerGroupName string `mapstructure:"peer-group-name" json:"peer-group-name,omitempty"` +} + +func (lhs *PeerGroupConfig) Equal(rhs *PeerGroupConfig) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.PeerAs != rhs.PeerAs { + return false + } + if lhs.LocalAs != rhs.LocalAs { + return false + } + if lhs.PeerType != rhs.PeerType { + return false + } + if lhs.AuthPassword != rhs.AuthPassword { + return false + } + if lhs.RemovePrivateAs != rhs.RemovePrivateAs { + return false + } + if lhs.RouteFlapDamping != rhs.RouteFlapDamping { + return false + } + if lhs.SendCommunity != rhs.SendCommunity { + return false + } + if lhs.Description != rhs.Description { + return false + } + if lhs.PeerGroupName != rhs.PeerGroupName { + return false + } + return true +} + +// struct for container bgp:peer-group. +// List of BGP peer-groups configured on the local system - +// uniquely identified by peer-group name. +type PeerGroup struct { + // original -> bgp:peer-group-name + // original -> bgp:peer-group-config + // Configuration parameters relating to the BGP neighbor or + // group. + Config PeerGroupConfig `mapstructure:"config" json:"config,omitempty"` + // original -> bgp:peer-group-state + // State information relating to the BGP neighbor or group. + State PeerGroupState `mapstructure:"state" json:"state,omitempty"` + // original -> bgp:timers + // Timers related to a BGP neighbor or group. + Timers Timers `mapstructure:"timers" json:"timers,omitempty"` + // original -> bgp:transport + // Transport session parameters for the BGP neighbor or group. + Transport Transport `mapstructure:"transport" json:"transport,omitempty"` + // original -> bgp:error-handling + // Error handling parameters used for the BGP neighbor or + // group. + ErrorHandling ErrorHandling `mapstructure:"error-handling" json:"error-handling,omitempty"` + // original -> bgp:logging-options + // Logging options for events related to the BGP neighbor or + // group. + LoggingOptions LoggingOptions `mapstructure:"logging-options" json:"logging-options,omitempty"` + // original -> bgp:ebgp-multihop + // eBGP multi-hop parameters for the BGP neighbor or group. + EbgpMultihop EbgpMultihop `mapstructure:"ebgp-multihop" json:"ebgp-multihop,omitempty"` + // original -> bgp:route-reflector + // Route reflector parameters for the BGP neighbor or group. + RouteReflector RouteReflector `mapstructure:"route-reflector" json:"route-reflector,omitempty"` + // original -> bgp:as-path-options + // AS_PATH manipulation parameters for the BGP neighbor or + // group. + AsPathOptions AsPathOptions `mapstructure:"as-path-options" json:"as-path-options,omitempty"` + // original -> bgp:add-paths + // Parameters relating to the advertisement and receipt of + // multiple paths for a single NLRI (add-paths). + AddPaths AddPaths `mapstructure:"add-paths" json:"add-paths,omitempty"` + // original -> bgp:afi-safis + // Per-address-family configuration parameters associated with + // the neighbor or group. + AfiSafis []AfiSafi `mapstructure:"afi-safis" json:"afi-safis,omitempty"` + // original -> bgp:graceful-restart + // Parameters relating the graceful restart mechanism for BGP. + GracefulRestart GracefulRestart `mapstructure:"graceful-restart" json:"graceful-restart,omitempty"` + // original -> rpol:apply-policy + // Anchor point for routing policies in the model. + // Import and export policies are with respect to the local + // routing table, i.e., export (send) and import (receive), + // depending on the context. + ApplyPolicy ApplyPolicy `mapstructure:"apply-policy" json:"apply-policy,omitempty"` + // original -> bgp-mp:use-multiple-paths + // Parameters related to the use of multiple paths for the + // same NLRI. + UseMultiplePaths UseMultiplePaths `mapstructure:"use-multiple-paths" json:"use-multiple-paths,omitempty"` + // original -> gobgp:route-server + // Configure the local router as a route server. + RouteServer RouteServer `mapstructure:"route-server" json:"route-server,omitempty"` + // original -> gobgp:ttl-security + // Configure TTL Security feature. + TtlSecurity TtlSecurity `mapstructure:"ttl-security" json:"ttl-security,omitempty"` +} + +func (lhs *PeerGroup) Equal(rhs *PeerGroup) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.Config.Equal(&(rhs.Config)) { + return false + } + if !lhs.Timers.Equal(&(rhs.Timers)) { + return false + } + if !lhs.Transport.Equal(&(rhs.Transport)) { + return false + } + if !lhs.ErrorHandling.Equal(&(rhs.ErrorHandling)) { + return false + } + if !lhs.LoggingOptions.Equal(&(rhs.LoggingOptions)) { + return false + } + if !lhs.EbgpMultihop.Equal(&(rhs.EbgpMultihop)) { + return false + } + if !lhs.RouteReflector.Equal(&(rhs.RouteReflector)) { + return false + } + if !lhs.AsPathOptions.Equal(&(rhs.AsPathOptions)) { + return false + } + if !lhs.AddPaths.Equal(&(rhs.AddPaths)) { + return false + } + if len(lhs.AfiSafis) != len(rhs.AfiSafis) { + return false + } + { + lmap := make(map[string]*AfiSafi) + for i, l := range lhs.AfiSafis { + lmap[mapkey(i, string(l.Config.AfiSafiName))] = &lhs.AfiSafis[i] + } + for i, r := range rhs.AfiSafis { + if l, y := lmap[mapkey(i, string(r.Config.AfiSafiName))]; !y { + return false + } else if !r.Equal(l) { + return false + } + } + } + if !lhs.GracefulRestart.Equal(&(rhs.GracefulRestart)) { + return false + } + if !lhs.ApplyPolicy.Equal(&(rhs.ApplyPolicy)) { + return false + } + if !lhs.UseMultiplePaths.Equal(&(rhs.UseMultiplePaths)) { + return false + } + if !lhs.RouteServer.Equal(&(rhs.RouteServer)) { + return false + } + if !lhs.TtlSecurity.Equal(&(rhs.TtlSecurity)) { + return false + } + return true +} + +// struct for container gobgp:state. +// State information for TTL Security. +type TtlSecurityState struct { + // original -> gobgp:enabled + // gobgp:enabled's original type is boolean. + // Enable features for TTL Security. + Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` + // original -> gobgp:ttl-min + // Reference to the port of the BMP server. + TtlMin uint8 `mapstructure:"ttl-min" json:"ttl-min,omitempty"` +} + +// struct for container gobgp:config. +// Configuration parameters for TTL Security. +type TtlSecurityConfig struct { + // original -> gobgp:enabled + // gobgp:enabled's original type is boolean. + // Enable features for TTL Security. + Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` + // original -> gobgp:ttl-min + // Reference to the port of the BMP server. + TtlMin uint8 `mapstructure:"ttl-min" json:"ttl-min,omitempty"` +} + +func (lhs *TtlSecurityConfig) Equal(rhs *TtlSecurityConfig) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.Enabled != rhs.Enabled { + return false + } + if lhs.TtlMin != rhs.TtlMin { + return false + } + return true +} + +// struct for container gobgp:ttl-security. +// Configure TTL Security feature. +type TtlSecurity struct { + // original -> gobgp:ttl-security-config + // Configuration parameters for TTL Security. + Config TtlSecurityConfig `mapstructure:"config" json:"config,omitempty"` + // original -> gobgp:ttl-security-state + // State information for TTL Security. + State TtlSecurityState `mapstructure:"state" json:"state,omitempty"` +} + +func (lhs *TtlSecurity) Equal(rhs *TtlSecurity) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.Config.Equal(&(rhs.Config)) { + return false + } + return true +} + +// struct for container gobgp:state. +// State information relating to route server +// client(s) used for the BGP neighbor. +type RouteServerState struct { + // original -> gobgp:route-server-client + // gobgp:route-server-client's original type is boolean. + // Configure the neighbor as a route server client. + RouteServerClient bool `mapstructure:"route-server-client" json:"route-server-client,omitempty"` +} + +// struct for container gobgp:config. +// Configuration parameters relating to route server +// client(s) used for the BGP neighbor. +type RouteServerConfig struct { + // original -> gobgp:route-server-client + // gobgp:route-server-client's original type is boolean. + // Configure the neighbor as a route server client. + RouteServerClient bool `mapstructure:"route-server-client" json:"route-server-client,omitempty"` +} + +func (lhs *RouteServerConfig) Equal(rhs *RouteServerConfig) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.RouteServerClient != rhs.RouteServerClient { + return false + } + return true +} + +// struct for container gobgp:route-server. +// Configure the local router as a route server. +type RouteServer struct { + // original -> gobgp:route-server-config + // Configuration parameters relating to route server + // client(s) used for the BGP neighbor. + Config RouteServerConfig `mapstructure:"config" json:"config,omitempty"` + // original -> gobgp:route-server-state + // State information relating to route server + // client(s) used for the BGP neighbor. + State RouteServerState `mapstructure:"state" json:"state,omitempty"` +} + +func (lhs *RouteServer) Equal(rhs *RouteServer) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.Config.Equal(&(rhs.Config)) { + return false + } + return true +} + +// struct for container bgp-op:prefixes. +// Prefix counters for the BGP session. +type Prefixes struct { + // original -> bgp-op:received + // The number of prefixes received from the neighbor. + Received uint32 `mapstructure:"received" json:"received,omitempty"` + // original -> bgp-op:sent + // The number of prefixes advertised to the neighbor. + Sent uint32 `mapstructure:"sent" json:"sent,omitempty"` + // original -> bgp-op:installed + // The number of advertised prefixes installed in the + // Loc-RIB. + Installed uint32 `mapstructure:"installed" json:"installed,omitempty"` +} + +func (lhs *Prefixes) Equal(rhs *Prefixes) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.Received != rhs.Received { + return false + } + if lhs.Sent != rhs.Sent { + return false + } + if lhs.Installed != rhs.Installed { + return false + } + return true +} + +// struct for container bgp:state. +// State information associated with ADD_PATHS. +type AddPathsState struct { + // original -> bgp:receive + // bgp:receive's original type is boolean. + // Enable ability to receive multiple path advertisements + // for an NLRI from the neighbor or group. + Receive bool `mapstructure:"receive" json:"receive,omitempty"` + // original -> bgp:send-max + // The maximum number of paths to advertise to neighbors + // for a single NLRI. + SendMax uint8 `mapstructure:"send-max" json:"send-max,omitempty"` +} + +// struct for container bgp:config. +// Configuration parameters relating to ADD_PATHS. +type AddPathsConfig struct { + // original -> bgp:receive + // bgp:receive's original type is boolean. + // Enable ability to receive multiple path advertisements + // for an NLRI from the neighbor or group. + Receive bool `mapstructure:"receive" json:"receive,omitempty"` + // original -> bgp:send-max + // The maximum number of paths to advertise to neighbors + // for a single NLRI. + SendMax uint8 `mapstructure:"send-max" json:"send-max,omitempty"` +} + +func (lhs *AddPathsConfig) Equal(rhs *AddPathsConfig) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.Receive != rhs.Receive { + return false + } + if lhs.SendMax != rhs.SendMax { + return false + } + return true +} + +// struct for container bgp:add-paths. +// Parameters relating to the advertisement and receipt of +// multiple paths for a single NLRI (add-paths). +type AddPaths struct { + // original -> bgp:add-paths-config + // Configuration parameters relating to ADD_PATHS. + Config AddPathsConfig `mapstructure:"config" json:"config,omitempty"` + // original -> bgp:add-paths-state + // State information associated with ADD_PATHS. + State AddPathsState `mapstructure:"state" json:"state,omitempty"` +} + +func (lhs *AddPaths) Equal(rhs *AddPaths) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.Config.Equal(&(rhs.Config)) { + return false + } + return true +} + +// struct for container bgp:state. +// State information relating to the AS_PATH manipulation +// mechanisms for the BGP peer or group. +type AsPathOptionsState struct { + // original -> bgp:allow-own-as + // Specify the number of occurrences of the local BGP speaker's + // AS that can occur within the AS_PATH before it is rejected. + AllowOwnAs uint8 `mapstructure:"allow-own-as" json:"allow-own-as,omitempty"` + // original -> bgp:replace-peer-as + // bgp:replace-peer-as's original type is boolean. + // Replace occurrences of the peer's AS in the AS_PATH + // with the local autonomous system number. + ReplacePeerAs bool `mapstructure:"replace-peer-as" json:"replace-peer-as,omitempty"` +} + +// struct for container bgp:config. +// Configuration parameters relating to AS_PATH manipulation +// for the BGP peer or group. +type AsPathOptionsConfig struct { + // original -> bgp:allow-own-as + // Specify the number of occurrences of the local BGP speaker's + // AS that can occur within the AS_PATH before it is rejected. + AllowOwnAs uint8 `mapstructure:"allow-own-as" json:"allow-own-as,omitempty"` + // original -> bgp:replace-peer-as + // bgp:replace-peer-as's original type is boolean. + // Replace occurrences of the peer's AS in the AS_PATH + // with the local autonomous system number. + ReplacePeerAs bool `mapstructure:"replace-peer-as" json:"replace-peer-as,omitempty"` +} + +func (lhs *AsPathOptionsConfig) Equal(rhs *AsPathOptionsConfig) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.AllowOwnAs != rhs.AllowOwnAs { + return false + } + if lhs.ReplacePeerAs != rhs.ReplacePeerAs { + return false + } + return true +} + +// struct for container bgp:as-path-options. +// AS_PATH manipulation parameters for the BGP neighbor or +// group. +type AsPathOptions struct { + // original -> bgp:as-path-options-config + // Configuration parameters relating to AS_PATH manipulation + // for the BGP peer or group. + Config AsPathOptionsConfig `mapstructure:"config" json:"config,omitempty"` + // original -> bgp:as-path-options-state + // State information relating to the AS_PATH manipulation + // mechanisms for the BGP peer or group. + State AsPathOptionsState `mapstructure:"state" json:"state,omitempty"` +} + +func (lhs *AsPathOptions) Equal(rhs *AsPathOptions) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.Config.Equal(&(rhs.Config)) { + return false + } + return true +} + +// struct for container bgp:state. +// State information relating to route reflection for the +// BGP neighbor or group. +type RouteReflectorState struct { + // original -> bgp:route-reflector-cluster-id + // route-reflector cluster id to use when local router is + // configured as a route reflector. Commonly set at the group + // level, but allows a different cluster + // id to be set for each neighbor. + RouteReflectorClusterId RrClusterIdType `mapstructure:"route-reflector-cluster-id" json:"route-reflector-cluster-id,omitempty"` + // original -> bgp:route-reflector-client + // bgp:route-reflector-client's original type is boolean. + // Configure the neighbor as a route reflector client. + RouteReflectorClient bool `mapstructure:"route-reflector-client" json:"route-reflector-client,omitempty"` +} + +// struct for container bgp:config. +// Configuraton parameters relating to route reflection +// for the BGP neighbor or group. +type RouteReflectorConfig struct { + // original -> bgp:route-reflector-cluster-id + // route-reflector cluster id to use when local router is + // configured as a route reflector. Commonly set at the group + // level, but allows a different cluster + // id to be set for each neighbor. + RouteReflectorClusterId RrClusterIdType `mapstructure:"route-reflector-cluster-id" json:"route-reflector-cluster-id,omitempty"` + // original -> bgp:route-reflector-client + // bgp:route-reflector-client's original type is boolean. + // Configure the neighbor as a route reflector client. + RouteReflectorClient bool `mapstructure:"route-reflector-client" json:"route-reflector-client,omitempty"` +} + +func (lhs *RouteReflectorConfig) Equal(rhs *RouteReflectorConfig) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.RouteReflectorClusterId != rhs.RouteReflectorClusterId { + return false + } + if lhs.RouteReflectorClient != rhs.RouteReflectorClient { + return false + } + return true +} + +// struct for container bgp:route-reflector. +// Route reflector parameters for the BGP neighbor or group. +type RouteReflector struct { + // original -> bgp:route-reflector-config + // Configuraton parameters relating to route reflection + // for the BGP neighbor or group. + Config RouteReflectorConfig `mapstructure:"config" json:"config,omitempty"` + // original -> bgp:route-reflector-state + // State information relating to route reflection for the + // BGP neighbor or group. + State RouteReflectorState `mapstructure:"state" json:"state,omitempty"` +} + +func (lhs *RouteReflector) Equal(rhs *RouteReflector) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.Config.Equal(&(rhs.Config)) { + return false + } + return true +} + +// struct for container bgp:state. +// State information for eBGP multihop, for the BGP neighbor +// or group. +type EbgpMultihopState struct { + // original -> bgp:enabled + // bgp:enabled's original type is boolean. + // When enabled the referenced group or neighbors are permitted + // to be indirectly connected - including cases where the TTL + // can be decremented between the BGP peers. + Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` + // original -> bgp:multihop-ttl + // Time-to-live value to use when packets are sent to the + // referenced group or neighbors and ebgp-multihop is enabled. + MultihopTtl uint8 `mapstructure:"multihop-ttl" json:"multihop-ttl,omitempty"` +} + +// struct for container bgp:config. +// Configuration parameters relating to eBGP multihop for the +// BGP neighbor or group. +type EbgpMultihopConfig struct { + // original -> bgp:enabled + // bgp:enabled's original type is boolean. + // When enabled the referenced group or neighbors are permitted + // to be indirectly connected - including cases where the TTL + // can be decremented between the BGP peers. + Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` + // original -> bgp:multihop-ttl + // Time-to-live value to use when packets are sent to the + // referenced group or neighbors and ebgp-multihop is enabled. + MultihopTtl uint8 `mapstructure:"multihop-ttl" json:"multihop-ttl,omitempty"` +} + +func (lhs *EbgpMultihopConfig) Equal(rhs *EbgpMultihopConfig) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.Enabled != rhs.Enabled { + return false + } + if lhs.MultihopTtl != rhs.MultihopTtl { + return false + } + return true +} + +// struct for container bgp:ebgp-multihop. +// eBGP multi-hop parameters for the BGP neighbor or group. +type EbgpMultihop struct { + // original -> bgp:ebgp-multihop-config + // Configuration parameters relating to eBGP multihop for the + // BGP neighbor or group. + Config EbgpMultihopConfig `mapstructure:"config" json:"config,omitempty"` + // original -> bgp:ebgp-multihop-state + // State information for eBGP multihop, for the BGP neighbor + // or group. + State EbgpMultihopState `mapstructure:"state" json:"state,omitempty"` +} + +func (lhs *EbgpMultihop) Equal(rhs *EbgpMultihop) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.Config.Equal(&(rhs.Config)) { + return false + } + return true +} + +// struct for container bgp:state. +// State information relating to logging for the BGP neighbor +// or group. +type LoggingOptionsState struct { + // original -> bgp:log-neighbor-state-changes + // bgp:log-neighbor-state-changes's original type is boolean. + // Configure logging of peer state changes. Default is + // to enable logging of peer state changes. + LogNeighborStateChanges bool `mapstructure:"log-neighbor-state-changes" json:"log-neighbor-state-changes,omitempty"` +} + +// struct for container bgp:config. +// Configuration parameters enabling or modifying logging +// for events relating to the BGP neighbor or group. +type LoggingOptionsConfig struct { + // original -> bgp:log-neighbor-state-changes + // bgp:log-neighbor-state-changes's original type is boolean. + // Configure logging of peer state changes. Default is + // to enable logging of peer state changes. + LogNeighborStateChanges bool `mapstructure:"log-neighbor-state-changes" json:"log-neighbor-state-changes,omitempty"` +} + +func (lhs *LoggingOptionsConfig) Equal(rhs *LoggingOptionsConfig) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.LogNeighborStateChanges != rhs.LogNeighborStateChanges { + return false + } + return true +} + +// struct for container bgp:logging-options. +// Logging options for events related to the BGP neighbor or +// group. +type LoggingOptions struct { + // original -> bgp:logging-options-config + // Configuration parameters enabling or modifying logging + // for events relating to the BGP neighbor or group. + Config LoggingOptionsConfig `mapstructure:"config" json:"config,omitempty"` + // original -> bgp:logging-options-state + // State information relating to logging for the BGP neighbor + // or group. + State LoggingOptionsState `mapstructure:"state" json:"state,omitempty"` +} + +func (lhs *LoggingOptions) Equal(rhs *LoggingOptions) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.Config.Equal(&(rhs.Config)) { + return false + } + return true +} + +// struct for container bgp:state. +// State information relating to enhanced error handling +// mechanisms for the BGP neighbor or group. +type ErrorHandlingState struct { + // original -> bgp:treat-as-withdraw + // bgp:treat-as-withdraw's original type is boolean. + // Specify whether erroneous UPDATE messages for which the + // NLRI can be extracted are reated as though the NLRI is + // withdrawn - avoiding session reset. + TreatAsWithdraw bool `mapstructure:"treat-as-withdraw" json:"treat-as-withdraw,omitempty"` + // original -> bgp-op:erroneous-update-messages + // The number of BGP UPDATE messages for which the + // treat-as-withdraw mechanism has been applied based + // on erroneous message contents. + ErroneousUpdateMessages uint32 `mapstructure:"erroneous-update-messages" json:"erroneous-update-messages,omitempty"` +} + +// struct for container bgp:config. +// Configuration parameters enabling or modifying the +// behavior or enhanced error handling mechanisms for the BGP +// neighbor or group. +type ErrorHandlingConfig struct { + // original -> bgp:treat-as-withdraw + // bgp:treat-as-withdraw's original type is boolean. + // Specify whether erroneous UPDATE messages for which the + // NLRI can be extracted are reated as though the NLRI is + // withdrawn - avoiding session reset. + TreatAsWithdraw bool `mapstructure:"treat-as-withdraw" json:"treat-as-withdraw,omitempty"` +} + +func (lhs *ErrorHandlingConfig) Equal(rhs *ErrorHandlingConfig) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.TreatAsWithdraw != rhs.TreatAsWithdraw { + return false + } + return true +} + +// struct for container bgp:error-handling. +// Error handling parameters used for the BGP neighbor or +// group. +type ErrorHandling struct { + // original -> bgp:error-handling-config + // Configuration parameters enabling or modifying the + // behavior or enhanced error handling mechanisms for the BGP + // neighbor or group. + Config ErrorHandlingConfig `mapstructure:"config" json:"config,omitempty"` + // original -> bgp:error-handling-state + // State information relating to enhanced error handling + // mechanisms for the BGP neighbor or group. + State ErrorHandlingState `mapstructure:"state" json:"state,omitempty"` +} + +func (lhs *ErrorHandling) Equal(rhs *ErrorHandling) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.Config.Equal(&(rhs.Config)) { + return false + } + return true +} + +// struct for container bgp:state. +// State information relating to the transport session(s) +// used for the BGP neighbor or group. +type TransportState struct { + // original -> bgp:tcp-mss + // Sets the max segment size for BGP TCP sessions. + TcpMss uint16 `mapstructure:"tcp-mss" json:"tcp-mss,omitempty"` + // original -> bgp:mtu-discovery + // bgp:mtu-discovery's original type is boolean. + // Turns path mtu discovery for BGP TCP sessions on (true) + // or off (false). + MtuDiscovery bool `mapstructure:"mtu-discovery" json:"mtu-discovery,omitempty"` + // original -> bgp:passive-mode + // bgp:passive-mode's original type is boolean. + // Wait for peers to issue requests to open a BGP session, + // rather than initiating sessions from the local router. + PassiveMode bool `mapstructure:"passive-mode" json:"passive-mode,omitempty"` + // original -> bgp:local-address + // bgp:local-address's original type is union. + // Set the local IP (either IPv4 or IPv6) address to use + // for the session when sending BGP update messages. This + // may be expressed as either an IP address or reference + // to the name of an interface. + LocalAddress string `mapstructure:"local-address" json:"local-address,omitempty"` + // original -> bgp-op:local-port + // bgp-op:local-port's original type is inet:port-number. + // Local TCP port being used for the TCP session supporting + // the BGP session. + LocalPort uint16 `mapstructure:"local-port" json:"local-port,omitempty"` + // original -> bgp-op:remote-address + // bgp-op:remote-address's original type is inet:ip-address. + // Remote address to which the BGP session has been + // established. + RemoteAddress string `mapstructure:"remote-address" json:"remote-address,omitempty"` + // original -> bgp-op:remote-port + // bgp-op:remote-port's original type is inet:port-number. + // Remote port being used by the peer for the TCP session + // supporting the BGP session. + RemotePort uint16 `mapstructure:"remote-port" json:"remote-port,omitempty"` +} + +// struct for container bgp:config. +// Configuration parameters relating to the transport +// session(s) used for the BGP neighbor or group. +type TransportConfig struct { + // original -> bgp:tcp-mss + // Sets the max segment size for BGP TCP sessions. + TcpMss uint16 `mapstructure:"tcp-mss" json:"tcp-mss,omitempty"` + // original -> bgp:mtu-discovery + // bgp:mtu-discovery's original type is boolean. + // Turns path mtu discovery for BGP TCP sessions on (true) + // or off (false). + MtuDiscovery bool `mapstructure:"mtu-discovery" json:"mtu-discovery,omitempty"` + // original -> bgp:passive-mode + // bgp:passive-mode's original type is boolean. + // Wait for peers to issue requests to open a BGP session, + // rather than initiating sessions from the local router. + PassiveMode bool `mapstructure:"passive-mode" json:"passive-mode,omitempty"` + // original -> bgp:local-address + // bgp:local-address's original type is union. + // Set the local IP (either IPv4 or IPv6) address to use + // for the session when sending BGP update messages. This + // may be expressed as either an IP address or reference + // to the name of an interface. + LocalAddress string `mapstructure:"local-address" json:"local-address,omitempty"` + // original -> gobgp:remote-port + // gobgp:remote-port's original type is inet:port-number. + RemotePort uint16 `mapstructure:"remote-port" json:"remote-port,omitempty"` + // original -> gobgp:ttl + // TTL value for BGP packets. + Ttl uint8 `mapstructure:"ttl" json:"ttl,omitempty"` +} + +func (lhs *TransportConfig) Equal(rhs *TransportConfig) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.TcpMss != rhs.TcpMss { + return false + } + if lhs.MtuDiscovery != rhs.MtuDiscovery { + return false + } + if lhs.PassiveMode != rhs.PassiveMode { + return false + } + if lhs.LocalAddress != rhs.LocalAddress { + return false + } + if lhs.RemotePort != rhs.RemotePort { + return false + } + if lhs.Ttl != rhs.Ttl { + return false + } + return true +} + +// struct for container bgp:transport. +// Transport session parameters for the BGP neighbor or group. +type Transport struct { + // original -> bgp:transport-config + // Configuration parameters relating to the transport + // session(s) used for the BGP neighbor or group. + Config TransportConfig `mapstructure:"config" json:"config,omitempty"` + // original -> bgp:transport-state + // State information relating to the transport session(s) + // used for the BGP neighbor or group. + State TransportState `mapstructure:"state" json:"state,omitempty"` +} + +func (lhs *Transport) Equal(rhs *Transport) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.Config.Equal(&(rhs.Config)) { + return false + } + return true +} + +// struct for container bgp:state. +// State information relating to the timers used for the BGP +// neighbor or group. +type TimersState struct { + // original -> bgp:connect-retry + // bgp:connect-retry's original type is decimal64. + // Time interval in seconds between attempts to establish a + // session with the peer. + ConnectRetry float64 `mapstructure:"connect-retry" json:"connect-retry,omitempty"` + // original -> bgp:hold-time + // bgp:hold-time's original type is decimal64. + // Time interval in seconds that a BGP session will be + // considered active in the absence of keepalive or other + // messages from the peer. The hold-time is typically + // set to 3x the keepalive-interval. + HoldTime float64 `mapstructure:"hold-time" json:"hold-time,omitempty"` + // original -> bgp:keepalive-interval + // bgp:keepalive-interval's original type is decimal64. + // Time interval in seconds between transmission of keepalive + // messages to the neighbor. Typically set to 1/3 the + // hold-time. + KeepaliveInterval float64 `mapstructure:"keepalive-interval" json:"keepalive-interval,omitempty"` + // original -> bgp:minimum-advertisement-interval + // bgp:minimum-advertisement-interval's original type is decimal64. + // Minimum time which must elapse between subsequent UPDATE + // messages relating to a common set of NLRI being transmitted + // to a peer. This timer is referred to as + // MinRouteAdvertisementIntervalTimer by RFC 4721 and serves to + // reduce the number of UPDATE messages transmitted when a + // particular set of NLRI exhibit instability. + MinimumAdvertisementInterval float64 `mapstructure:"minimum-advertisement-interval" json:"minimum-advertisement-interval,omitempty"` + // original -> bgp-op:uptime + // bgp-op:uptime's original type is yang:timeticks. + // This timer determines the amount of time since the + // BGP last transitioned in or out of the Established + // state. + Uptime int64 `mapstructure:"uptime" json:"uptime,omitempty"` + // original -> bgp-op:negotiated-hold-time + // bgp-op:negotiated-hold-time's original type is decimal64. + // The negotiated hold-time for the BGP session. + NegotiatedHoldTime float64 `mapstructure:"negotiated-hold-time" json:"negotiated-hold-time,omitempty"` + // original -> gobgp:idle-hold-time-after-reset + // gobgp:idle-hold-time-after-reset's original type is decimal64. + // Time interval in seconds that a BGP session will be + // in idle state after neighbor reset operation. + IdleHoldTimeAfterReset float64 `mapstructure:"idle-hold-time-after-reset" json:"idle-hold-time-after-reset,omitempty"` + // original -> gobgp:downtime + // gobgp:downtime's original type is yang:timeticks. + // This timer determines the amount of time since the + // BGP last transitioned out of the Established state. + Downtime int64 `mapstructure:"downtime" json:"downtime,omitempty"` + // original -> gobgp:update-recv-time + // The number of seconds elasped since January 1, 1970 UTC + // last time the BGP session received an UPDATE message. + UpdateRecvTime int64 `mapstructure:"update-recv-time" json:"update-recv-time,omitempty"` +} + +// struct for container bgp:config. +// Configuration parameters relating to timers used for the +// BGP neighbor or group. +type TimersConfig struct { + // original -> bgp:connect-retry + // bgp:connect-retry's original type is decimal64. + // Time interval in seconds between attempts to establish a + // session with the peer. + ConnectRetry float64 `mapstructure:"connect-retry" json:"connect-retry,omitempty"` + // original -> bgp:hold-time + // bgp:hold-time's original type is decimal64. + // Time interval in seconds that a BGP session will be + // considered active in the absence of keepalive or other + // messages from the peer. The hold-time is typically + // set to 3x the keepalive-interval. + HoldTime float64 `mapstructure:"hold-time" json:"hold-time,omitempty"` + // original -> bgp:keepalive-interval + // bgp:keepalive-interval's original type is decimal64. + // Time interval in seconds between transmission of keepalive + // messages to the neighbor. Typically set to 1/3 the + // hold-time. + KeepaliveInterval float64 `mapstructure:"keepalive-interval" json:"keepalive-interval,omitempty"` + // original -> bgp:minimum-advertisement-interval + // bgp:minimum-advertisement-interval's original type is decimal64. + // Minimum time which must elapse between subsequent UPDATE + // messages relating to a common set of NLRI being transmitted + // to a peer. This timer is referred to as + // MinRouteAdvertisementIntervalTimer by RFC 4721 and serves to + // reduce the number of UPDATE messages transmitted when a + // particular set of NLRI exhibit instability. + MinimumAdvertisementInterval float64 `mapstructure:"minimum-advertisement-interval" json:"minimum-advertisement-interval,omitempty"` + // original -> gobgp:idle-hold-time-after-reset + // gobgp:idle-hold-time-after-reset's original type is decimal64. + // Time interval in seconds that a BGP session will be + // in idle state after neighbor reset operation. + IdleHoldTimeAfterReset float64 `mapstructure:"idle-hold-time-after-reset" json:"idle-hold-time-after-reset,omitempty"` +} + +func (lhs *TimersConfig) Equal(rhs *TimersConfig) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.ConnectRetry != rhs.ConnectRetry { + return false + } + if lhs.HoldTime != rhs.HoldTime { + return false + } + if lhs.KeepaliveInterval != rhs.KeepaliveInterval { + return false + } + if lhs.MinimumAdvertisementInterval != rhs.MinimumAdvertisementInterval { + return false + } + if lhs.IdleHoldTimeAfterReset != rhs.IdleHoldTimeAfterReset { + return false + } + return true +} + +// struct for container bgp:timers. +// Timers related to a BGP neighbor or group. +type Timers struct { + // original -> bgp:timers-config + // Configuration parameters relating to timers used for the + // BGP neighbor or group. + Config TimersConfig `mapstructure:"config" json:"config,omitempty"` + // original -> bgp:timers-state + // State information relating to the timers used for the BGP + // neighbor or group. + State TimersState `mapstructure:"state" json:"state,omitempty"` +} + +func (lhs *Timers) Equal(rhs *Timers) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.Config.Equal(&(rhs.Config)) { + return false + } + return true +} + +// struct for container gobgp:adj-table. +type AdjTable struct { + // original -> gobgp:ADVERTISED + Advertised uint32 `mapstructure:"advertised" json:"advertised,omitempty"` + // original -> gobgp:FILTERED + Filtered uint32 `mapstructure:"filtered" json:"filtered,omitempty"` + // original -> gobgp:RECEIVED + Received uint32 `mapstructure:"received" json:"received,omitempty"` + // original -> gobgp:ACCEPTED + Accepted uint32 `mapstructure:"accepted" json:"accepted,omitempty"` +} + +func (lhs *AdjTable) Equal(rhs *AdjTable) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.Advertised != rhs.Advertised { + return false + } + if lhs.Filtered != rhs.Filtered { + return false + } + if lhs.Received != rhs.Received { + return false + } + if lhs.Accepted != rhs.Accepted { + return false + } + return true +} + +// struct for container bgp:queues. +// Counters related to queued messages associated with the +// BGP neighbor. +type Queues struct { + // original -> bgp-op:input + // The number of messages received from the peer currently + // queued. + Input uint32 `mapstructure:"input" json:"input,omitempty"` + // original -> bgp-op:output + // The number of messages queued to be sent to the peer. + Output uint32 `mapstructure:"output" json:"output,omitempty"` +} + +func (lhs *Queues) Equal(rhs *Queues) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.Input != rhs.Input { + return false + } + if lhs.Output != rhs.Output { + return false + } + return true +} + +// struct for container bgp:received. +// Counters for BGP messages received from the neighbor. +type Received struct { + // original -> bgp-op:UPDATE + // Number of BGP UPDATE messages announcing, withdrawing + // or modifying paths exchanged. + Update uint64 `mapstructure:"update" json:"update,omitempty"` + // original -> bgp-op:NOTIFICATION + // Number of BGP NOTIFICATION messages indicating an + // error condition has occurred exchanged. + Notification uint64 `mapstructure:"notification" json:"notification,omitempty"` + // original -> gobgp:OPEN + // Number of BGP open messages announcing, withdrawing + // or modifying paths exchanged. + Open uint64 `mapstructure:"open" json:"open,omitempty"` + // original -> gobgp:REFRESH + // Number of BGP Route-Refresh messages indicating an + // error condition has occurred exchanged. + Refresh uint64 `mapstructure:"refresh" json:"refresh,omitempty"` + // original -> gobgp:KEEPALIVE + // Number of BGP Keepalive messages indicating an + // error condition has occurred exchanged. + Keepalive uint64 `mapstructure:"keepalive" json:"keepalive,omitempty"` + // original -> gobgp:DYNAMIC-CAP + // Number of BGP dynamic-cap messages indicating an + // error condition has occurred exchanged. + DynamicCap uint64 `mapstructure:"dynamic-cap" json:"dynamic-cap,omitempty"` + // original -> gobgp:WITHDRAW-UPDATE + // Number of updates subjected to treat-as-withdraw treatment. + WithdrawUpdate uint32 `mapstructure:"withdraw-update" json:"withdraw-update,omitempty"` + // original -> gobgp:WITHDRAW-PREFIX + // Number of prefixes subjected to treat-as-withdraw treatment. + WithdrawPrefix uint32 `mapstructure:"withdraw-prefix" json:"withdraw-prefix,omitempty"` + // original -> gobgp:DISCARDED + // Number of discarded messages indicating an + // error condition has occurred exchanged. + Discarded uint64 `mapstructure:"discarded" json:"discarded,omitempty"` + // original -> gobgp:TOTAL + // Number of total messages indicating an + // error condition has occurred exchanged. + Total uint64 `mapstructure:"total" json:"total,omitempty"` +} + +func (lhs *Received) Equal(rhs *Received) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.Update != rhs.Update { + return false + } + if lhs.Notification != rhs.Notification { + return false + } + if lhs.Open != rhs.Open { + return false + } + if lhs.Refresh != rhs.Refresh { + return false + } + if lhs.Keepalive != rhs.Keepalive { + return false + } + if lhs.DynamicCap != rhs.DynamicCap { + return false + } + if lhs.WithdrawUpdate != rhs.WithdrawUpdate { + return false + } + if lhs.WithdrawPrefix != rhs.WithdrawPrefix { + return false + } + if lhs.Discarded != rhs.Discarded { + return false + } + if lhs.Total != rhs.Total { + return false + } + return true +} + +// struct for container bgp:sent. +// Counters relating to BGP messages sent to the neighbor. +type Sent struct { + // original -> bgp-op:UPDATE + // Number of BGP UPDATE messages announcing, withdrawing + // or modifying paths exchanged. + Update uint64 `mapstructure:"update" json:"update,omitempty"` + // original -> bgp-op:NOTIFICATION + // Number of BGP NOTIFICATION messages indicating an + // error condition has occurred exchanged. + Notification uint64 `mapstructure:"notification" json:"notification,omitempty"` + // original -> gobgp:OPEN + // Number of BGP open messages announcing, withdrawing + // or modifying paths exchanged. + Open uint64 `mapstructure:"open" json:"open,omitempty"` + // original -> gobgp:REFRESH + // Number of BGP Route-Refresh messages indicating an + // error condition has occurred exchanged. + Refresh uint64 `mapstructure:"refresh" json:"refresh,omitempty"` + // original -> gobgp:KEEPALIVE + // Number of BGP Keepalive messages indicating an + // error condition has occurred exchanged. + Keepalive uint64 `mapstructure:"keepalive" json:"keepalive,omitempty"` + // original -> gobgp:DYNAMIC-CAP + // Number of BGP dynamic-cap messages indicating an + // error condition has occurred exchanged. + DynamicCap uint64 `mapstructure:"dynamic-cap" json:"dynamic-cap,omitempty"` + // original -> gobgp:WITHDRAW-UPDATE + // Number of updates subjected to treat-as-withdraw treatment. + WithdrawUpdate uint32 `mapstructure:"withdraw-update" json:"withdraw-update,omitempty"` + // original -> gobgp:WITHDRAW-PREFIX + // Number of prefixes subjected to treat-as-withdraw treatment. + WithdrawPrefix uint32 `mapstructure:"withdraw-prefix" json:"withdraw-prefix,omitempty"` + // original -> gobgp:DISCARDED + // Number of discarded messages indicating an + // error condition has occurred exchanged. + Discarded uint64 `mapstructure:"discarded" json:"discarded,omitempty"` + // original -> gobgp:TOTAL + // Number of total messages indicating an + // error condition has occurred exchanged. + Total uint64 `mapstructure:"total" json:"total,omitempty"` +} + +func (lhs *Sent) Equal(rhs *Sent) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.Update != rhs.Update { + return false + } + if lhs.Notification != rhs.Notification { + return false + } + if lhs.Open != rhs.Open { + return false + } + if lhs.Refresh != rhs.Refresh { + return false + } + if lhs.Keepalive != rhs.Keepalive { + return false + } + if lhs.DynamicCap != rhs.DynamicCap { + return false + } + if lhs.WithdrawUpdate != rhs.WithdrawUpdate { + return false + } + if lhs.WithdrawPrefix != rhs.WithdrawPrefix { + return false + } + if lhs.Discarded != rhs.Discarded { + return false + } + if lhs.Total != rhs.Total { + return false + } + return true +} + +// struct for container bgp:messages. +// Counters for BGP messages sent and received from the +// neighbor. +type Messages struct { + // original -> bgp:sent + // Counters relating to BGP messages sent to the neighbor. + Sent Sent `mapstructure:"sent" json:"sent,omitempty"` + // original -> bgp:received + // Counters for BGP messages received from the neighbor. + Received Received `mapstructure:"received" json:"received,omitempty"` +} + +func (lhs *Messages) Equal(rhs *Messages) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.Sent.Equal(&(rhs.Sent)) { + return false + } + if !lhs.Received.Equal(&(rhs.Received)) { + return false + } + return true +} + +// struct for container bgp:state. +// State information relating to the BGP neighbor or group. +type NeighborState struct { + // original -> bgp:peer-as + // bgp:peer-as's original type is inet:as-number. + // AS number of the peer. + PeerAs uint32 `mapstructure:"peer-as" json:"peer-as,omitempty"` + // original -> bgp:local-as + // bgp:local-as's original type is inet:as-number. + // The local autonomous system number that is to be used + // when establishing sessions with the remote peer or peer + // group, if this differs from the global BGP router + // autonomous system number. + LocalAs uint32 `mapstructure:"local-as" json:"local-as,omitempty"` + // original -> bgp:peer-type + // Explicitly designate the peer or peer group as internal + // (iBGP) or external (eBGP). + PeerType PeerType `mapstructure:"peer-type" json:"peer-type,omitempty"` + // original -> bgp:auth-password + // Configures an MD5 authentication password for use with + // neighboring devices. + AuthPassword string `mapstructure:"auth-password" json:"auth-password,omitempty"` + // original -> bgp:remove-private-as + // Remove private AS numbers from updates sent to peers. + RemovePrivateAs RemovePrivateAsOption `mapstructure:"remove-private-as" json:"remove-private-as,omitempty"` + // original -> bgp:route-flap-damping + // bgp:route-flap-damping's original type is boolean. + // Enable route flap damping. + RouteFlapDamping bool `mapstructure:"route-flap-damping" json:"route-flap-damping,omitempty"` + // original -> bgp:send-community + // Specify which types of community should be sent to the + // neighbor or group. The default is to not send the + // community attribute. + SendCommunity CommunityType `mapstructure:"send-community" json:"send-community,omitempty"` + // original -> bgp:description + // An optional textual description (intended primarily for use + // with a peer or group. + Description string `mapstructure:"description" json:"description,omitempty"` + // original -> bgp:peer-group + // The peer-group with which this neighbor is associated. + PeerGroup string `mapstructure:"peer-group" json:"peer-group,omitempty"` + // original -> bgp:neighbor-address + // bgp:neighbor-address's original type is inet:ip-address. + // Address of the BGP peer, either in IPv4 or IPv6. + NeighborAddress string `mapstructure:"neighbor-address" json:"neighbor-address,omitempty"` + // original -> bgp-op:session-state + // Operational state of the BGP peer. + SessionState SessionState `mapstructure:"session-state" json:"session-state,omitempty"` + // original -> bgp-op:supported-capabilities + // BGP capabilities negotiated as supported with the peer. + SupportedCapabilitiesList []BgpCapability `mapstructure:"supported-capabilities-list" json:"supported-capabilities-list,omitempty"` + // original -> bgp:messages + // Counters for BGP messages sent and received from the + // neighbor. + Messages Messages `mapstructure:"messages" json:"messages,omitempty"` + // original -> bgp:queues + // Counters related to queued messages associated with the + // BGP neighbor. + Queues Queues `mapstructure:"queues" json:"queues,omitempty"` + // original -> gobgp:adj-table + AdjTable AdjTable `mapstructure:"adj-table" json:"adj-table,omitempty"` + // original -> gobgp:remote-capability + // original type is list of bgp-capability + RemoteCapabilityList []bgp.ParameterCapabilityInterface `mapstructure:"remote-capability-list" json:"remote-capability-list,omitempty"` + // original -> gobgp:local-capability + // original type is list of bgp-capability + LocalCapabilityList []bgp.ParameterCapabilityInterface `mapstructure:"local-capability-list" json:"local-capability-list,omitempty"` + // original -> gobgp:received-open-message + // gobgp:received-open-message's original type is bgp-open-message. + ReceivedOpenMessage *bgp.BGPMessage `mapstructure:"received-open-message" json:"received-open-message,omitempty"` + // original -> gobgp:admin-down + // gobgp:admin-down's original type is boolean. + // The state of administrative operation. If the state is true, it indicates the neighbor is disabled by the administrator. + AdminDown bool `mapstructure:"admin-down" json:"admin-down,omitempty"` + // original -> gobgp:admin-state + AdminState AdminState `mapstructure:"admin-state" json:"admin-state,omitempty"` + // original -> gobgp:established-count + // The number of how many the peer became established state. + EstablishedCount uint32 `mapstructure:"established-count" json:"established-count,omitempty"` + // original -> gobgp:flops + // The number of flip-flops. + Flops uint32 `mapstructure:"flops" json:"flops,omitempty"` + // original -> gobgp:neighbor-interface + NeighborInterface string `mapstructure:"neighbor-interface" json:"neighbor-interface,omitempty"` + // original -> gobgp:vrf + Vrf string `mapstructure:"vrf" json:"vrf,omitempty"` + // original -> gobgp:remote-router-id + RemoteRouterId string `mapstructure:"remote-router-id" json:"remote-router-id,omitempty"` +} + +// struct for container bgp:config. +// Configuration parameters relating to the BGP neighbor or +// group. +type NeighborConfig struct { + // original -> bgp:peer-as + // bgp:peer-as's original type is inet:as-number. + // AS number of the peer. + PeerAs uint32 `mapstructure:"peer-as" json:"peer-as,omitempty"` + // original -> bgp:local-as + // bgp:local-as's original type is inet:as-number. + // The local autonomous system number that is to be used + // when establishing sessions with the remote peer or peer + // group, if this differs from the global BGP router + // autonomous system number. + LocalAs uint32 `mapstructure:"local-as" json:"local-as,omitempty"` + // original -> bgp:peer-type + // Explicitly designate the peer or peer group as internal + // (iBGP) or external (eBGP). + PeerType PeerType `mapstructure:"peer-type" json:"peer-type,omitempty"` + // original -> bgp:auth-password + // Configures an MD5 authentication password for use with + // neighboring devices. + AuthPassword string `mapstructure:"auth-password" json:"auth-password,omitempty"` + // original -> bgp:remove-private-as + // Remove private AS numbers from updates sent to peers. + RemovePrivateAs RemovePrivateAsOption `mapstructure:"remove-private-as" json:"remove-private-as,omitempty"` + // original -> bgp:route-flap-damping + // bgp:route-flap-damping's original type is boolean. + // Enable route flap damping. + RouteFlapDamping bool `mapstructure:"route-flap-damping" json:"route-flap-damping,omitempty"` + // original -> bgp:send-community + // Specify which types of community should be sent to the + // neighbor or group. The default is to not send the + // community attribute. + SendCommunity CommunityType `mapstructure:"send-community" json:"send-community,omitempty"` + // original -> bgp:description + // An optional textual description (intended primarily for use + // with a peer or group. + Description string `mapstructure:"description" json:"description,omitempty"` + // original -> bgp:peer-group + // The peer-group with which this neighbor is associated. + PeerGroup string `mapstructure:"peer-group" json:"peer-group,omitempty"` + // original -> bgp:neighbor-address + // bgp:neighbor-address's original type is inet:ip-address. + // Address of the BGP peer, either in IPv4 or IPv6. + NeighborAddress string `mapstructure:"neighbor-address" json:"neighbor-address,omitempty"` + // original -> gobgp:admin-down + // gobgp:admin-down's original type is boolean. + // The config of administrative operation. If state, indicates the neighbor is disabled by the administrator. + AdminDown bool `mapstructure:"admin-down" json:"admin-down,omitempty"` + // original -> gobgp:neighbor-interface + NeighborInterface string `mapstructure:"neighbor-interface" json:"neighbor-interface,omitempty"` + // original -> gobgp:vrf + Vrf string `mapstructure:"vrf" json:"vrf,omitempty"` +} + +func (lhs *NeighborConfig) Equal(rhs *NeighborConfig) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.PeerAs != rhs.PeerAs { + return false + } + if lhs.LocalAs != rhs.LocalAs { + return false + } + if lhs.PeerType != rhs.PeerType { + return false + } + if lhs.AuthPassword != rhs.AuthPassword { + return false + } + if lhs.RemovePrivateAs != rhs.RemovePrivateAs { + return false + } + if lhs.RouteFlapDamping != rhs.RouteFlapDamping { + return false + } + if lhs.SendCommunity != rhs.SendCommunity { + return false + } + if lhs.Description != rhs.Description { + return false + } + if lhs.PeerGroup != rhs.PeerGroup { + return false + } + if lhs.NeighborAddress != rhs.NeighborAddress { + return false + } + if lhs.AdminDown != rhs.AdminDown { + return false + } + if lhs.NeighborInterface != rhs.NeighborInterface { + return false + } + if lhs.Vrf != rhs.Vrf { + return false + } + return true +} + +// struct for container bgp:neighbor. +// List of BGP neighbors configured on the local system, +// uniquely identified by peer IPv[46] address. +type Neighbor struct { + // original -> bgp:neighbor-address + // original -> bgp:neighbor-config + // Configuration parameters relating to the BGP neighbor or + // group. + Config NeighborConfig `mapstructure:"config" json:"config,omitempty"` + // original -> bgp:neighbor-state + // State information relating to the BGP neighbor or group. + State NeighborState `mapstructure:"state" json:"state,omitempty"` + // original -> bgp:timers + // Timers related to a BGP neighbor or group. + Timers Timers `mapstructure:"timers" json:"timers,omitempty"` + // original -> bgp:transport + // Transport session parameters for the BGP neighbor or group. + Transport Transport `mapstructure:"transport" json:"transport,omitempty"` + // original -> bgp:error-handling + // Error handling parameters used for the BGP neighbor or + // group. + ErrorHandling ErrorHandling `mapstructure:"error-handling" json:"error-handling,omitempty"` + // original -> bgp:logging-options + // Logging options for events related to the BGP neighbor or + // group. + LoggingOptions LoggingOptions `mapstructure:"logging-options" json:"logging-options,omitempty"` + // original -> bgp:ebgp-multihop + // eBGP multi-hop parameters for the BGP neighbor or group. + EbgpMultihop EbgpMultihop `mapstructure:"ebgp-multihop" json:"ebgp-multihop,omitempty"` + // original -> bgp:route-reflector + // Route reflector parameters for the BGP neighbor or group. + RouteReflector RouteReflector `mapstructure:"route-reflector" json:"route-reflector,omitempty"` + // original -> bgp:as-path-options + // AS_PATH manipulation parameters for the BGP neighbor or + // group. + AsPathOptions AsPathOptions `mapstructure:"as-path-options" json:"as-path-options,omitempty"` + // original -> bgp:add-paths + // Parameters relating to the advertisement and receipt of + // multiple paths for a single NLRI (add-paths). + AddPaths AddPaths `mapstructure:"add-paths" json:"add-paths,omitempty"` + // original -> bgp:afi-safis + // Per-address-family configuration parameters associated with + // the neighbor or group. + AfiSafis []AfiSafi `mapstructure:"afi-safis" json:"afi-safis,omitempty"` + // original -> bgp:graceful-restart + // Parameters relating the graceful restart mechanism for BGP. + GracefulRestart GracefulRestart `mapstructure:"graceful-restart" json:"graceful-restart,omitempty"` + // original -> rpol:apply-policy + // Anchor point for routing policies in the model. + // Import and export policies are with respect to the local + // routing table, i.e., export (send) and import (receive), + // depending on the context. + ApplyPolicy ApplyPolicy `mapstructure:"apply-policy" json:"apply-policy,omitempty"` + // original -> bgp-mp:use-multiple-paths + // Parameters related to the use of multiple-paths for the same + // NLRI when they are received only from this neighbor. + UseMultiplePaths UseMultiplePaths `mapstructure:"use-multiple-paths" json:"use-multiple-paths,omitempty"` + // original -> gobgp:route-server + // Configure the local router as a route server. + RouteServer RouteServer `mapstructure:"route-server" json:"route-server,omitempty"` + // original -> gobgp:ttl-security + // Configure TTL Security feature. + TtlSecurity TtlSecurity `mapstructure:"ttl-security" json:"ttl-security,omitempty"` +} + +func (lhs *Neighbor) Equal(rhs *Neighbor) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.Config.Equal(&(rhs.Config)) { + return false + } + if !lhs.Timers.Equal(&(rhs.Timers)) { + return false + } + if !lhs.Transport.Equal(&(rhs.Transport)) { + return false + } + if !lhs.ErrorHandling.Equal(&(rhs.ErrorHandling)) { + return false + } + if !lhs.LoggingOptions.Equal(&(rhs.LoggingOptions)) { + return false + } + if !lhs.EbgpMultihop.Equal(&(rhs.EbgpMultihop)) { + return false + } + if !lhs.RouteReflector.Equal(&(rhs.RouteReflector)) { + return false + } + if !lhs.AsPathOptions.Equal(&(rhs.AsPathOptions)) { + return false + } + if !lhs.AddPaths.Equal(&(rhs.AddPaths)) { + return false + } + if len(lhs.AfiSafis) != len(rhs.AfiSafis) { + return false + } + { + lmap := make(map[string]*AfiSafi) + for i, l := range lhs.AfiSafis { + lmap[mapkey(i, string(l.Config.AfiSafiName))] = &lhs.AfiSafis[i] + } + for i, r := range rhs.AfiSafis { + if l, y := lmap[mapkey(i, string(r.Config.AfiSafiName))]; !y { + return false + } else if !r.Equal(l) { + return false + } + } + } + if !lhs.GracefulRestart.Equal(&(rhs.GracefulRestart)) { + return false + } + if !lhs.ApplyPolicy.Equal(&(rhs.ApplyPolicy)) { + return false + } + if !lhs.UseMultiplePaths.Equal(&(rhs.UseMultiplePaths)) { + return false + } + if !lhs.RouteServer.Equal(&(rhs.RouteServer)) { + return false + } + if !lhs.TtlSecurity.Equal(&(rhs.TtlSecurity)) { + return false + } + return true +} + +// struct for container gobgp:state. +type LongLivedGracefulRestartState struct { + // original -> gobgp:enabled + // gobgp:enabled's original type is boolean. + Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` + // original -> gobgp:received + // gobgp:received's original type is boolean. + Received bool `mapstructure:"received" json:"received,omitempty"` + // original -> gobgp:advertised + // gobgp:advertised's original type is boolean. + Advertised bool `mapstructure:"advertised" json:"advertised,omitempty"` + // original -> gobgp:peer-restart-time + PeerRestartTime uint32 `mapstructure:"peer-restart-time" json:"peer-restart-time,omitempty"` + // original -> gobgp:peer-restart-timer-expired + // gobgp:peer-restart-timer-expired's original type is boolean. + PeerRestartTimerExpired bool `mapstructure:"peer-restart-timer-expired" json:"peer-restart-timer-expired,omitempty"` +} + +// struct for container gobgp:config. +type LongLivedGracefulRestartConfig struct { + // original -> gobgp:enabled + // gobgp:enabled's original type is boolean. + Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` + // original -> gobgp:restart-time + RestartTime uint32 `mapstructure:"restart-time" json:"restart-time,omitempty"` +} + +func (lhs *LongLivedGracefulRestartConfig) Equal(rhs *LongLivedGracefulRestartConfig) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.Enabled != rhs.Enabled { + return false + } + if lhs.RestartTime != rhs.RestartTime { + return false + } + return true +} + +// struct for container gobgp:long-lived-graceful-restart. +type LongLivedGracefulRestart struct { + // original -> gobgp:long-lived-graceful-restart-config + Config LongLivedGracefulRestartConfig `mapstructure:"config" json:"config,omitempty"` + // original -> gobgp:long-lived-graceful-restart-state + State LongLivedGracefulRestartState `mapstructure:"state" json:"state,omitempty"` +} + +func (lhs *LongLivedGracefulRestart) Equal(rhs *LongLivedGracefulRestart) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.Config.Equal(&(rhs.Config)) { + return false + } + return true +} + +// struct for container gobgp:state. +type RouteTargetMembershipState struct { + // original -> gobgp:deferral-time + DeferralTime uint16 `mapstructure:"deferral-time" json:"deferral-time,omitempty"` +} + +// struct for container gobgp:config. +type RouteTargetMembershipConfig struct { + // original -> gobgp:deferral-time + DeferralTime uint16 `mapstructure:"deferral-time" json:"deferral-time,omitempty"` +} + +func (lhs *RouteTargetMembershipConfig) Equal(rhs *RouteTargetMembershipConfig) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.DeferralTime != rhs.DeferralTime { + return false + } + return true +} + +// struct for container gobgp:route-target-membership. +type RouteTargetMembership struct { + // original -> gobgp:route-target-membership-config + Config RouteTargetMembershipConfig `mapstructure:"config" json:"config,omitempty"` + // original -> gobgp:route-target-membership-state + State RouteTargetMembershipState `mapstructure:"state" json:"state,omitempty"` +} + +func (lhs *RouteTargetMembership) Equal(rhs *RouteTargetMembership) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.Config.Equal(&(rhs.Config)) { + return false + } + return true +} + +// struct for container bgp-mp:l2vpn-evpn. +// BGP EVPN configuration options. +type L2vpnEvpn struct { + // original -> bgp-mp:prefix-limit + // Configure the maximum number of prefixes that will be + // accepted from a peer. + PrefixLimit PrefixLimit `mapstructure:"prefix-limit" json:"prefix-limit,omitempty"` +} + +func (lhs *L2vpnEvpn) Equal(rhs *L2vpnEvpn) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.PrefixLimit.Equal(&(rhs.PrefixLimit)) { + return false + } + return true +} + +// struct for container bgp-mp:l2vpn-vpls. +// BGP-signalled VPLS configuration options. +type L2vpnVpls struct { + // original -> bgp-mp:prefix-limit + // Configure the maximum number of prefixes that will be + // accepted from a peer. + PrefixLimit PrefixLimit `mapstructure:"prefix-limit" json:"prefix-limit,omitempty"` +} + +func (lhs *L2vpnVpls) Equal(rhs *L2vpnVpls) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.PrefixLimit.Equal(&(rhs.PrefixLimit)) { + return false + } + return true +} + +// struct for container bgp-mp:l3vpn-ipv6-multicast. +// Multicast IPv6 L3VPN configuration options. +type L3vpnIpv6Multicast struct { + // original -> bgp-mp:prefix-limit + // Configure the maximum number of prefixes that will be + // accepted from a peer. + PrefixLimit PrefixLimit `mapstructure:"prefix-limit" json:"prefix-limit,omitempty"` +} + +func (lhs *L3vpnIpv6Multicast) Equal(rhs *L3vpnIpv6Multicast) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.PrefixLimit.Equal(&(rhs.PrefixLimit)) { + return false + } + return true +} + +// struct for container bgp-mp:l3vpn-ipv4-multicast. +// Multicast IPv4 L3VPN configuration options. +type L3vpnIpv4Multicast struct { + // original -> bgp-mp:prefix-limit + // Configure the maximum number of prefixes that will be + // accepted from a peer. + PrefixLimit PrefixLimit `mapstructure:"prefix-limit" json:"prefix-limit,omitempty"` +} + +func (lhs *L3vpnIpv4Multicast) Equal(rhs *L3vpnIpv4Multicast) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.PrefixLimit.Equal(&(rhs.PrefixLimit)) { + return false + } + return true +} + +// struct for container bgp-mp:l3vpn-ipv6-unicast. +// Unicast IPv6 L3VPN configuration options. +type L3vpnIpv6Unicast struct { + // original -> bgp-mp:prefix-limit + // Configure the maximum number of prefixes that will be + // accepted from a peer. + PrefixLimit PrefixLimit `mapstructure:"prefix-limit" json:"prefix-limit,omitempty"` +} + +func (lhs *L3vpnIpv6Unicast) Equal(rhs *L3vpnIpv6Unicast) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.PrefixLimit.Equal(&(rhs.PrefixLimit)) { + return false + } + return true +} + +// struct for container bgp-mp:l3vpn-ipv4-unicast. +// Unicast IPv4 L3VPN configuration options. +type L3vpnIpv4Unicast struct { + // original -> bgp-mp:prefix-limit + // Configure the maximum number of prefixes that will be + // accepted from a peer. + PrefixLimit PrefixLimit `mapstructure:"prefix-limit" json:"prefix-limit,omitempty"` +} + +func (lhs *L3vpnIpv4Unicast) Equal(rhs *L3vpnIpv4Unicast) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.PrefixLimit.Equal(&(rhs.PrefixLimit)) { + return false + } + return true +} + +// struct for container bgp-mp:ipv6-labelled-unicast. +// IPv6 Labelled Unicast configuration options. +type Ipv6LabelledUnicast struct { + // original -> bgp-mp:prefix-limit + // Configure the maximum number of prefixes that will be + // accepted from a peer. + PrefixLimit PrefixLimit `mapstructure:"prefix-limit" json:"prefix-limit,omitempty"` +} + +func (lhs *Ipv6LabelledUnicast) Equal(rhs *Ipv6LabelledUnicast) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.PrefixLimit.Equal(&(rhs.PrefixLimit)) { + return false + } + return true +} + +// struct for container bgp-mp:ipv4-labelled-unicast. +// IPv4 Labelled Unicast configuration options. +type Ipv4LabelledUnicast struct { + // original -> bgp-mp:prefix-limit + // Configure the maximum number of prefixes that will be + // accepted from a peer. + PrefixLimit PrefixLimit `mapstructure:"prefix-limit" json:"prefix-limit,omitempty"` +} + +func (lhs *Ipv4LabelledUnicast) Equal(rhs *Ipv4LabelledUnicast) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.PrefixLimit.Equal(&(rhs.PrefixLimit)) { + return false + } + return true +} + +// struct for container bgp-mp:state. +// State information for common IPv4 and IPv6 unicast +// parameters. +type Ipv6UnicastState struct { + // original -> bgp-mp:send-default-route + // bgp-mp:send-default-route's original type is boolean. + // If set to true, send the default-route to the neighbour(s). + SendDefaultRoute bool `mapstructure:"send-default-route" json:"send-default-route,omitempty"` +} + +// struct for container bgp-mp:config. +// Configuration parameters for common IPv4 and IPv6 unicast +// AFI-SAFI options. +type Ipv6UnicastConfig struct { + // original -> bgp-mp:send-default-route + // bgp-mp:send-default-route's original type is boolean. + // If set to true, send the default-route to the neighbour(s). + SendDefaultRoute bool `mapstructure:"send-default-route" json:"send-default-route,omitempty"` +} + +func (lhs *Ipv6UnicastConfig) Equal(rhs *Ipv6UnicastConfig) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.SendDefaultRoute != rhs.SendDefaultRoute { + return false + } + return true +} + +// struct for container bgp-mp:ipv6-unicast. +// IPv6 unicast configuration options. +type Ipv6Unicast struct { + // original -> bgp-mp:prefix-limit + // Configure the maximum number of prefixes that will be + // accepted from a peer. + PrefixLimit PrefixLimit `mapstructure:"prefix-limit" json:"prefix-limit,omitempty"` + // original -> bgp-mp:ipv6-unicast-config + // Configuration parameters for common IPv4 and IPv6 unicast + // AFI-SAFI options. + Config Ipv6UnicastConfig `mapstructure:"config" json:"config,omitempty"` + // original -> bgp-mp:ipv6-unicast-state + // State information for common IPv4 and IPv6 unicast + // parameters. + State Ipv6UnicastState `mapstructure:"state" json:"state,omitempty"` +} + +func (lhs *Ipv6Unicast) Equal(rhs *Ipv6Unicast) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.PrefixLimit.Equal(&(rhs.PrefixLimit)) { + return false + } + if !lhs.Config.Equal(&(rhs.Config)) { + return false + } + return true +} + +// struct for container bgp-mp:state. +// State information for common IPv4 and IPv6 unicast +// parameters. +type Ipv4UnicastState struct { + // original -> bgp-mp:send-default-route + // bgp-mp:send-default-route's original type is boolean. + // If set to true, send the default-route to the neighbour(s). + SendDefaultRoute bool `mapstructure:"send-default-route" json:"send-default-route,omitempty"` +} + +// struct for container bgp-mp:config. +// Configuration parameters for common IPv4 and IPv6 unicast +// AFI-SAFI options. +type Ipv4UnicastConfig struct { + // original -> bgp-mp:send-default-route + // bgp-mp:send-default-route's original type is boolean. + // If set to true, send the default-route to the neighbour(s). + SendDefaultRoute bool `mapstructure:"send-default-route" json:"send-default-route,omitempty"` +} + +func (lhs *Ipv4UnicastConfig) Equal(rhs *Ipv4UnicastConfig) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.SendDefaultRoute != rhs.SendDefaultRoute { + return false + } + return true +} + +// struct for container bgp-mp:state. +// State information relating to the prefix-limit for the +// AFI-SAFI. +type PrefixLimitState struct { + // original -> bgp-mp:max-prefixes + // Maximum number of prefixes that will be accepted + // from the neighbour. + MaxPrefixes uint32 `mapstructure:"max-prefixes" json:"max-prefixes,omitempty"` + // original -> bgp-mp:shutdown-threshold-pct + // Threshold on number of prefixes that can be received + // from a neighbour before generation of warning messages + // or log entries. Expressed as a percentage of + // max-prefixes. + ShutdownThresholdPct Percentage `mapstructure:"shutdown-threshold-pct" json:"shutdown-threshold-pct,omitempty"` + // original -> bgp-mp:restart-timer + // bgp-mp:restart-timer's original type is decimal64. + // Time interval in seconds after which the BGP session + // is re-established after being torn down due to exceeding + // the max-prefix limit. + RestartTimer float64 `mapstructure:"restart-timer" json:"restart-timer,omitempty"` +} + +// struct for container bgp-mp:config. +// Configuration parameters relating to the prefix +// limit for the AFI-SAFI. +type PrefixLimitConfig struct { + // original -> bgp-mp:max-prefixes + // Maximum number of prefixes that will be accepted + // from the neighbour. + MaxPrefixes uint32 `mapstructure:"max-prefixes" json:"max-prefixes,omitempty"` + // original -> bgp-mp:shutdown-threshold-pct + // Threshold on number of prefixes that can be received + // from a neighbour before generation of warning messages + // or log entries. Expressed as a percentage of + // max-prefixes. + ShutdownThresholdPct Percentage `mapstructure:"shutdown-threshold-pct" json:"shutdown-threshold-pct,omitempty"` + // original -> bgp-mp:restart-timer + // bgp-mp:restart-timer's original type is decimal64. + // Time interval in seconds after which the BGP session + // is re-established after being torn down due to exceeding + // the max-prefix limit. + RestartTimer float64 `mapstructure:"restart-timer" json:"restart-timer,omitempty"` +} + +func (lhs *PrefixLimitConfig) Equal(rhs *PrefixLimitConfig) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.MaxPrefixes != rhs.MaxPrefixes { + return false + } + if lhs.ShutdownThresholdPct != rhs.ShutdownThresholdPct { + return false + } + if lhs.RestartTimer != rhs.RestartTimer { + return false + } + return true +} + +// struct for container bgp-mp:prefix-limit. +// Configure the maximum number of prefixes that will be +// accepted from a peer. +type PrefixLimit struct { + // original -> bgp-mp:prefix-limit-config + // Configuration parameters relating to the prefix + // limit for the AFI-SAFI. + Config PrefixLimitConfig `mapstructure:"config" json:"config,omitempty"` + // original -> bgp-mp:prefix-limit-state + // State information relating to the prefix-limit for the + // AFI-SAFI. + State PrefixLimitState `mapstructure:"state" json:"state,omitempty"` +} + +func (lhs *PrefixLimit) Equal(rhs *PrefixLimit) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.Config.Equal(&(rhs.Config)) { + return false + } + return true +} + +// struct for container bgp-mp:ipv4-unicast. +// IPv4 unicast configuration options. +type Ipv4Unicast struct { + // original -> bgp-mp:prefix-limit + // Configure the maximum number of prefixes that will be + // accepted from a peer. + PrefixLimit PrefixLimit `mapstructure:"prefix-limit" json:"prefix-limit,omitempty"` + // original -> bgp-mp:ipv4-unicast-config + // Configuration parameters for common IPv4 and IPv6 unicast + // AFI-SAFI options. + Config Ipv4UnicastConfig `mapstructure:"config" json:"config,omitempty"` + // original -> bgp-mp:ipv4-unicast-state + // State information for common IPv4 and IPv6 unicast + // parameters. + State Ipv4UnicastState `mapstructure:"state" json:"state,omitempty"` +} + +func (lhs *Ipv4Unicast) Equal(rhs *Ipv4Unicast) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.PrefixLimit.Equal(&(rhs.PrefixLimit)) { + return false + } + if !lhs.Config.Equal(&(rhs.Config)) { + return false + } + return true +} + +// struct for container rpol:state. +// Operational state for routing policy. +type ApplyPolicyState struct { + // original -> rpol:import-policy + // list of policy names in sequence to be applied on + // receiving a routing update in the current context, e.g., + // for the current peer group, neighbor, address family, + // etc. + ImportPolicyList []string `mapstructure:"import-policy-list" json:"import-policy-list,omitempty"` + // original -> rpol:default-import-policy + // explicitly set a default policy if no policy definition + // in the import policy chain is satisfied. + DefaultImportPolicy DefaultPolicyType `mapstructure:"default-import-policy" json:"default-import-policy,omitempty"` + // original -> rpol:export-policy + // list of policy names in sequence to be applied on + // sending a routing update in the current context, e.g., + // for the current peer group, neighbor, address family, + // etc. + ExportPolicyList []string `mapstructure:"export-policy-list" json:"export-policy-list,omitempty"` + // original -> rpol:default-export-policy + // explicitly set a default policy if no policy definition + // in the export policy chain is satisfied. + DefaultExportPolicy DefaultPolicyType `mapstructure:"default-export-policy" json:"default-export-policy,omitempty"` + // original -> gobgp:in-policy + // list of policy names in sequence to be applied on + // sending a routing update in the current context, e.g., + // for the current other route server clients. + InPolicyList []string `mapstructure:"in-policy-list" json:"in-policy-list,omitempty"` + // original -> gobgp:default-in-policy + // explicitly set a default policy if no policy definition + // in the in-policy chain is satisfied. + DefaultInPolicy DefaultPolicyType `mapstructure:"default-in-policy" json:"default-in-policy,omitempty"` +} + +// struct for container rpol:config. +// Policy configuration data. +type ApplyPolicyConfig struct { + // original -> rpol:import-policy + // list of policy names in sequence to be applied on + // receiving a routing update in the current context, e.g., + // for the current peer group, neighbor, address family, + // etc. + ImportPolicyList []string `mapstructure:"import-policy-list" json:"import-policy-list,omitempty"` + // original -> rpol:default-import-policy + // explicitly set a default policy if no policy definition + // in the import policy chain is satisfied. + DefaultImportPolicy DefaultPolicyType `mapstructure:"default-import-policy" json:"default-import-policy,omitempty"` + // original -> rpol:export-policy + // list of policy names in sequence to be applied on + // sending a routing update in the current context, e.g., + // for the current peer group, neighbor, address family, + // etc. + ExportPolicyList []string `mapstructure:"export-policy-list" json:"export-policy-list,omitempty"` + // original -> rpol:default-export-policy + // explicitly set a default policy if no policy definition + // in the export policy chain is satisfied. + DefaultExportPolicy DefaultPolicyType `mapstructure:"default-export-policy" json:"default-export-policy,omitempty"` + // original -> gobgp:in-policy + // list of policy names in sequence to be applied on + // sending a routing update in the current context, e.g., + // for the current other route server clients. + InPolicyList []string `mapstructure:"in-policy-list" json:"in-policy-list,omitempty"` + // original -> gobgp:default-in-policy + // explicitly set a default policy if no policy definition + // in the in-policy chain is satisfied. + DefaultInPolicy DefaultPolicyType `mapstructure:"default-in-policy" json:"default-in-policy,omitempty"` +} + +func (lhs *ApplyPolicyConfig) Equal(rhs *ApplyPolicyConfig) bool { + if lhs == nil || rhs == nil { + return false + } + if len(lhs.ImportPolicyList) != len(rhs.ImportPolicyList) { + return false + } + for idx, l := range lhs.ImportPolicyList { + if l != rhs.ImportPolicyList[idx] { + return false + } + } + if lhs.DefaultImportPolicy != rhs.DefaultImportPolicy { + return false + } + if len(lhs.ExportPolicyList) != len(rhs.ExportPolicyList) { + return false + } + for idx, l := range lhs.ExportPolicyList { + if l != rhs.ExportPolicyList[idx] { + return false + } + } + if lhs.DefaultExportPolicy != rhs.DefaultExportPolicy { + return false + } + if len(lhs.InPolicyList) != len(rhs.InPolicyList) { + return false + } + for idx, l := range lhs.InPolicyList { + if l != rhs.InPolicyList[idx] { + return false + } + } + if lhs.DefaultInPolicy != rhs.DefaultInPolicy { + return false + } + return true +} + +// struct for container rpol:apply-policy. +// Anchor point for routing policies in the model. +// Import and export policies are with respect to the local +// routing table, i.e., export (send) and import (receive), +// depending on the context. +type ApplyPolicy struct { + // original -> rpol:apply-policy-config + // Policy configuration data. + Config ApplyPolicyConfig `mapstructure:"config" json:"config,omitempty"` + // original -> rpol:apply-policy-state + // Operational state for routing policy. + State ApplyPolicyState `mapstructure:"state" json:"state,omitempty"` +} + +func (lhs *ApplyPolicy) Equal(rhs *ApplyPolicy) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.Config.Equal(&(rhs.Config)) { + return false + } + return true +} + +// struct for container bgp-mp:state. +// State information relating to the AFI-SAFI. +type AfiSafiState struct { + // original -> bgp-mp:afi-safi-name + // AFI,SAFI. + AfiSafiName AfiSafiType `mapstructure:"afi-safi-name" json:"afi-safi-name,omitempty"` + // original -> bgp-mp:enabled + // bgp-mp:enabled's original type is boolean. + // This leaf indicates whether the IPv4 Unicast AFI,SAFI is + // enabled for the neighbour or group. + Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` + // original -> bgp-op:total-paths + // Total number of BGP paths within the context. + TotalPaths uint32 `mapstructure:"total-paths" json:"total-paths,omitempty"` + // original -> bgp-op:total-prefixes + // . + TotalPrefixes uint32 `mapstructure:"total-prefixes" json:"total-prefixes,omitempty"` + // original -> gobgp:family + // gobgp:family's original type is route-family. + // Address family value of AFI-SAFI pair translated from afi-safi-name. + Family bgp.RouteFamily `mapstructure:"family" json:"family,omitempty"` +} + +// struct for container bgp-mp:config. +// Configuration parameters for the AFI-SAFI. +type AfiSafiConfig struct { + // original -> bgp-mp:afi-safi-name + // AFI,SAFI. + AfiSafiName AfiSafiType `mapstructure:"afi-safi-name" json:"afi-safi-name,omitempty"` + // original -> bgp-mp:enabled + // bgp-mp:enabled's original type is boolean. + // This leaf indicates whether the IPv4 Unicast AFI,SAFI is + // enabled for the neighbour or group. + Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` +} + +func (lhs *AfiSafiConfig) Equal(rhs *AfiSafiConfig) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.AfiSafiName != rhs.AfiSafiName { + return false + } + if lhs.Enabled != rhs.Enabled { + return false + } + return true +} + +// struct for container bgp-mp:state. +// State information for BGP graceful-restart. +type MpGracefulRestartState struct { + // original -> bgp-mp:enabled + // bgp-mp:enabled's original type is boolean. + // This leaf indicates whether graceful-restart is enabled for + // this AFI-SAFI. + Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` + // original -> bgp-op:received + // bgp-op:received's original type is boolean. + // This leaf indicates whether the neighbor advertised the + // ability to support graceful-restart for this AFI-SAFI. + Received bool `mapstructure:"received" json:"received,omitempty"` + // original -> bgp-op:advertised + // bgp-op:advertised's original type is boolean. + // This leaf indicates whether the ability to support + // graceful-restart has been advertised to the peer. + Advertised bool `mapstructure:"advertised" json:"advertised,omitempty"` + // original -> gobgp:end-of-rib-received + // gobgp:end-of-rib-received's original type is boolean. + EndOfRibReceived bool `mapstructure:"end-of-rib-received" json:"end-of-rib-received,omitempty"` + // original -> gobgp:end-of-rib-sent + // gobgp:end-of-rib-sent's original type is boolean. + EndOfRibSent bool `mapstructure:"end-of-rib-sent" json:"end-of-rib-sent,omitempty"` +} + +// struct for container bgp-mp:config. +// Configuration options for BGP graceful-restart. +type MpGracefulRestartConfig struct { + // original -> bgp-mp:enabled + // bgp-mp:enabled's original type is boolean. + // This leaf indicates whether graceful-restart is enabled for + // this AFI-SAFI. + Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` +} + +func (lhs *MpGracefulRestartConfig) Equal(rhs *MpGracefulRestartConfig) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.Enabled != rhs.Enabled { + return false + } + return true +} + +// struct for container bgp-mp:graceful-restart. +// Parameters relating to BGP graceful-restart. +type MpGracefulRestart struct { + // original -> bgp-mp:mp-graceful-restart-config + // Configuration options for BGP graceful-restart. + Config MpGracefulRestartConfig `mapstructure:"config" json:"config,omitempty"` + // original -> bgp-mp:mp-graceful-restart-state + // State information for BGP graceful-restart. + State MpGracefulRestartState `mapstructure:"state" json:"state,omitempty"` +} + +func (lhs *MpGracefulRestart) Equal(rhs *MpGracefulRestart) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.Config.Equal(&(rhs.Config)) { + return false + } + return true +} + +// struct for container bgp-mp:afi-safi. +// AFI,SAFI configuration available for the +// neighbour or group. +type AfiSafi struct { + // original -> bgp-mp:afi-safi-name + // original -> bgp-mp:mp-graceful-restart + // Parameters relating to BGP graceful-restart. + MpGracefulRestart MpGracefulRestart `mapstructure:"mp-graceful-restart" json:"mp-graceful-restart,omitempty"` + // original -> bgp-mp:afi-safi-config + // Configuration parameters for the AFI-SAFI. + Config AfiSafiConfig `mapstructure:"config" json:"config,omitempty"` + // original -> bgp-mp:afi-safi-state + // State information relating to the AFI-SAFI. + State AfiSafiState `mapstructure:"state" json:"state,omitempty"` + // original -> rpol:apply-policy + // Anchor point for routing policies in the model. + // Import and export policies are with respect to the local + // routing table, i.e., export (send) and import (receive), + // depending on the context. + ApplyPolicy ApplyPolicy `mapstructure:"apply-policy" json:"apply-policy,omitempty"` + // original -> bgp-mp:ipv4-unicast + // IPv4 unicast configuration options. + Ipv4Unicast Ipv4Unicast `mapstructure:"ipv4-unicast" json:"ipv4-unicast,omitempty"` + // original -> bgp-mp:ipv6-unicast + // IPv6 unicast configuration options. + Ipv6Unicast Ipv6Unicast `mapstructure:"ipv6-unicast" json:"ipv6-unicast,omitempty"` + // original -> bgp-mp:ipv4-labelled-unicast + // IPv4 Labelled Unicast configuration options. + Ipv4LabelledUnicast Ipv4LabelledUnicast `mapstructure:"ipv4-labelled-unicast" json:"ipv4-labelled-unicast,omitempty"` + // original -> bgp-mp:ipv6-labelled-unicast + // IPv6 Labelled Unicast configuration options. + Ipv6LabelledUnicast Ipv6LabelledUnicast `mapstructure:"ipv6-labelled-unicast" json:"ipv6-labelled-unicast,omitempty"` + // original -> bgp-mp:l3vpn-ipv4-unicast + // Unicast IPv4 L3VPN configuration options. + L3vpnIpv4Unicast L3vpnIpv4Unicast `mapstructure:"l3vpn-ipv4-unicast" json:"l3vpn-ipv4-unicast,omitempty"` + // original -> bgp-mp:l3vpn-ipv6-unicast + // Unicast IPv6 L3VPN configuration options. + L3vpnIpv6Unicast L3vpnIpv6Unicast `mapstructure:"l3vpn-ipv6-unicast" json:"l3vpn-ipv6-unicast,omitempty"` + // original -> bgp-mp:l3vpn-ipv4-multicast + // Multicast IPv4 L3VPN configuration options. + L3vpnIpv4Multicast L3vpnIpv4Multicast `mapstructure:"l3vpn-ipv4-multicast" json:"l3vpn-ipv4-multicast,omitempty"` + // original -> bgp-mp:l3vpn-ipv6-multicast + // Multicast IPv6 L3VPN configuration options. + L3vpnIpv6Multicast L3vpnIpv6Multicast `mapstructure:"l3vpn-ipv6-multicast" json:"l3vpn-ipv6-multicast,omitempty"` + // original -> bgp-mp:l2vpn-vpls + // BGP-signalled VPLS configuration options. + L2vpnVpls L2vpnVpls `mapstructure:"l2vpn-vpls" json:"l2vpn-vpls,omitempty"` + // original -> bgp-mp:l2vpn-evpn + // BGP EVPN configuration options. + L2vpnEvpn L2vpnEvpn `mapstructure:"l2vpn-evpn" json:"l2vpn-evpn,omitempty"` + // original -> bgp-mp:route-selection-options + // Parameters relating to options for route selection. + RouteSelectionOptions RouteSelectionOptions `mapstructure:"route-selection-options" json:"route-selection-options,omitempty"` + // original -> bgp-mp:use-multiple-paths + // Parameters related to the use of multiple paths for the + // same NLRI. + UseMultiplePaths UseMultiplePaths `mapstructure:"use-multiple-paths" json:"use-multiple-paths,omitempty"` + // original -> bgp-mp:prefix-limit + // Configure the maximum number of prefixes that will be + // accepted from a peer. + PrefixLimit PrefixLimit `mapstructure:"prefix-limit" json:"prefix-limit,omitempty"` + // original -> gobgp:route-target-membership + RouteTargetMembership RouteTargetMembership `mapstructure:"route-target-membership" json:"route-target-membership,omitempty"` + // original -> gobgp:long-lived-graceful-restart + LongLivedGracefulRestart LongLivedGracefulRestart `mapstructure:"long-lived-graceful-restart" json:"long-lived-graceful-restart,omitempty"` + // original -> gobgp:add-paths + // add-paths configuration options related to a particular AFI-SAFI. + AddPaths AddPaths `mapstructure:"add-paths" json:"add-paths,omitempty"` +} + +func (lhs *AfiSafi) Equal(rhs *AfiSafi) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.MpGracefulRestart.Equal(&(rhs.MpGracefulRestart)) { + return false + } + if !lhs.Config.Equal(&(rhs.Config)) { + return false + } + if !lhs.ApplyPolicy.Equal(&(rhs.ApplyPolicy)) { + return false + } + if !lhs.Ipv4Unicast.Equal(&(rhs.Ipv4Unicast)) { + return false + } + if !lhs.Ipv6Unicast.Equal(&(rhs.Ipv6Unicast)) { + return false + } + if !lhs.Ipv4LabelledUnicast.Equal(&(rhs.Ipv4LabelledUnicast)) { + return false + } + if !lhs.Ipv6LabelledUnicast.Equal(&(rhs.Ipv6LabelledUnicast)) { + return false + } + if !lhs.L3vpnIpv4Unicast.Equal(&(rhs.L3vpnIpv4Unicast)) { + return false + } + if !lhs.L3vpnIpv6Unicast.Equal(&(rhs.L3vpnIpv6Unicast)) { + return false + } + if !lhs.L3vpnIpv4Multicast.Equal(&(rhs.L3vpnIpv4Multicast)) { + return false + } + if !lhs.L3vpnIpv6Multicast.Equal(&(rhs.L3vpnIpv6Multicast)) { + return false + } + if !lhs.L2vpnVpls.Equal(&(rhs.L2vpnVpls)) { + return false + } + if !lhs.L2vpnEvpn.Equal(&(rhs.L2vpnEvpn)) { + return false + } + if !lhs.RouteSelectionOptions.Equal(&(rhs.RouteSelectionOptions)) { + return false + } + if !lhs.UseMultiplePaths.Equal(&(rhs.UseMultiplePaths)) { + return false + } + if !lhs.PrefixLimit.Equal(&(rhs.PrefixLimit)) { + return false + } + if !lhs.RouteTargetMembership.Equal(&(rhs.RouteTargetMembership)) { + return false + } + if !lhs.LongLivedGracefulRestart.Equal(&(rhs.LongLivedGracefulRestart)) { + return false + } + if !lhs.AddPaths.Equal(&(rhs.AddPaths)) { + return false + } + return true +} + +// struct for container bgp:state. +// State information associated with graceful-restart. +type GracefulRestartState struct { + // original -> bgp:enabled + // bgp:enabled's original type is boolean. + // Enable or disable the graceful-restart capability. + Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` + // original -> bgp:restart-time + // Estimated time (in seconds) for the local BGP speaker to + // restart a session. This value is advertise in the graceful + // restart BGP capability. This is a 12-bit value, referred to + // as Restart Time in RFC4724. Per RFC4724, the suggested + // default value is <= the hold-time value. + RestartTime uint16 `mapstructure:"restart-time" json:"restart-time,omitempty"` + // original -> bgp:stale-routes-time + // bgp:stale-routes-time's original type is decimal64. + // An upper-bound on the time thate stale routes will be + // retained by a router after a session is restarted. If an + // End-of-RIB (EOR) marker is received prior to this timer + // expiring stale-routes will be flushed upon its receipt - if + // no EOR is received, then when this timer expires stale paths + // will be purged. This timer is referred to as the + // Selection_Deferral_Timer in RFC4724. + StaleRoutesTime float64 `mapstructure:"stale-routes-time" json:"stale-routes-time,omitempty"` + // original -> bgp:helper-only + // bgp:helper-only's original type is boolean. + // Enable graceful-restart in helper mode only. When this + // leaf is set, the local system does not retain forwarding + // its own state during a restart, but supports procedures + // for the receiving speaker, as defined in RFC4724. + HelperOnly bool `mapstructure:"helper-only" json:"helper-only,omitempty"` + // original -> bgp-op:peer-restart-time + // The period of time (advertised by the peer) that + // the peer expects a restart of a BGP session to + // take. + PeerRestartTime uint16 `mapstructure:"peer-restart-time" json:"peer-restart-time,omitempty"` + // original -> bgp-op:peer-restarting + // bgp-op:peer-restarting's original type is boolean. + // This flag indicates whether the remote neighbor is currently + // in the process of restarting, and hence received routes are + // currently stale. + PeerRestarting bool `mapstructure:"peer-restarting" json:"peer-restarting,omitempty"` + // original -> bgp-op:local-restarting + // bgp-op:local-restarting's original type is boolean. + // This flag indicates whether the local neighbor is currently + // restarting. The flag is unset after all NLRI have been + // advertised to the peer, and the End-of-RIB (EOR) marker has + // been unset. + LocalRestarting bool `mapstructure:"local-restarting" json:"local-restarting,omitempty"` + // original -> bgp-op:mode + // Ths leaf indicates the mode of operation of BGP graceful + // restart with the peer. + Mode Mode `mapstructure:"mode" json:"mode,omitempty"` + // original -> gobgp:deferral-time + DeferralTime uint16 `mapstructure:"deferral-time" json:"deferral-time,omitempty"` + // original -> gobgp:notification-enabled + // gobgp:notification-enabled's original type is boolean. + NotificationEnabled bool `mapstructure:"notification-enabled" json:"notification-enabled,omitempty"` + // original -> gobgp:long-lived-enabled + // gobgp:long-lived-enabled's original type is boolean. + LongLivedEnabled bool `mapstructure:"long-lived-enabled" json:"long-lived-enabled,omitempty"` +} + +// struct for container bgp:config. +// Configuration parameters relating to graceful-restart. +type GracefulRestartConfig struct { + // original -> bgp:enabled + // bgp:enabled's original type is boolean. + // Enable or disable the graceful-restart capability. + Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` + // original -> bgp:restart-time + // Estimated time (in seconds) for the local BGP speaker to + // restart a session. This value is advertise in the graceful + // restart BGP capability. This is a 12-bit value, referred to + // as Restart Time in RFC4724. Per RFC4724, the suggested + // default value is <= the hold-time value. + RestartTime uint16 `mapstructure:"restart-time" json:"restart-time,omitempty"` + // original -> bgp:stale-routes-time + // bgp:stale-routes-time's original type is decimal64. + // An upper-bound on the time thate stale routes will be + // retained by a router after a session is restarted. If an + // End-of-RIB (EOR) marker is received prior to this timer + // expiring stale-routes will be flushed upon its receipt - if + // no EOR is received, then when this timer expires stale paths + // will be purged. This timer is referred to as the + // Selection_Deferral_Timer in RFC4724. + StaleRoutesTime float64 `mapstructure:"stale-routes-time" json:"stale-routes-time,omitempty"` + // original -> bgp:helper-only + // bgp:helper-only's original type is boolean. + // Enable graceful-restart in helper mode only. When this + // leaf is set, the local system does not retain forwarding + // its own state during a restart, but supports procedures + // for the receiving speaker, as defined in RFC4724. + HelperOnly bool `mapstructure:"helper-only" json:"helper-only,omitempty"` + // original -> gobgp:deferral-time + DeferralTime uint16 `mapstructure:"deferral-time" json:"deferral-time,omitempty"` + // original -> gobgp:notification-enabled + // gobgp:notification-enabled's original type is boolean. + NotificationEnabled bool `mapstructure:"notification-enabled" json:"notification-enabled,omitempty"` + // original -> gobgp:long-lived-enabled + // gobgp:long-lived-enabled's original type is boolean. + LongLivedEnabled bool `mapstructure:"long-lived-enabled" json:"long-lived-enabled,omitempty"` +} + +func (lhs *GracefulRestartConfig) Equal(rhs *GracefulRestartConfig) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.Enabled != rhs.Enabled { + return false + } + if lhs.RestartTime != rhs.RestartTime { + return false + } + if lhs.StaleRoutesTime != rhs.StaleRoutesTime { + return false + } + if lhs.HelperOnly != rhs.HelperOnly { + return false + } + if lhs.DeferralTime != rhs.DeferralTime { + return false + } + if lhs.NotificationEnabled != rhs.NotificationEnabled { + return false + } + if lhs.LongLivedEnabled != rhs.LongLivedEnabled { + return false + } + return true +} + +// struct for container bgp:graceful-restart. +// Parameters relating the graceful restart mechanism for BGP. +type GracefulRestart struct { + // original -> bgp:graceful-restart-config + // Configuration parameters relating to graceful-restart. + Config GracefulRestartConfig `mapstructure:"config" json:"config,omitempty"` + // original -> bgp:graceful-restart-state + // State information associated with graceful-restart. + State GracefulRestartState `mapstructure:"state" json:"state,omitempty"` +} + +func (lhs *GracefulRestart) Equal(rhs *GracefulRestart) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.Config.Equal(&(rhs.Config)) { + return false + } + return true +} + +// struct for container bgp-mp:state. +// State information relating to iBGP multipath. +type IbgpState struct { + // original -> bgp-mp:maximum-paths + // Maximum number of parallel paths to consider when using + // iBGP multipath. The default is to use a single path. + MaximumPaths uint32 `mapstructure:"maximum-paths" json:"maximum-paths,omitempty"` +} + +// struct for container bgp-mp:config. +// Configuration parameters relating to iBGP multipath. +type IbgpConfig struct { + // original -> bgp-mp:maximum-paths + // Maximum number of parallel paths to consider when using + // iBGP multipath. The default is to use a single path. + MaximumPaths uint32 `mapstructure:"maximum-paths" json:"maximum-paths,omitempty"` +} + +func (lhs *IbgpConfig) Equal(rhs *IbgpConfig) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.MaximumPaths != rhs.MaximumPaths { + return false + } + return true +} + +// struct for container bgp-mp:ibgp. +// Multipath parameters for iBGP. +type Ibgp struct { + // original -> bgp-mp:ibgp-config + // Configuration parameters relating to iBGP multipath. + Config IbgpConfig `mapstructure:"config" json:"config,omitempty"` + // original -> bgp-mp:ibgp-state + // State information relating to iBGP multipath. + State IbgpState `mapstructure:"state" json:"state,omitempty"` +} + +func (lhs *Ibgp) Equal(rhs *Ibgp) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.Config.Equal(&(rhs.Config)) { + return false + } + return true +} + +// struct for container bgp-mp:state. +// State information relating to eBGP multipath. +type EbgpState struct { + // original -> bgp-mp:allow-multiple-as + // bgp-mp:allow-multiple-as's original type is boolean. + // Allow multipath to use paths from different neighbouring + // ASes. The default is to only consider multiple paths from + // the same neighbouring AS. + AllowMultipleAs bool `mapstructure:"allow-multiple-as" json:"allow-multiple-as,omitempty"` + // original -> bgp-mp:maximum-paths + // Maximum number of parallel paths to consider when using + // BGP multipath. The default is use a single path. + MaximumPaths uint32 `mapstructure:"maximum-paths" json:"maximum-paths,omitempty"` +} + +// struct for container bgp-mp:config. +// Configuration parameters relating to eBGP multipath. +type EbgpConfig struct { + // original -> bgp-mp:allow-multiple-as + // bgp-mp:allow-multiple-as's original type is boolean. + // Allow multipath to use paths from different neighbouring + // ASes. The default is to only consider multiple paths from + // the same neighbouring AS. + AllowMultipleAs bool `mapstructure:"allow-multiple-as" json:"allow-multiple-as,omitempty"` + // original -> bgp-mp:maximum-paths + // Maximum number of parallel paths to consider when using + // BGP multipath. The default is use a single path. + MaximumPaths uint32 `mapstructure:"maximum-paths" json:"maximum-paths,omitempty"` +} + +func (lhs *EbgpConfig) Equal(rhs *EbgpConfig) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.AllowMultipleAs != rhs.AllowMultipleAs { + return false + } + if lhs.MaximumPaths != rhs.MaximumPaths { + return false + } + return true +} + +// struct for container bgp-mp:ebgp. +// Multipath parameters for eBGP. +type Ebgp struct { + // original -> bgp-mp:ebgp-config + // Configuration parameters relating to eBGP multipath. + Config EbgpConfig `mapstructure:"config" json:"config,omitempty"` + // original -> bgp-mp:ebgp-state + // State information relating to eBGP multipath. + State EbgpState `mapstructure:"state" json:"state,omitempty"` +} + +func (lhs *Ebgp) Equal(rhs *Ebgp) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.Config.Equal(&(rhs.Config)) { + return false + } + return true +} + +// struct for container bgp-mp:state. +// State parameters relating to multipath. +type UseMultiplePathsState struct { + // original -> bgp-mp:enabled + // bgp-mp:enabled's original type is boolean. + // Whether the use of multiple paths for the same NLRI is + // enabled for the neighbor. This value is overridden by + // any more specific configuration value. + Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` +} + +// struct for container bgp-mp:config. +// Configuration parameters relating to multipath. +type UseMultiplePathsConfig struct { + // original -> bgp-mp:enabled + // bgp-mp:enabled's original type is boolean. + // Whether the use of multiple paths for the same NLRI is + // enabled for the neighbor. This value is overridden by + // any more specific configuration value. + Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` +} + +func (lhs *UseMultiplePathsConfig) Equal(rhs *UseMultiplePathsConfig) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.Enabled != rhs.Enabled { + return false + } + return true +} + +// struct for container bgp-mp:use-multiple-paths. +// Parameters related to the use of multiple paths for the +// same NLRI. +type UseMultiplePaths struct { + // original -> bgp-mp:use-multiple-paths-config + // Configuration parameters relating to multipath. + Config UseMultiplePathsConfig `mapstructure:"config" json:"config,omitempty"` + // original -> bgp-mp:use-multiple-paths-state + // State parameters relating to multipath. + State UseMultiplePathsState `mapstructure:"state" json:"state,omitempty"` + // original -> bgp-mp:ebgp + // Multipath parameters for eBGP. + Ebgp Ebgp `mapstructure:"ebgp" json:"ebgp,omitempty"` + // original -> bgp-mp:ibgp + // Multipath parameters for iBGP. + Ibgp Ibgp `mapstructure:"ibgp" json:"ibgp,omitempty"` +} + +func (lhs *UseMultiplePaths) Equal(rhs *UseMultiplePaths) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.Config.Equal(&(rhs.Config)) { + return false + } + if !lhs.Ebgp.Equal(&(rhs.Ebgp)) { + return false + } + if !lhs.Ibgp.Equal(&(rhs.Ibgp)) { + return false + } + return true +} + +// struct for container bgp:state. +// State information relating to the BGP confederations. +type ConfederationState struct { + // original -> bgp:enabled + // bgp:enabled's original type is boolean. + // When this leaf is set to true it indicates that + // the local-AS is part of a BGP confederation. + Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` + // original -> bgp:identifier + // bgp:identifier's original type is inet:as-number. + // Confederation identifier for the autonomous system. + Identifier uint32 `mapstructure:"identifier" json:"identifier,omitempty"` + // original -> bgp:member-as + // original type is list of inet:as-number + // Remote autonomous systems that are to be treated + // as part of the local confederation. + MemberAsList []uint32 `mapstructure:"member-as-list" json:"member-as-list,omitempty"` +} + +// struct for container bgp:config. +// Configuration parameters relating to BGP confederations. +type ConfederationConfig struct { + // original -> bgp:enabled + // bgp:enabled's original type is boolean. + // When this leaf is set to true it indicates that + // the local-AS is part of a BGP confederation. + Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` + // original -> bgp:identifier + // bgp:identifier's original type is inet:as-number. + // Confederation identifier for the autonomous system. + Identifier uint32 `mapstructure:"identifier" json:"identifier,omitempty"` + // original -> bgp:member-as + // original type is list of inet:as-number + // Remote autonomous systems that are to be treated + // as part of the local confederation. + MemberAsList []uint32 `mapstructure:"member-as-list" json:"member-as-list,omitempty"` +} + +func (lhs *ConfederationConfig) Equal(rhs *ConfederationConfig) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.Enabled != rhs.Enabled { + return false + } + if lhs.Identifier != rhs.Identifier { + return false + } + if len(lhs.MemberAsList) != len(rhs.MemberAsList) { + return false + } + for idx, l := range lhs.MemberAsList { + if l != rhs.MemberAsList[idx] { + return false + } + } + return true +} + +// struct for container bgp:confederation. +// Parameters indicating whether the local system acts as part +// of a BGP confederation. +type Confederation struct { + // original -> bgp:confederation-config + // Configuration parameters relating to BGP confederations. + Config ConfederationConfig `mapstructure:"config" json:"config,omitempty"` + // original -> bgp:confederation-state + // State information relating to the BGP confederations. + State ConfederationState `mapstructure:"state" json:"state,omitempty"` +} + +func (lhs *Confederation) Equal(rhs *Confederation) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.Config.Equal(&(rhs.Config)) { + return false + } + return true +} + +// struct for container bgp:state. +// State information relating to the default route distance. +type DefaultRouteDistanceState struct { + // original -> bgp:external-route-distance + // Administrative distance for routes learned from external + // BGP (eBGP). + ExternalRouteDistance uint8 `mapstructure:"external-route-distance" json:"external-route-distance,omitempty"` + // original -> bgp:internal-route-distance + // Administrative distance for routes learned from internal + // BGP (iBGP). + InternalRouteDistance uint8 `mapstructure:"internal-route-distance" json:"internal-route-distance,omitempty"` +} + +// struct for container bgp:config. +// Configuration parameters relating to the default route +// distance. +type DefaultRouteDistanceConfig struct { + // original -> bgp:external-route-distance + // Administrative distance for routes learned from external + // BGP (eBGP). + ExternalRouteDistance uint8 `mapstructure:"external-route-distance" json:"external-route-distance,omitempty"` + // original -> bgp:internal-route-distance + // Administrative distance for routes learned from internal + // BGP (iBGP). + InternalRouteDistance uint8 `mapstructure:"internal-route-distance" json:"internal-route-distance,omitempty"` +} + +func (lhs *DefaultRouteDistanceConfig) Equal(rhs *DefaultRouteDistanceConfig) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.ExternalRouteDistance != rhs.ExternalRouteDistance { + return false + } + if lhs.InternalRouteDistance != rhs.InternalRouteDistance { + return false + } + return true +} + +// struct for container bgp:default-route-distance. +// Administrative distance (or preference) assigned to +// routes received from different sources +// (external, internal, and local). +type DefaultRouteDistance struct { + // original -> bgp:default-route-distance-config + // Configuration parameters relating to the default route + // distance. + Config DefaultRouteDistanceConfig `mapstructure:"config" json:"config,omitempty"` + // original -> bgp:default-route-distance-state + // State information relating to the default route distance. + State DefaultRouteDistanceState `mapstructure:"state" json:"state,omitempty"` +} + +func (lhs *DefaultRouteDistance) Equal(rhs *DefaultRouteDistance) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.Config.Equal(&(rhs.Config)) { + return false + } + return true +} + +// struct for container bgp-mp:state. +// State information for the route selection options. +type RouteSelectionOptionsState struct { + // original -> bgp-mp:always-compare-med + // bgp-mp:always-compare-med's original type is boolean. + // Compare multi-exit discriminator (MED) value from + // different ASes when selecting the best route. The + // default behavior is to only compare MEDs for paths + // received from the same AS. + AlwaysCompareMed bool `mapstructure:"always-compare-med" json:"always-compare-med,omitempty"` + // original -> bgp-mp:ignore-as-path-length + // bgp-mp:ignore-as-path-length's original type is boolean. + // Ignore the AS path length when selecting the best path. + // The default is to use the AS path length and prefer paths + // with shorter length. + IgnoreAsPathLength bool `mapstructure:"ignore-as-path-length" json:"ignore-as-path-length,omitempty"` + // original -> bgp-mp:external-compare-router-id + // bgp-mp:external-compare-router-id's original type is boolean. + // When comparing similar routes received from external + // BGP peers, use the router-id as a criterion to select + // the active path. + ExternalCompareRouterId bool `mapstructure:"external-compare-router-id" json:"external-compare-router-id,omitempty"` + // original -> bgp-mp:advertise-inactive-routes + // bgp-mp:advertise-inactive-routes's original type is boolean. + // Advertise inactive routes to external peers. The + // default is to only advertise active routes. + AdvertiseInactiveRoutes bool `mapstructure:"advertise-inactive-routes" json:"advertise-inactive-routes,omitempty"` + // original -> bgp-mp:enable-aigp + // bgp-mp:enable-aigp's original type is boolean. + // Flag to enable sending / receiving accumulated IGP + // attribute in routing updates. + EnableAigp bool `mapstructure:"enable-aigp" json:"enable-aigp,omitempty"` + // original -> bgp-mp:ignore-next-hop-igp-metric + // bgp-mp:ignore-next-hop-igp-metric's original type is boolean. + // Ignore the IGP metric to the next-hop when calculating + // BGP best-path. The default is to select the route for + // which the metric to the next-hop is lowest. + IgnoreNextHopIgpMetric bool `mapstructure:"ignore-next-hop-igp-metric" json:"ignore-next-hop-igp-metric,omitempty"` + // original -> gobgp:disable-best-path-selection + // gobgp:disable-best-path-selection's original type is boolean. + // Disables best path selection process. + DisableBestPathSelection bool `mapstructure:"disable-best-path-selection" json:"disable-best-path-selection,omitempty"` +} + +// struct for container bgp-mp:config. +// Configuration parameters relating to route selection +// options. +type RouteSelectionOptionsConfig struct { + // original -> bgp-mp:always-compare-med + // bgp-mp:always-compare-med's original type is boolean. + // Compare multi-exit discriminator (MED) value from + // different ASes when selecting the best route. The + // default behavior is to only compare MEDs for paths + // received from the same AS. + AlwaysCompareMed bool `mapstructure:"always-compare-med" json:"always-compare-med,omitempty"` + // original -> bgp-mp:ignore-as-path-length + // bgp-mp:ignore-as-path-length's original type is boolean. + // Ignore the AS path length when selecting the best path. + // The default is to use the AS path length and prefer paths + // with shorter length. + IgnoreAsPathLength bool `mapstructure:"ignore-as-path-length" json:"ignore-as-path-length,omitempty"` + // original -> bgp-mp:external-compare-router-id + // bgp-mp:external-compare-router-id's original type is boolean. + // When comparing similar routes received from external + // BGP peers, use the router-id as a criterion to select + // the active path. + ExternalCompareRouterId bool `mapstructure:"external-compare-router-id" json:"external-compare-router-id,omitempty"` + // original -> bgp-mp:advertise-inactive-routes + // bgp-mp:advertise-inactive-routes's original type is boolean. + // Advertise inactive routes to external peers. The + // default is to only advertise active routes. + AdvertiseInactiveRoutes bool `mapstructure:"advertise-inactive-routes" json:"advertise-inactive-routes,omitempty"` + // original -> bgp-mp:enable-aigp + // bgp-mp:enable-aigp's original type is boolean. + // Flag to enable sending / receiving accumulated IGP + // attribute in routing updates. + EnableAigp bool `mapstructure:"enable-aigp" json:"enable-aigp,omitempty"` + // original -> bgp-mp:ignore-next-hop-igp-metric + // bgp-mp:ignore-next-hop-igp-metric's original type is boolean. + // Ignore the IGP metric to the next-hop when calculating + // BGP best-path. The default is to select the route for + // which the metric to the next-hop is lowest. + IgnoreNextHopIgpMetric bool `mapstructure:"ignore-next-hop-igp-metric" json:"ignore-next-hop-igp-metric,omitempty"` + // original -> gobgp:disable-best-path-selection + // gobgp:disable-best-path-selection's original type is boolean. + // Disables best path selection process. + DisableBestPathSelection bool `mapstructure:"disable-best-path-selection" json:"disable-best-path-selection,omitempty"` +} + +func (lhs *RouteSelectionOptionsConfig) Equal(rhs *RouteSelectionOptionsConfig) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.AlwaysCompareMed != rhs.AlwaysCompareMed { + return false + } + if lhs.IgnoreAsPathLength != rhs.IgnoreAsPathLength { + return false + } + if lhs.ExternalCompareRouterId != rhs.ExternalCompareRouterId { + return false + } + if lhs.AdvertiseInactiveRoutes != rhs.AdvertiseInactiveRoutes { + return false + } + if lhs.EnableAigp != rhs.EnableAigp { + return false + } + if lhs.IgnoreNextHopIgpMetric != rhs.IgnoreNextHopIgpMetric { + return false + } + if lhs.DisableBestPathSelection != rhs.DisableBestPathSelection { + return false + } + return true +} + +// struct for container bgp-mp:route-selection-options. +// Parameters relating to options for route selection. +type RouteSelectionOptions struct { + // original -> bgp-mp:route-selection-options-config + // Configuration parameters relating to route selection + // options. + Config RouteSelectionOptionsConfig `mapstructure:"config" json:"config,omitempty"` + // original -> bgp-mp:route-selection-options-state + // State information for the route selection options. + State RouteSelectionOptionsState `mapstructure:"state" json:"state,omitempty"` +} + +func (lhs *RouteSelectionOptions) Equal(rhs *RouteSelectionOptions) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.Config.Equal(&(rhs.Config)) { + return false + } + return true +} + +// struct for container bgp:state. +// State information relating to the global BGP router. +type GlobalState struct { + // original -> bgp:as + // bgp:as's original type is inet:as-number. + // Local autonomous system number of the router. Uses + // the 32-bit as-number type from the model in RFC 6991. + As uint32 `mapstructure:"as" json:"as,omitempty"` + // original -> bgp:router-id + // bgp:router-id's original type is inet:ipv4-address. + // Router id of the router, expressed as an + // 32-bit value, IPv4 address. + RouterId string `mapstructure:"router-id" json:"router-id,omitempty"` + // original -> bgp-op:total-paths + // Total number of BGP paths within the context. + TotalPaths uint32 `mapstructure:"total-paths" json:"total-paths,omitempty"` + // original -> bgp-op:total-prefixes + // . + TotalPrefixes uint32 `mapstructure:"total-prefixes" json:"total-prefixes,omitempty"` + // original -> gobgp:port + Port int32 `mapstructure:"port" json:"port,omitempty"` + // original -> gobgp:local-address + LocalAddressList []string `mapstructure:"local-address-list" json:"local-address-list,omitempty"` +} + +// struct for container bgp:config. +// Configuration parameters relating to the global BGP router. +type GlobalConfig struct { + // original -> bgp:as + // bgp:as's original type is inet:as-number. + // Local autonomous system number of the router. Uses + // the 32-bit as-number type from the model in RFC 6991. + As uint32 `mapstructure:"as" json:"as,omitempty"` + // original -> bgp:router-id + // bgp:router-id's original type is inet:ipv4-address. + // Router id of the router, expressed as an + // 32-bit value, IPv4 address. + RouterId string `mapstructure:"router-id" json:"router-id,omitempty"` + // original -> gobgp:port + Port int32 `mapstructure:"port" json:"port,omitempty"` + // original -> gobgp:local-address + LocalAddressList []string `mapstructure:"local-address-list" json:"local-address-list,omitempty"` +} + +func (lhs *GlobalConfig) Equal(rhs *GlobalConfig) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.As != rhs.As { + return false + } + if lhs.RouterId != rhs.RouterId { + return false + } + if lhs.Port != rhs.Port { + return false + } + if len(lhs.LocalAddressList) != len(rhs.LocalAddressList) { + return false + } + for idx, l := range lhs.LocalAddressList { + if l != rhs.LocalAddressList[idx] { + return false + } + } + return true +} + +// struct for container bgp:global. +// Global configuration for the BGP router. +type Global struct { + // original -> bgp:global-config + // Configuration parameters relating to the global BGP router. + Config GlobalConfig `mapstructure:"config" json:"config,omitempty"` + // original -> bgp:global-state + // State information relating to the global BGP router. + State GlobalState `mapstructure:"state" json:"state,omitempty"` + // original -> bgp-mp:route-selection-options + // Parameters relating to options for route selection. + RouteSelectionOptions RouteSelectionOptions `mapstructure:"route-selection-options" json:"route-selection-options,omitempty"` + // original -> bgp:default-route-distance + // Administrative distance (or preference) assigned to + // routes received from different sources + // (external, internal, and local). + DefaultRouteDistance DefaultRouteDistance `mapstructure:"default-route-distance" json:"default-route-distance,omitempty"` + // original -> bgp:confederation + // Parameters indicating whether the local system acts as part + // of a BGP confederation. + Confederation Confederation `mapstructure:"confederation" json:"confederation,omitempty"` + // original -> bgp-mp:use-multiple-paths + // Parameters related to the use of multiple paths for the + // same NLRI. + UseMultiplePaths UseMultiplePaths `mapstructure:"use-multiple-paths" json:"use-multiple-paths,omitempty"` + // original -> bgp:graceful-restart + // Parameters relating the graceful restart mechanism for BGP. + GracefulRestart GracefulRestart `mapstructure:"graceful-restart" json:"graceful-restart,omitempty"` + // original -> bgp:afi-safis + // Address family specific configuration. + AfiSafis []AfiSafi `mapstructure:"afi-safis" json:"afi-safis,omitempty"` + // original -> rpol:apply-policy + // Anchor point for routing policies in the model. + // Import and export policies are with respect to the local + // routing table, i.e., export (send) and import (receive), + // depending on the context. + ApplyPolicy ApplyPolicy `mapstructure:"apply-policy" json:"apply-policy,omitempty"` +} + +func (lhs *Global) Equal(rhs *Global) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.Config.Equal(&(rhs.Config)) { + return false + } + if !lhs.RouteSelectionOptions.Equal(&(rhs.RouteSelectionOptions)) { + return false + } + if !lhs.DefaultRouteDistance.Equal(&(rhs.DefaultRouteDistance)) { + return false + } + if !lhs.Confederation.Equal(&(rhs.Confederation)) { + return false + } + if !lhs.UseMultiplePaths.Equal(&(rhs.UseMultiplePaths)) { + return false + } + if !lhs.GracefulRestart.Equal(&(rhs.GracefulRestart)) { + return false + } + if len(lhs.AfiSafis) != len(rhs.AfiSafis) { + return false + } + { + lmap := make(map[string]*AfiSafi) + for i, l := range lhs.AfiSafis { + lmap[mapkey(i, string(l.Config.AfiSafiName))] = &lhs.AfiSafis[i] + } + for i, r := range rhs.AfiSafis { + if l, y := lmap[mapkey(i, string(r.Config.AfiSafiName))]; !y { + return false + } else if !r.Equal(l) { + return false + } + } + } + if !lhs.ApplyPolicy.Equal(&(rhs.ApplyPolicy)) { + return false + } + return true +} + +// struct for container bgp:bgp. +// Top-level configuration and state for the BGP router. +type Bgp struct { + // original -> bgp:global + // Global configuration for the BGP router. + Global Global `mapstructure:"global" json:"global,omitempty"` + // original -> bgp:neighbors + // Configuration for BGP neighbors. + Neighbors []Neighbor `mapstructure:"neighbors" json:"neighbors,omitempty"` + // original -> bgp:peer-groups + // Configuration for BGP peer-groups. + PeerGroups []PeerGroup `mapstructure:"peer-groups" json:"peer-groups,omitempty"` + // original -> gobgp:rpki-servers + RpkiServers []RpkiServer `mapstructure:"rpki-servers" json:"rpki-servers,omitempty"` + // original -> gobgp:bmp-servers + BmpServers []BmpServer `mapstructure:"bmp-servers" json:"bmp-servers,omitempty"` + // original -> gobgp:vrfs + Vrfs []Vrf `mapstructure:"vrfs" json:"vrfs,omitempty"` + // original -> gobgp:mrt-dump + MrtDump []Mrt `mapstructure:"mrt-dump" json:"mrt-dump,omitempty"` + // original -> gobgp:zebra + Zebra Zebra `mapstructure:"zebra" json:"zebra,omitempty"` + // original -> gobgp:collector + Collector Collector `mapstructure:"collector" json:"collector,omitempty"` + // original -> gobgp:dynamic-neighbors + DynamicNeighbors []DynamicNeighbor `mapstructure:"dynamic-neighbors" json:"dynamic-neighbors,omitempty"` +} + +func (lhs *Bgp) Equal(rhs *Bgp) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.Global.Equal(&(rhs.Global)) { + return false + } + if len(lhs.Neighbors) != len(rhs.Neighbors) { + return false + } + { + lmap := make(map[string]*Neighbor) + for i, l := range lhs.Neighbors { + lmap[mapkey(i, string(l.Config.NeighborAddress))] = &lhs.Neighbors[i] + } + for i, r := range rhs.Neighbors { + if l, y := lmap[mapkey(i, string(r.Config.NeighborAddress))]; !y { + return false + } else if !r.Equal(l) { + return false + } + } + } + if len(lhs.PeerGroups) != len(rhs.PeerGroups) { + return false + } + { + lmap := make(map[string]*PeerGroup) + for i, l := range lhs.PeerGroups { + lmap[mapkey(i, string(l.Config.PeerGroupName))] = &lhs.PeerGroups[i] + } + for i, r := range rhs.PeerGroups { + if l, y := lmap[mapkey(i, string(r.Config.PeerGroupName))]; !y { + return false + } else if !r.Equal(l) { + return false + } + } + } + if len(lhs.RpkiServers) != len(rhs.RpkiServers) { + return false + } + { + lmap := make(map[string]*RpkiServer) + for i, l := range lhs.RpkiServers { + lmap[mapkey(i, string(l.Config.Address))] = &lhs.RpkiServers[i] + } + for i, r := range rhs.RpkiServers { + if l, y := lmap[mapkey(i, string(r.Config.Address))]; !y { + return false + } else if !r.Equal(l) { + return false + } + } + } + if len(lhs.BmpServers) != len(rhs.BmpServers) { + return false + } + { + lmap := make(map[string]*BmpServer) + for i, l := range lhs.BmpServers { + lmap[mapkey(i, string(l.Config.Address))] = &lhs.BmpServers[i] + } + for i, r := range rhs.BmpServers { + if l, y := lmap[mapkey(i, string(r.Config.Address))]; !y { + return false + } else if !r.Equal(l) { + return false + } + } + } + if len(lhs.Vrfs) != len(rhs.Vrfs) { + return false + } + { + lmap := make(map[string]*Vrf) + for i, l := range lhs.Vrfs { + lmap[mapkey(i, string(l.Config.Name))] = &lhs.Vrfs[i] + } + for i, r := range rhs.Vrfs { + if l, y := lmap[mapkey(i, string(r.Config.Name))]; !y { + return false + } else if !r.Equal(l) { + return false + } + } + } + if len(lhs.MrtDump) != len(rhs.MrtDump) { + return false + } + { + lmap := make(map[string]*Mrt) + for i, l := range lhs.MrtDump { + lmap[mapkey(i, string(l.Config.FileName))] = &lhs.MrtDump[i] + } + for i, r := range rhs.MrtDump { + if l, y := lmap[mapkey(i, string(r.Config.FileName))]; !y { + return false + } else if !r.Equal(l) { + return false + } + } + } + if !lhs.Zebra.Equal(&(rhs.Zebra)) { + return false + } + if !lhs.Collector.Equal(&(rhs.Collector)) { + return false + } + if len(lhs.DynamicNeighbors) != len(rhs.DynamicNeighbors) { + return false + } + { + lmap := make(map[string]*DynamicNeighbor) + for i, l := range lhs.DynamicNeighbors { + lmap[mapkey(i, string(l.Config.Prefix))] = &lhs.DynamicNeighbors[i] + } + for i, r := range rhs.DynamicNeighbors { + if l, y := lmap[mapkey(i, string(r.Config.Prefix))]; !y { + return false + } else if !r.Equal(l) { + return false + } + } + } + return true +} + +// struct for container gobgp:set-large-community-method. +type SetLargeCommunityMethod struct { + // original -> gobgp:communities + CommunitiesList []string `mapstructure:"communities-list" json:"communities-list,omitempty"` +} + +func (lhs *SetLargeCommunityMethod) Equal(rhs *SetLargeCommunityMethod) bool { + if lhs == nil || rhs == nil { + return false + } + if len(lhs.CommunitiesList) != len(rhs.CommunitiesList) { + return false + } + for idx, l := range lhs.CommunitiesList { + if l != rhs.CommunitiesList[idx] { + return false + } + } + return true +} + +// struct for container gobgp:set-large-community. +type SetLargeCommunity struct { + // original -> gobgp:set-large-community-method + SetLargeCommunityMethod SetLargeCommunityMethod `mapstructure:"set-large-community-method" json:"set-large-community-method,omitempty"` + // original -> gobgp:options + Options BgpSetCommunityOptionType `mapstructure:"options" json:"options,omitempty"` +} + +func (lhs *SetLargeCommunity) Equal(rhs *SetLargeCommunity) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.SetLargeCommunityMethod.Equal(&(rhs.SetLargeCommunityMethod)) { + return false + } + if lhs.Options != rhs.Options { + return false + } + return true +} + +// struct for container bgp-pol:set-ext-community-method. +// Option to set communities using an inline list or +// reference to an existing defined set. +type SetExtCommunityMethod struct { + // original -> bgp-pol:communities + // original type is list of union + // Set the community values for the update inline with + // a list. + CommunitiesList []string `mapstructure:"communities-list" json:"communities-list,omitempty"` + // original -> bgp-pol:ext-community-set-ref + // References a defined extended community set by + // name. + ExtCommunitySetRef string `mapstructure:"ext-community-set-ref" json:"ext-community-set-ref,omitempty"` +} + +func (lhs *SetExtCommunityMethod) Equal(rhs *SetExtCommunityMethod) bool { + if lhs == nil || rhs == nil { + return false + } + if len(lhs.CommunitiesList) != len(rhs.CommunitiesList) { + return false + } + for idx, l := range lhs.CommunitiesList { + if l != rhs.CommunitiesList[idx] { + return false + } + } + if lhs.ExtCommunitySetRef != rhs.ExtCommunitySetRef { + return false + } + return true +} + +// struct for container bgp-pol:set-ext-community. +// Action to set the extended community attributes of the +// route, along with options to modify how the community is +// modified. +type SetExtCommunity struct { + // original -> bgp-pol:set-ext-community-method + // Option to set communities using an inline list or + // reference to an existing defined set. + SetExtCommunityMethod SetExtCommunityMethod `mapstructure:"set-ext-community-method" json:"set-ext-community-method,omitempty"` + // original -> bgp-pol:options + // bgp-pol:options's original type is bgp-set-community-option-type. + // options for modifying the extended community + // attribute with the specified values. These options + // apply to both methods of setting the community + // attribute. + Options string `mapstructure:"options" json:"options,omitempty"` +} + +func (lhs *SetExtCommunity) Equal(rhs *SetExtCommunity) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.SetExtCommunityMethod.Equal(&(rhs.SetExtCommunityMethod)) { + return false + } + if lhs.Options != rhs.Options { + return false + } + return true +} + +// struct for container bgp-pol:set-community-method. +// Option to set communities using an inline list or +// reference to an existing defined set. +type SetCommunityMethod struct { + // original -> bgp-pol:communities + // original type is list of union + // Set the community values for the update inline with + // a list. + CommunitiesList []string `mapstructure:"communities-list" json:"communities-list,omitempty"` + // original -> bgp-pol:community-set-ref + // References a defined community set by name. + CommunitySetRef string `mapstructure:"community-set-ref" json:"community-set-ref,omitempty"` +} + +func (lhs *SetCommunityMethod) Equal(rhs *SetCommunityMethod) bool { + if lhs == nil || rhs == nil { + return false + } + if len(lhs.CommunitiesList) != len(rhs.CommunitiesList) { + return false + } + for idx, l := range lhs.CommunitiesList { + if l != rhs.CommunitiesList[idx] { + return false + } + } + if lhs.CommunitySetRef != rhs.CommunitySetRef { + return false + } + return true +} + +// struct for container bgp-pol:set-community. +// action to set the community attributes of the route, along +// with options to modify how the community is modified. +type SetCommunity struct { + // original -> bgp-pol:set-community-method + // Option to set communities using an inline list or + // reference to an existing defined set. + SetCommunityMethod SetCommunityMethod `mapstructure:"set-community-method" json:"set-community-method,omitempty"` + // original -> bgp-pol:options + // bgp-pol:options's original type is bgp-set-community-option-type. + // Options for modifying the community attribute with + // the specified values. These options apply to both + // methods of setting the community attribute. + Options string `mapstructure:"options" json:"options,omitempty"` +} + +func (lhs *SetCommunity) Equal(rhs *SetCommunity) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.SetCommunityMethod.Equal(&(rhs.SetCommunityMethod)) { + return false + } + if lhs.Options != rhs.Options { + return false + } + return true +} + +// struct for container bgp-pol:set-as-path-prepend. +// action to prepend local AS number to the AS-path a +// specified number of times. +type SetAsPathPrepend struct { + // original -> bgp-pol:repeat-n + // number of times to prepend the local AS + // number. + RepeatN uint8 `mapstructure:"repeat-n" json:"repeat-n,omitempty"` + // original -> gobgp:as + // gobgp:as's original type is union. + // autonomous system number or 'last-as' which means + // the leftmost as number in the AS-path to be prepended. + As string `mapstructure:"as" json:"as,omitempty"` +} + +func (lhs *SetAsPathPrepend) Equal(rhs *SetAsPathPrepend) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.RepeatN != rhs.RepeatN { + return false + } + if lhs.As != rhs.As { + return false + } + return true +} + +// struct for container bgp-pol:bgp-actions. +// Definitions for policy action statements that +// change BGP-specific attributes of the route. +type BgpActions struct { + // original -> bgp-pol:set-as-path-prepend + // action to prepend local AS number to the AS-path a + // specified number of times. + SetAsPathPrepend SetAsPathPrepend `mapstructure:"set-as-path-prepend" json:"set-as-path-prepend,omitempty"` + // original -> bgp-pol:set-community + // action to set the community attributes of the route, along + // with options to modify how the community is modified. + SetCommunity SetCommunity `mapstructure:"set-community" json:"set-community,omitempty"` + // original -> bgp-pol:set-ext-community + // Action to set the extended community attributes of the + // route, along with options to modify how the community is + // modified. + SetExtCommunity SetExtCommunity `mapstructure:"set-ext-community" json:"set-ext-community,omitempty"` + // original -> bgp-pol:set-route-origin + // set the origin attribute to the specified + // value. + SetRouteOrigin BgpOriginAttrType `mapstructure:"set-route-origin" json:"set-route-origin,omitempty"` + // original -> bgp-pol:set-local-pref + // set the local pref attribute on the route + // update. + SetLocalPref uint32 `mapstructure:"set-local-pref" json:"set-local-pref,omitempty"` + // original -> bgp-pol:set-next-hop + // set the next-hop attribute in the route update. + SetNextHop BgpNextHopType `mapstructure:"set-next-hop" json:"set-next-hop,omitempty"` + // original -> bgp-pol:set-med + // set the med metric attribute in the route + // update. + SetMed BgpSetMedType `mapstructure:"set-med" json:"set-med,omitempty"` + // original -> gobgp:set-large-community + SetLargeCommunity SetLargeCommunity `mapstructure:"set-large-community" json:"set-large-community,omitempty"` +} + +func (lhs *BgpActions) Equal(rhs *BgpActions) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.SetAsPathPrepend.Equal(&(rhs.SetAsPathPrepend)) { + return false + } + if !lhs.SetCommunity.Equal(&(rhs.SetCommunity)) { + return false + } + if !lhs.SetExtCommunity.Equal(&(rhs.SetExtCommunity)) { + return false + } + if lhs.SetRouteOrigin != rhs.SetRouteOrigin { + return false + } + if lhs.SetLocalPref != rhs.SetLocalPref { + return false + } + if lhs.SetNextHop != rhs.SetNextHop { + return false + } + if lhs.SetMed != rhs.SetMed { + return false + } + if !lhs.SetLargeCommunity.Equal(&(rhs.SetLargeCommunity)) { + return false + } + return true +} + +// struct for container rpol:igp-actions. +// Actions to set IGP route attributes; these actions +// apply to multiple IGPs. +type IgpActions struct { + // original -> rpol:set-tag + // Set the tag value for OSPF or IS-IS routes. + SetTag TagType `mapstructure:"set-tag" json:"set-tag,omitempty"` +} + +func (lhs *IgpActions) Equal(rhs *IgpActions) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.SetTag != rhs.SetTag { + return false + } + return true +} + +// struct for container rpol:actions. +// Action statements for this policy +// statement. +type Actions struct { + // original -> rpol:route-disposition + // Select the final disposition for the route, either + // accept or reject. + RouteDisposition RouteDisposition `mapstructure:"route-disposition" json:"route-disposition,omitempty"` + // original -> rpol:igp-actions + // Actions to set IGP route attributes; these actions + // apply to multiple IGPs. + IgpActions IgpActions `mapstructure:"igp-actions" json:"igp-actions,omitempty"` + // original -> bgp-pol:bgp-actions + // Definitions for policy action statements that + // change BGP-specific attributes of the route. + BgpActions BgpActions `mapstructure:"bgp-actions" json:"bgp-actions,omitempty"` +} + +func (lhs *Actions) Equal(rhs *Actions) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.RouteDisposition != rhs.RouteDisposition { + return false + } + if !lhs.IgpActions.Equal(&(rhs.IgpActions)) { + return false + } + if !lhs.BgpActions.Equal(&(rhs.BgpActions)) { + return false + } + return true +} + +// struct for container gobgp:match-large-community-set. +type MatchLargeCommunitySet struct { + // original -> gobgp:large-community-set + LargeCommunitySet string `mapstructure:"large-community-set" json:"large-community-set,omitempty"` + // original -> rpol:match-set-options + // Optional parameter that governs the behaviour of the + // match operation. + MatchSetOptions MatchSetOptionsType `mapstructure:"match-set-options" json:"match-set-options,omitempty"` +} + +func (lhs *MatchLargeCommunitySet) Equal(rhs *MatchLargeCommunitySet) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.LargeCommunitySet != rhs.LargeCommunitySet { + return false + } + if lhs.MatchSetOptions != rhs.MatchSetOptions { + return false + } + return true +} + +// struct for container bgp-pol:as-path-length. +// Value and comparison operations for conditions based on the +// length of the AS path in the route update. +type AsPathLength struct { + // original -> ptypes:operator + // type of comparison to be performed. + Operator AttributeComparison `mapstructure:"operator" json:"operator,omitempty"` + // original -> ptypes:value + // value to compare with the community count. + Value uint32 `mapstructure:"value" json:"value,omitempty"` +} + +func (lhs *AsPathLength) Equal(rhs *AsPathLength) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.Operator != rhs.Operator { + return false + } + if lhs.Value != rhs.Value { + return false + } + return true +} + +// struct for container bgp-pol:community-count. +// Value and comparison operations for conditions based on the +// number of communities in the route update. +type CommunityCount struct { + // original -> ptypes:operator + // type of comparison to be performed. + Operator AttributeComparison `mapstructure:"operator" json:"operator,omitempty"` + // original -> ptypes:value + // value to compare with the community count. + Value uint32 `mapstructure:"value" json:"value,omitempty"` +} + +func (lhs *CommunityCount) Equal(rhs *CommunityCount) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.Operator != rhs.Operator { + return false + } + if lhs.Value != rhs.Value { + return false + } + return true +} + +// struct for container bgp-pol:match-as-path-set. +// Match a referenced as-path set according to the logic +// defined in the match-set-options leaf. +type MatchAsPathSet struct { + // original -> bgp-pol:as-path-set + // References a defined AS path set. + AsPathSet string `mapstructure:"as-path-set" json:"as-path-set,omitempty"` + // original -> rpol:match-set-options + // Optional parameter that governs the behaviour of the + // match operation. + MatchSetOptions MatchSetOptionsType `mapstructure:"match-set-options" json:"match-set-options,omitempty"` +} + +func (lhs *MatchAsPathSet) Equal(rhs *MatchAsPathSet) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.AsPathSet != rhs.AsPathSet { + return false + } + if lhs.MatchSetOptions != rhs.MatchSetOptions { + return false + } + return true +} + +// struct for container bgp-pol:match-ext-community-set. +// Match a referenced extended community-set according to the +// logic defined in the match-set-options leaf. +type MatchExtCommunitySet struct { + // original -> bgp-pol:ext-community-set + // References a defined extended community set. + ExtCommunitySet string `mapstructure:"ext-community-set" json:"ext-community-set,omitempty"` + // original -> rpol:match-set-options + // Optional parameter that governs the behaviour of the + // match operation. + MatchSetOptions MatchSetOptionsType `mapstructure:"match-set-options" json:"match-set-options,omitempty"` +} + +func (lhs *MatchExtCommunitySet) Equal(rhs *MatchExtCommunitySet) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.ExtCommunitySet != rhs.ExtCommunitySet { + return false + } + if lhs.MatchSetOptions != rhs.MatchSetOptions { + return false + } + return true +} + +// struct for container bgp-pol:match-community-set. +// Match a referenced community-set according to the logic +// defined in the match-set-options leaf. +type MatchCommunitySet struct { + // original -> bgp-pol:community-set + // References a defined community set. + CommunitySet string `mapstructure:"community-set" json:"community-set,omitempty"` + // original -> rpol:match-set-options + // Optional parameter that governs the behaviour of the + // match operation. + MatchSetOptions MatchSetOptionsType `mapstructure:"match-set-options" json:"match-set-options,omitempty"` +} + +func (lhs *MatchCommunitySet) Equal(rhs *MatchCommunitySet) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.CommunitySet != rhs.CommunitySet { + return false + } + if lhs.MatchSetOptions != rhs.MatchSetOptions { + return false + } + return true +} + +// struct for container bgp-pol:bgp-conditions. +// Policy conditions for matching +// BGP-specific defined sets or comparing BGP-specific +// attributes. +type BgpConditions struct { + // original -> bgp-pol:match-community-set + // Match a referenced community-set according to the logic + // defined in the match-set-options leaf. + MatchCommunitySet MatchCommunitySet `mapstructure:"match-community-set" json:"match-community-set,omitempty"` + // original -> bgp-pol:match-ext-community-set + // Match a referenced extended community-set according to the + // logic defined in the match-set-options leaf. + MatchExtCommunitySet MatchExtCommunitySet `mapstructure:"match-ext-community-set" json:"match-ext-community-set,omitempty"` + // original -> bgp-pol:match-as-path-set + // Match a referenced as-path set according to the logic + // defined in the match-set-options leaf. + MatchAsPathSet MatchAsPathSet `mapstructure:"match-as-path-set" json:"match-as-path-set,omitempty"` + // original -> bgp-pol:med-eq + // Condition to check if the received MED value is equal to + // the specified value. + MedEq uint32 `mapstructure:"med-eq" json:"med-eq,omitempty"` + // original -> bgp-pol:origin-eq + // Condition to check if the route origin is equal to the + // specified value. + OriginEq BgpOriginAttrType `mapstructure:"origin-eq" json:"origin-eq,omitempty"` + // original -> bgp-pol:next-hop-in + // original type is list of inet:ip-address + // List of next hop addresses to check for in the route + // update. + NextHopInList []string `mapstructure:"next-hop-in-list" json:"next-hop-in-list,omitempty"` + // original -> bgp-pol:afi-safi-in + // List of address families which the NLRI may be + // within. + AfiSafiInList []AfiSafiType `mapstructure:"afi-safi-in-list" json:"afi-safi-in-list,omitempty"` + // original -> bgp-pol:local-pref-eq + // Condition to check if the local pref attribute is equal to + // the specified value. + LocalPrefEq uint32 `mapstructure:"local-pref-eq" json:"local-pref-eq,omitempty"` + // original -> bgp-pol:community-count + // Value and comparison operations for conditions based on the + // number of communities in the route update. + CommunityCount CommunityCount `mapstructure:"community-count" json:"community-count,omitempty"` + // original -> bgp-pol:as-path-length + // Value and comparison operations for conditions based on the + // length of the AS path in the route update. + AsPathLength AsPathLength `mapstructure:"as-path-length" json:"as-path-length,omitempty"` + // original -> bgp-pol:route-type + // Condition to check the route type in the route update. + RouteType RouteType `mapstructure:"route-type" json:"route-type,omitempty"` + // original -> gobgp:rpki-validation-result + // specify the validation result of RPKI based on ROA as conditions. + RpkiValidationResult RpkiValidationResultType `mapstructure:"rpki-validation-result" json:"rpki-validation-result,omitempty"` + // original -> gobgp:match-large-community-set + MatchLargeCommunitySet MatchLargeCommunitySet `mapstructure:"match-large-community-set" json:"match-large-community-set,omitempty"` +} + +func (lhs *BgpConditions) Equal(rhs *BgpConditions) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.MatchCommunitySet.Equal(&(rhs.MatchCommunitySet)) { + return false + } + if !lhs.MatchExtCommunitySet.Equal(&(rhs.MatchExtCommunitySet)) { + return false + } + if !lhs.MatchAsPathSet.Equal(&(rhs.MatchAsPathSet)) { + return false + } + if lhs.MedEq != rhs.MedEq { + return false + } + if lhs.OriginEq != rhs.OriginEq { + return false + } + if len(lhs.NextHopInList) != len(rhs.NextHopInList) { + return false + } + for idx, l := range lhs.NextHopInList { + if l != rhs.NextHopInList[idx] { + return false + } + } + if len(lhs.AfiSafiInList) != len(rhs.AfiSafiInList) { + return false + } + for idx, l := range lhs.AfiSafiInList { + if l != rhs.AfiSafiInList[idx] { + return false + } + } + if lhs.LocalPrefEq != rhs.LocalPrefEq { + return false + } + if !lhs.CommunityCount.Equal(&(rhs.CommunityCount)) { + return false + } + if !lhs.AsPathLength.Equal(&(rhs.AsPathLength)) { + return false + } + if lhs.RouteType != rhs.RouteType { + return false + } + if lhs.RpkiValidationResult != rhs.RpkiValidationResult { + return false + } + if !lhs.MatchLargeCommunitySet.Equal(&(rhs.MatchLargeCommunitySet)) { + return false + } + return true +} + +// struct for container rpol:igp-conditions. +// Policy conditions for IGP attributes. +type IgpConditions struct { +} + +func (lhs *IgpConditions) Equal(rhs *IgpConditions) bool { + if lhs == nil || rhs == nil { + return false + } + return true +} + +// struct for container rpol:match-tag-set. +// Match a referenced tag set according to the logic defined +// in the match-options-set leaf. +type MatchTagSet struct { + // original -> rpol:tag-set + // References a defined tag set. + TagSet string `mapstructure:"tag-set" json:"tag-set,omitempty"` + // original -> rpol:match-set-options + // Optional parameter that governs the behaviour of the + // match operation. This leaf only supports matching on ANY + // member of the set or inverting the match. Matching on ALL is + // not supported). + MatchSetOptions MatchSetOptionsRestrictedType `mapstructure:"match-set-options" json:"match-set-options,omitempty"` +} + +func (lhs *MatchTagSet) Equal(rhs *MatchTagSet) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.TagSet != rhs.TagSet { + return false + } + if lhs.MatchSetOptions != rhs.MatchSetOptions { + return false + } + return true +} + +// struct for container rpol:match-neighbor-set. +// Match a referenced neighbor set according to the logic +// defined in the match-set-options-leaf. +type MatchNeighborSet struct { + // original -> rpol:neighbor-set + // References a defined neighbor set. + NeighborSet string `mapstructure:"neighbor-set" json:"neighbor-set,omitempty"` + // original -> rpol:match-set-options + // Optional parameter that governs the behaviour of the + // match operation. This leaf only supports matching on ANY + // member of the set or inverting the match. Matching on ALL is + // not supported). + MatchSetOptions MatchSetOptionsRestrictedType `mapstructure:"match-set-options" json:"match-set-options,omitempty"` +} + +func (lhs *MatchNeighborSet) Equal(rhs *MatchNeighborSet) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.NeighborSet != rhs.NeighborSet { + return false + } + if lhs.MatchSetOptions != rhs.MatchSetOptions { + return false + } + return true +} + +// struct for container rpol:match-prefix-set. +// Match a referenced prefix-set according to the logic +// defined in the match-set-options leaf. +type MatchPrefixSet struct { + // original -> rpol:prefix-set + // References a defined prefix set. + PrefixSet string `mapstructure:"prefix-set" json:"prefix-set,omitempty"` + // original -> rpol:match-set-options + // Optional parameter that governs the behaviour of the + // match operation. This leaf only supports matching on ANY + // member of the set or inverting the match. Matching on ALL is + // not supported). + MatchSetOptions MatchSetOptionsRestrictedType `mapstructure:"match-set-options" json:"match-set-options,omitempty"` +} + +func (lhs *MatchPrefixSet) Equal(rhs *MatchPrefixSet) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.PrefixSet != rhs.PrefixSet { + return false + } + if lhs.MatchSetOptions != rhs.MatchSetOptions { + return false + } + return true +} + +// struct for container rpol:conditions. +// Condition statements for this +// policy statement. +type Conditions struct { + // original -> rpol:call-policy + // Applies the statements from the specified policy + // definition and then returns control the current + // policy statement. Note that the called policy may + // itself call other policies (subject to + // implementation limitations). This is intended to + // provide a policy 'subroutine' capability. The + // called policy should contain an explicit or a + // default route disposition that returns an + // effective true (accept-route) or false + // (reject-route), otherwise the behavior may be + // ambiguous and implementation dependent. + CallPolicy string `mapstructure:"call-policy" json:"call-policy,omitempty"` + // original -> rpol:match-prefix-set + // Match a referenced prefix-set according to the logic + // defined in the match-set-options leaf. + MatchPrefixSet MatchPrefixSet `mapstructure:"match-prefix-set" json:"match-prefix-set,omitempty"` + // original -> rpol:match-neighbor-set + // Match a referenced neighbor set according to the logic + // defined in the match-set-options-leaf. + MatchNeighborSet MatchNeighborSet `mapstructure:"match-neighbor-set" json:"match-neighbor-set,omitempty"` + // original -> rpol:match-tag-set + // Match a referenced tag set according to the logic defined + // in the match-options-set leaf. + MatchTagSet MatchTagSet `mapstructure:"match-tag-set" json:"match-tag-set,omitempty"` + // original -> rpol:install-protocol-eq + // Condition to check the protocol / method used to install + // which installed the route into the local routing table. + InstallProtocolEq InstallProtocolType `mapstructure:"install-protocol-eq" json:"install-protocol-eq,omitempty"` + // original -> rpol:igp-conditions + // Policy conditions for IGP attributes. + IgpConditions IgpConditions `mapstructure:"igp-conditions" json:"igp-conditions,omitempty"` + // original -> bgp-pol:bgp-conditions + // Policy conditions for matching + // BGP-specific defined sets or comparing BGP-specific + // attributes. + BgpConditions BgpConditions `mapstructure:"bgp-conditions" json:"bgp-conditions,omitempty"` +} + +func (lhs *Conditions) Equal(rhs *Conditions) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.CallPolicy != rhs.CallPolicy { + return false + } + if !lhs.MatchPrefixSet.Equal(&(rhs.MatchPrefixSet)) { + return false + } + if !lhs.MatchNeighborSet.Equal(&(rhs.MatchNeighborSet)) { + return false + } + if !lhs.MatchTagSet.Equal(&(rhs.MatchTagSet)) { + return false + } + if lhs.InstallProtocolEq != rhs.InstallProtocolEq { + return false + } + if !lhs.IgpConditions.Equal(&(rhs.IgpConditions)) { + return false + } + if !lhs.BgpConditions.Equal(&(rhs.BgpConditions)) { + return false + } + return true +} + +// struct for container rpol:statement. +// Policy statements group conditions and actions +// within a policy definition. They are evaluated in +// the order specified (see the description of policy +// evaluation at the top of this module. +type Statement struct { + // original -> rpol:name + // name of the policy statement. + Name string `mapstructure:"name" json:"name,omitempty"` + // original -> rpol:conditions + // Condition statements for this + // policy statement. + Conditions Conditions `mapstructure:"conditions" json:"conditions,omitempty"` + // original -> rpol:actions + // Action statements for this policy + // statement. + Actions Actions `mapstructure:"actions" json:"actions,omitempty"` +} + +func (lhs *Statement) Equal(rhs *Statement) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.Name != rhs.Name { + return false + } + if !lhs.Conditions.Equal(&(rhs.Conditions)) { + return false + } + if !lhs.Actions.Equal(&(rhs.Actions)) { + return false + } + return true +} + +// struct for container rpol:policy-definition. +// List of top-level policy definitions, keyed by unique +// name. These policy definitions are expected to be +// referenced (by name) in policy chains specified in import +// or export configuration statements. +type PolicyDefinition struct { + // original -> rpol:name + // Name of the top-level policy definition -- this name + // is used in references to the current policy. + Name string `mapstructure:"name" json:"name,omitempty"` + // original -> rpol:statements + // Enclosing container for policy statements. + Statements []Statement `mapstructure:"statements" json:"statements,omitempty"` +} + +func (lhs *PolicyDefinition) Equal(rhs *PolicyDefinition) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.Name != rhs.Name { + return false + } + if len(lhs.Statements) != len(rhs.Statements) { + return false + } + { + lmap := make(map[string]*Statement) + for i, l := range lhs.Statements { + lmap[mapkey(i, string(l.Name))] = &lhs.Statements[i] + } + for i, r := range rhs.Statements { + if l, y := lmap[mapkey(i, string(r.Name))]; !y { + return false + } else if !r.Equal(l) { + return false + } + } + } + return true +} + +// struct for container gobgp:large-community-set. +type LargeCommunitySet struct { + // original -> gobgp:large-community-set-name + LargeCommunitySetName string `mapstructure:"large-community-set-name" json:"large-community-set-name,omitempty"` + // original -> gobgp:large-community + // extended community set member. + LargeCommunityList []string `mapstructure:"large-community-list" json:"large-community-list,omitempty"` +} + +func (lhs *LargeCommunitySet) Equal(rhs *LargeCommunitySet) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.LargeCommunitySetName != rhs.LargeCommunitySetName { + return false + } + if len(lhs.LargeCommunityList) != len(rhs.LargeCommunityList) { + return false + } + for idx, l := range lhs.LargeCommunityList { + if l != rhs.LargeCommunityList[idx] { + return false + } + } + return true +} + +// struct for container bgp-pol:as-path-set. +// Definitions for AS path sets. +type AsPathSet struct { + // original -> bgp-pol:as-path-set-name + // name of the AS path set -- this is used to reference + // the set in match conditions. + AsPathSetName string `mapstructure:"as-path-set-name" json:"as-path-set-name,omitempty"` + // original -> gobgp:as-path + // AS path expression. + AsPathList []string `mapstructure:"as-path-list" json:"as-path-list,omitempty"` +} + +func (lhs *AsPathSet) Equal(rhs *AsPathSet) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.AsPathSetName != rhs.AsPathSetName { + return false + } + if len(lhs.AsPathList) != len(rhs.AsPathList) { + return false + } + for idx, l := range lhs.AsPathList { + if l != rhs.AsPathList[idx] { + return false + } + } + return true +} + +// struct for container bgp-pol:ext-community-set. +// Definitions for extended community sets. +type ExtCommunitySet struct { + // original -> bgp-pol:ext-community-set-name + // name / label of the extended community set -- this is + // used to reference the set in match conditions. + ExtCommunitySetName string `mapstructure:"ext-community-set-name" json:"ext-community-set-name,omitempty"` + // original -> gobgp:ext-community + // extended community set member. + ExtCommunityList []string `mapstructure:"ext-community-list" json:"ext-community-list,omitempty"` +} + +func (lhs *ExtCommunitySet) Equal(rhs *ExtCommunitySet) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.ExtCommunitySetName != rhs.ExtCommunitySetName { + return false + } + if len(lhs.ExtCommunityList) != len(rhs.ExtCommunityList) { + return false + } + for idx, l := range lhs.ExtCommunityList { + if l != rhs.ExtCommunityList[idx] { + return false + } + } + return true +} + +// struct for container bgp-pol:community-set. +// Definitions for community sets. +type CommunitySet struct { + // original -> bgp-pol:community-set-name + // name / label of the community set -- this is used to + // reference the set in match conditions. + CommunitySetName string `mapstructure:"community-set-name" json:"community-set-name,omitempty"` + // original -> gobgp:community + // community set member. + CommunityList []string `mapstructure:"community-list" json:"community-list,omitempty"` +} + +func (lhs *CommunitySet) Equal(rhs *CommunitySet) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.CommunitySetName != rhs.CommunitySetName { + return false + } + if len(lhs.CommunityList) != len(rhs.CommunityList) { + return false + } + for idx, l := range lhs.CommunityList { + if l != rhs.CommunityList[idx] { + return false + } + } + return true +} + +// struct for container bgp-pol:bgp-defined-sets. +// BGP-related set definitions for policy match conditions. +type BgpDefinedSets struct { + // original -> bgp-pol:community-sets + // Enclosing container for community sets. + CommunitySets []CommunitySet `mapstructure:"community-sets" json:"community-sets,omitempty"` + // original -> bgp-pol:ext-community-sets + // Enclosing container for extended community sets. + ExtCommunitySets []ExtCommunitySet `mapstructure:"ext-community-sets" json:"ext-community-sets,omitempty"` + // original -> bgp-pol:as-path-sets + // Enclosing container for AS path sets. + AsPathSets []AsPathSet `mapstructure:"as-path-sets" json:"as-path-sets,omitempty"` + // original -> gobgp:large-community-sets + LargeCommunitySets []LargeCommunitySet `mapstructure:"large-community-sets" json:"large-community-sets,omitempty"` +} + +func (lhs *BgpDefinedSets) Equal(rhs *BgpDefinedSets) bool { + if lhs == nil || rhs == nil { + return false + } + if len(lhs.CommunitySets) != len(rhs.CommunitySets) { + return false + } + { + lmap := make(map[string]*CommunitySet) + for i, l := range lhs.CommunitySets { + lmap[mapkey(i, string(l.CommunitySetName))] = &lhs.CommunitySets[i] + } + for i, r := range rhs.CommunitySets { + if l, y := lmap[mapkey(i, string(r.CommunitySetName))]; !y { + return false + } else if !r.Equal(l) { + return false + } + } + } + if len(lhs.ExtCommunitySets) != len(rhs.ExtCommunitySets) { + return false + } + { + lmap := make(map[string]*ExtCommunitySet) + for i, l := range lhs.ExtCommunitySets { + lmap[mapkey(i, string(l.ExtCommunitySetName))] = &lhs.ExtCommunitySets[i] + } + for i, r := range rhs.ExtCommunitySets { + if l, y := lmap[mapkey(i, string(r.ExtCommunitySetName))]; !y { + return false + } else if !r.Equal(l) { + return false + } + } + } + if len(lhs.AsPathSets) != len(rhs.AsPathSets) { + return false + } + { + lmap := make(map[string]*AsPathSet) + for i, l := range lhs.AsPathSets { + lmap[mapkey(i, string(l.AsPathSetName))] = &lhs.AsPathSets[i] + } + for i, r := range rhs.AsPathSets { + if l, y := lmap[mapkey(i, string(r.AsPathSetName))]; !y { + return false + } else if !r.Equal(l) { + return false + } + } + } + if len(lhs.LargeCommunitySets) != len(rhs.LargeCommunitySets) { + return false + } + { + lmap := make(map[string]*LargeCommunitySet) + for i, l := range lhs.LargeCommunitySets { + lmap[mapkey(i, string(l.LargeCommunitySetName))] = &lhs.LargeCommunitySets[i] + } + for i, r := range rhs.LargeCommunitySets { + if l, y := lmap[mapkey(i, string(r.LargeCommunitySetName))]; !y { + return false + } else if !r.Equal(l) { + return false + } + } + } + return true +} + +// struct for container rpol:tag. +// list of tags that are part of the tag set. +type Tag struct { + // original -> rpol:value + // Value of the tag set member. + Value TagType `mapstructure:"value" json:"value,omitempty"` +} + +func (lhs *Tag) Equal(rhs *Tag) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.Value != rhs.Value { + return false + } + return true +} + +// struct for container rpol:tag-set. +// Definitions for tag sets. +type TagSet struct { + // original -> rpol:tag-set-name + // name / label of the tag set -- this is used to reference + // the set in match conditions. + TagSetName string `mapstructure:"tag-set-name" json:"tag-set-name,omitempty"` + // original -> rpol:tag + // list of tags that are part of the tag set. + TagList []Tag `mapstructure:"tag-list" json:"tag-list,omitempty"` +} + +func (lhs *TagSet) Equal(rhs *TagSet) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.TagSetName != rhs.TagSetName { + return false + } + if len(lhs.TagList) != len(rhs.TagList) { + return false + } + { + lmap := make(map[string]*Tag) + for i, l := range lhs.TagList { + lmap[mapkey(i, string(l.Value))] = &lhs.TagList[i] + } + for i, r := range rhs.TagList { + if l, y := lmap[mapkey(i, string(r.Value))]; !y { + return false + } else if !r.Equal(l) { + return false + } + } + } + return true +} + +// struct for container rpol:neighbor-set. +// Definitions for neighbor sets. +type NeighborSet struct { + // original -> rpol:neighbor-set-name + // name / label of the neighbor set -- this is used to + // reference the set in match conditions. + NeighborSetName string `mapstructure:"neighbor-set-name" json:"neighbor-set-name,omitempty"` + // original -> gobgp:neighbor-info + // original type is list of inet:ip-address + // neighbor ip address or prefix. + NeighborInfoList []string `mapstructure:"neighbor-info-list" json:"neighbor-info-list,omitempty"` +} + +func (lhs *NeighborSet) Equal(rhs *NeighborSet) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.NeighborSetName != rhs.NeighborSetName { + return false + } + if len(lhs.NeighborInfoList) != len(rhs.NeighborInfoList) { + return false + } + for idx, l := range lhs.NeighborInfoList { + if l != rhs.NeighborInfoList[idx] { + return false + } + } + return true +} + +// struct for container rpol:prefix. +// List of prefix expressions that are part of the set. +type Prefix struct { + // original -> rpol:ip-prefix + // rpol:ip-prefix's original type is inet:ip-prefix. + // The prefix member in CIDR notation -- while the + // prefix may be either IPv4 or IPv6, most + // implementations require all members of the prefix set + // to be the same address family. Mixing address types in + // the same prefix set is likely to cause an error. + IpPrefix string `mapstructure:"ip-prefix" json:"ip-prefix,omitempty"` + // original -> rpol:masklength-range + // Defines a range for the masklength, or 'exact' if + // the prefix has an exact length. + // + // Example: 10.3.192.0/21 through 10.3.192.0/24 would be + // expressed as prefix: 10.3.192.0/21, + // masklength-range: 21..24. + // + // Example: 10.3.192.0/21 would be expressed as + // prefix: 10.3.192.0/21, + // masklength-range: exact. + MasklengthRange string `mapstructure:"masklength-range" json:"masklength-range,omitempty"` +} + +func (lhs *Prefix) Equal(rhs *Prefix) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.IpPrefix != rhs.IpPrefix { + return false + } + if lhs.MasklengthRange != rhs.MasklengthRange { + return false + } + return true +} + +// struct for container rpol:prefix-set. +// List of the defined prefix sets. +type PrefixSet struct { + // original -> rpol:prefix-set-name + // name / label of the prefix set -- this is used to + // reference the set in match conditions. + PrefixSetName string `mapstructure:"prefix-set-name" json:"prefix-set-name,omitempty"` + // original -> rpol:prefix + // List of prefix expressions that are part of the set. + PrefixList []Prefix `mapstructure:"prefix-list" json:"prefix-list,omitempty"` +} + +func (lhs *PrefixSet) Equal(rhs *PrefixSet) bool { + if lhs == nil || rhs == nil { + return false + } + if lhs.PrefixSetName != rhs.PrefixSetName { + return false + } + if len(lhs.PrefixList) != len(rhs.PrefixList) { + return false + } + { + lmap := make(map[string]*Prefix) + for i, l := range lhs.PrefixList { + lmap[mapkey(i, string(l.IpPrefix+l.MasklengthRange))] = &lhs.PrefixList[i] + } + for i, r := range rhs.PrefixList { + if l, y := lmap[mapkey(i, string(r.IpPrefix+r.MasklengthRange))]; !y { + return false + } else if !r.Equal(l) { + return false + } + } + } + return true +} + +// struct for container rpol:defined-sets. +// Predefined sets of attributes used in policy match +// statements. +type DefinedSets struct { + // original -> rpol:prefix-sets + // Enclosing container for defined prefix sets for matching. + PrefixSets []PrefixSet `mapstructure:"prefix-sets" json:"prefix-sets,omitempty"` + // original -> rpol:neighbor-sets + // Enclosing container for defined neighbor sets for matching. + NeighborSets []NeighborSet `mapstructure:"neighbor-sets" json:"neighbor-sets,omitempty"` + // original -> rpol:tag-sets + // Enclosing container for defined tag sets for matching. + TagSets []TagSet `mapstructure:"tag-sets" json:"tag-sets,omitempty"` + // original -> bgp-pol:bgp-defined-sets + // BGP-related set definitions for policy match conditions. + BgpDefinedSets BgpDefinedSets `mapstructure:"bgp-defined-sets" json:"bgp-defined-sets,omitempty"` +} + +func (lhs *DefinedSets) Equal(rhs *DefinedSets) bool { + if lhs == nil || rhs == nil { + return false + } + if len(lhs.PrefixSets) != len(rhs.PrefixSets) { + return false + } + { + lmap := make(map[string]*PrefixSet) + for i, l := range lhs.PrefixSets { + lmap[mapkey(i, string(l.PrefixSetName))] = &lhs.PrefixSets[i] + } + for i, r := range rhs.PrefixSets { + if l, y := lmap[mapkey(i, string(r.PrefixSetName))]; !y { + return false + } else if !r.Equal(l) { + return false + } + } + } + if len(lhs.NeighborSets) != len(rhs.NeighborSets) { + return false + } + { + lmap := make(map[string]*NeighborSet) + for i, l := range lhs.NeighborSets { + lmap[mapkey(i, string(l.NeighborSetName))] = &lhs.NeighborSets[i] + } + for i, r := range rhs.NeighborSets { + if l, y := lmap[mapkey(i, string(r.NeighborSetName))]; !y { + return false + } else if !r.Equal(l) { + return false + } + } + } + if len(lhs.TagSets) != len(rhs.TagSets) { + return false + } + { + lmap := make(map[string]*TagSet) + for i, l := range lhs.TagSets { + lmap[mapkey(i, string(l.TagSetName))] = &lhs.TagSets[i] + } + for i, r := range rhs.TagSets { + if l, y := lmap[mapkey(i, string(r.TagSetName))]; !y { + return false + } else if !r.Equal(l) { + return false + } + } + } + if !lhs.BgpDefinedSets.Equal(&(rhs.BgpDefinedSets)) { + return false + } + return true +} + +// struct for container rpol:routing-policy. +// top-level container for all routing policy configuration. +type RoutingPolicy struct { + // original -> rpol:defined-sets + // Predefined sets of attributes used in policy match + // statements. + DefinedSets DefinedSets `mapstructure:"defined-sets" json:"defined-sets,omitempty"` + // original -> rpol:policy-definitions + // Enclosing container for the list of top-level policy + // definitions. + PolicyDefinitions []PolicyDefinition `mapstructure:"policy-definitions" json:"policy-definitions,omitempty"` +} + +func (lhs *RoutingPolicy) Equal(rhs *RoutingPolicy) bool { + if lhs == nil || rhs == nil { + return false + } + if !lhs.DefinedSets.Equal(&(rhs.DefinedSets)) { + return false + } + if len(lhs.PolicyDefinitions) != len(rhs.PolicyDefinitions) { + return false + } + { + lmap := make(map[string]*PolicyDefinition) + for i, l := range lhs.PolicyDefinitions { + lmap[mapkey(i, string(l.Name))] = &lhs.PolicyDefinitions[i] + } + for i, r := range rhs.PolicyDefinitions { + if l, y := lmap[mapkey(i, string(r.Name))]; !y { + return false + } else if !r.Equal(l) { + return false + } + } + } + return true +} diff --git a/internal/pkg/config/bgp_configs_test.go b/internal/pkg/config/bgp_configs_test.go new file mode 100644 index 00000000..6d6853a7 --- /dev/null +++ b/internal/pkg/config/bgp_configs_test.go @@ -0,0 +1,113 @@ +// 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 config + +import ( + "bufio" + "os" + "path" + "runtime" + "strings" + "testing" + + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" +) + +func TestEqual(t *testing.T) { + assert := assert.New(t) + p1 := Prefix{ + IpPrefix: "192.168.0.0", + MasklengthRange: "24..32", + } + p2 := Prefix{ + IpPrefix: "192.168.0.0", + MasklengthRange: "24..32", + } + assert.True(p1.Equal(&p2)) + assert.False(p1.Equal(nil)) + var p3 *Prefix + assert.False(p3.Equal(&p1)) + p3 = &Prefix{ + IpPrefix: "192.168.0.0", + MasklengthRange: "24..32", + } + assert.True(p3.Equal(&p1)) + p3.IpPrefix = "10.10.0.0" + assert.False(p3.Equal(&p1)) + ps1 := PrefixSet{ + PrefixSetName: "ps", + PrefixList: []Prefix{p1, p2}, + } + ps2 := PrefixSet{ + PrefixSetName: "ps", + PrefixList: []Prefix{p2, p1}, + } + assert.True(ps1.Equal(&ps2)) + ps2.PrefixSetName = "ps2" + assert.False(ps1.Equal(&ps2)) +} + +func extractTomlFromMarkdown(fileMd string, fileToml string) error { + fMd, err := os.Open(fileMd) + if err != nil { + return err + } + defer fMd.Close() + + fToml, err := os.Create(fileToml) + if err != nil { + return err + } + defer fToml.Close() + + isBody := false + scanner := bufio.NewScanner(fMd) + fTomlWriter := bufio.NewWriter(fToml) + for scanner.Scan() { + if curText := scanner.Text(); strings.HasPrefix(curText, "```toml") { + isBody = true + } else if strings.HasPrefix(curText, "```") { + isBody = false + } else if isBody { + if _, err := fTomlWriter.WriteString(curText + "\n"); err != nil { + return err + } + } + } + + fTomlWriter.Flush() + return scanner.Err() +} + +func TestConfigExample(t *testing.T) { + assert := assert.New(t) + + _, f, _, _ := runtime.Caller(0) + fileMd := path.Join(path.Dir(f), "../../../docs/sources/configuration.md") + fileToml := "/tmp/gobgpd.example.toml" + assert.NoError(extractTomlFromMarkdown(fileMd, fileToml)) + defer os.Remove(fileToml) + + format := detectConfigFileType(fileToml, "") + c := &BgpConfigSet{} + v := viper.New() + v.SetConfigFile(fileToml) + v.SetConfigType(format) + assert.NoError(v.ReadInConfig()) + assert.NoError(v.UnmarshalExact(c)) + assert.NoError(setDefaultConfigValuesWithViper(v, c)) +} diff --git a/internal/pkg/config/default.go b/internal/pkg/config/default.go new file mode 100644 index 00000000..6ab1eddc --- /dev/null +++ b/internal/pkg/config/default.go @@ -0,0 +1,524 @@ +package config + +import ( + "encoding/binary" + "fmt" + "math" + "net" + "reflect" + "strconv" + + "github.com/osrg/gobgp/pkg/packet/bgp" + "github.com/osrg/gobgp/pkg/packet/bmp" + "github.com/osrg/gobgp/pkg/packet/rtr" + "github.com/spf13/viper" +) + +const ( + DEFAULT_HOLDTIME = 90 + DEFAULT_IDLE_HOLDTIME_AFTER_RESET = 30 + DEFAULT_CONNECT_RETRY = 120 +) + +var forcedOverwrittenConfig = []string{ + "neighbor.config.peer-as", + "neighbor.timers.config.minimum-advertisement-interval", +} + +var configuredFields map[string]interface{} + +func RegisterConfiguredFields(addr string, n interface{}) { + if configuredFields == nil { + configuredFields = make(map[string]interface{}) + } + configuredFields[addr] = n +} + +func defaultAfiSafi(typ AfiSafiType, enable bool) AfiSafi { + return AfiSafi{ + Config: AfiSafiConfig{ + AfiSafiName: typ, + Enabled: enable, + }, + State: AfiSafiState{ + AfiSafiName: typ, + Family: bgp.AddressFamilyValueMap[string(typ)], + }, + } +} + +func SetDefaultNeighborConfigValues(n *Neighbor, pg *PeerGroup, g *Global) error { + // Determines this function is called against the same Neighbor struct, + // and if already called, returns immediately. + if n.State.LocalAs != 0 { + return nil + } + + return setDefaultNeighborConfigValuesWithViper(nil, n, g, pg) +} + +func setDefaultNeighborConfigValuesWithViper(v *viper.Viper, n *Neighbor, g *Global, pg *PeerGroup) error { + if n == nil { + return fmt.Errorf("neighbor config is nil") + } + if g == nil { + return fmt.Errorf("global config is nil") + } + + if v == nil { + v = viper.New() + } + + if pg != nil { + if err := OverwriteNeighborConfigWithPeerGroup(n, pg); err != nil { + return err + } + } + + if n.Config.LocalAs == 0 { + n.Config.LocalAs = g.Config.As + if !g.Confederation.Config.Enabled || n.IsConfederation(g) { + n.Config.LocalAs = g.Config.As + } else { + n.Config.LocalAs = g.Confederation.Config.Identifier + } + } + n.State.LocalAs = n.Config.LocalAs + + if n.Config.PeerAs != n.Config.LocalAs { + n.Config.PeerType = PEER_TYPE_EXTERNAL + n.State.PeerType = PEER_TYPE_EXTERNAL + n.State.RemovePrivateAs = n.Config.RemovePrivateAs + n.AsPathOptions.State.ReplacePeerAs = n.AsPathOptions.Config.ReplacePeerAs + } else { + n.Config.PeerType = PEER_TYPE_INTERNAL + n.State.PeerType = PEER_TYPE_INTERNAL + if string(n.Config.RemovePrivateAs) != "" { + return fmt.Errorf("can't set remove-private-as for iBGP peer") + } + if n.AsPathOptions.Config.ReplacePeerAs { + return fmt.Errorf("can't set replace-peer-as for iBGP peer") + } + } + + if n.State.NeighborAddress == "" { + n.State.NeighborAddress = n.Config.NeighborAddress + } + + n.State.PeerAs = n.Config.PeerAs + n.AsPathOptions.State.AllowOwnAs = n.AsPathOptions.Config.AllowOwnAs + + if !v.IsSet("neighbor.error-handling.config.treat-as-withdraw") { + n.ErrorHandling.Config.TreatAsWithdraw = true + } + + if !v.IsSet("neighbor.timers.config.connect-retry") && n.Timers.Config.ConnectRetry == 0 { + n.Timers.Config.ConnectRetry = float64(DEFAULT_CONNECT_RETRY) + } + if !v.IsSet("neighbor.timers.config.hold-time") && n.Timers.Config.HoldTime == 0 { + n.Timers.Config.HoldTime = float64(DEFAULT_HOLDTIME) + } + if !v.IsSet("neighbor.timers.config.keepalive-interval") && n.Timers.Config.KeepaliveInterval == 0 { + n.Timers.Config.KeepaliveInterval = n.Timers.Config.HoldTime / 3 + } + if !v.IsSet("neighbor.timers.config.idle-hold-time-after-reset") && n.Timers.Config.IdleHoldTimeAfterReset == 0 { + n.Timers.Config.IdleHoldTimeAfterReset = float64(DEFAULT_IDLE_HOLDTIME_AFTER_RESET) + } + + if n.Config.NeighborInterface != "" { + if n.RouteServer.Config.RouteServerClient { + return fmt.Errorf("configuring route server client as unnumbered peer is not supported") + } + addr, err := GetIPv6LinkLocalNeighborAddress(n.Config.NeighborInterface) + if err != nil { + return err + } + n.State.NeighborAddress = addr + } + + if n.Transport.Config.LocalAddress == "" { + if n.State.NeighborAddress == "" { + return fmt.Errorf("no neighbor address/interface specified") + } + ipAddr, err := net.ResolveIPAddr("ip", n.State.NeighborAddress) + if err != nil { + return err + } + localAddress := "0.0.0.0" + if ipAddr.IP.To4() == nil { + localAddress = "::" + if ipAddr.Zone != "" { + localAddress, err = getIPv6LinkLocalAddress(ipAddr.Zone) + if err != nil { + return err + } + } + } + n.Transport.Config.LocalAddress = localAddress + } + + if len(n.AfiSafis) == 0 { + if n.Config.NeighborInterface != "" { + n.AfiSafis = []AfiSafi{ + defaultAfiSafi(AFI_SAFI_TYPE_IPV4_UNICAST, true), + defaultAfiSafi(AFI_SAFI_TYPE_IPV6_UNICAST, true), + } + } else if ipAddr, err := net.ResolveIPAddr("ip", n.State.NeighborAddress); err != nil { + return fmt.Errorf("invalid neighbor address: %s", n.State.NeighborAddress) + } else if ipAddr.IP.To4() != nil { + n.AfiSafis = []AfiSafi{defaultAfiSafi(AFI_SAFI_TYPE_IPV4_UNICAST, true)} + } else { + n.AfiSafis = []AfiSafi{defaultAfiSafi(AFI_SAFI_TYPE_IPV6_UNICAST, true)} + } + for i := range n.AfiSafis { + n.AfiSafis[i].AddPaths.Config.Receive = n.AddPaths.Config.Receive + n.AfiSafis[i].AddPaths.State.Receive = n.AddPaths.Config.Receive + n.AfiSafis[i].AddPaths.Config.SendMax = n.AddPaths.Config.SendMax + n.AfiSafis[i].AddPaths.State.SendMax = n.AddPaths.Config.SendMax + } + } else { + afs, err := extractArray(v.Get("neighbor.afi-safis")) + if err != nil { + return err + } + for i := range n.AfiSafis { + vv := viper.New() + if len(afs) > i { + vv.Set("afi-safi", afs[i]) + } + rf, err := bgp.GetRouteFamily(string(n.AfiSafis[i].Config.AfiSafiName)) + if err != nil { + return err + } + n.AfiSafis[i].State.Family = rf + n.AfiSafis[i].State.AfiSafiName = n.AfiSafis[i].Config.AfiSafiName + if !vv.IsSet("afi-safi.config.enabled") { + n.AfiSafis[i].Config.Enabled = true + } + n.AfiSafis[i].MpGracefulRestart.State.Enabled = n.AfiSafis[i].MpGracefulRestart.Config.Enabled + if !vv.IsSet("afi-safi.add-paths.config.receive") { + n.AfiSafis[i].AddPaths.Config.Receive = n.AddPaths.Config.Receive + } + n.AfiSafis[i].AddPaths.State.Receive = n.AfiSafis[i].AddPaths.Config.Receive + if !vv.IsSet("afi-safi.add-paths.config.send-max") { + n.AfiSafis[i].AddPaths.Config.SendMax = n.AddPaths.Config.SendMax + } + n.AfiSafis[i].AddPaths.State.SendMax = n.AfiSafis[i].AddPaths.Config.SendMax + } + } + + n.State.Description = n.Config.Description + n.State.AdminDown = n.Config.AdminDown + + if n.GracefulRestart.Config.Enabled { + if !v.IsSet("neighbor.graceful-restart.config.restart-time") && n.GracefulRestart.Config.RestartTime == 0 { + // RFC 4724 4. Operation + // A suggested default for the Restart Time is a value less than or + // equal to the HOLDTIME carried in the OPEN. + n.GracefulRestart.Config.RestartTime = uint16(n.Timers.Config.HoldTime) + } + if !v.IsSet("neighbor.graceful-restart.config.deferral-time") && n.GracefulRestart.Config.DeferralTime == 0 { + // RFC 4724 4.1. Procedures for the Restarting Speaker + // The value of this timer should be large + // enough, so as to provide all the peers of the Restarting Speaker with + // enough time to send all the routes to the Restarting Speaker + n.GracefulRestart.Config.DeferralTime = uint16(360) + } + } + + if n.EbgpMultihop.Config.Enabled { + if n.TtlSecurity.Config.Enabled { + return fmt.Errorf("ebgp-multihop and ttl-security are mututally exclusive") + } + if n.EbgpMultihop.Config.MultihopTtl == 0 { + n.EbgpMultihop.Config.MultihopTtl = 255 + } + } else if n.TtlSecurity.Config.Enabled { + if n.TtlSecurity.Config.TtlMin == 0 { + n.TtlSecurity.Config.TtlMin = 255 + } + } + + if n.RouteReflector.Config.RouteReflectorClient { + if n.RouteReflector.Config.RouteReflectorClusterId == "" { + n.RouteReflector.State.RouteReflectorClusterId = RrClusterIdType(g.Config.RouterId) + } else { + id := string(n.RouteReflector.Config.RouteReflectorClusterId) + if ip := net.ParseIP(id).To4(); ip != nil { + n.RouteReflector.State.RouteReflectorClusterId = n.RouteReflector.Config.RouteReflectorClusterId + } else if num, err := strconv.ParseUint(id, 10, 32); err == nil { + ip = make(net.IP, 4) + binary.BigEndian.PutUint32(ip, uint32(num)) + n.RouteReflector.State.RouteReflectorClusterId = RrClusterIdType(ip.String()) + } else { + return fmt.Errorf("route-reflector-cluster-id should be specified as IPv4 address or 32-bit unsigned integer") + } + } + } + + return nil +} + +func SetDefaultGlobalConfigValues(g *Global) error { + if len(g.AfiSafis) == 0 { + g.AfiSafis = []AfiSafi{} + for k := range AfiSafiTypeToIntMap { + g.AfiSafis = append(g.AfiSafis, defaultAfiSafi(k, true)) + } + } + + if g.Config.Port == 0 { + g.Config.Port = bgp.BGP_PORT + } + + if len(g.Config.LocalAddressList) == 0 { + g.Config.LocalAddressList = []string{"0.0.0.0", "::"} + } + return nil +} + +func setDefaultVrfConfigValues(v *Vrf) error { + if v == nil { + return fmt.Errorf("cannot set default values for nil vrf config") + } + + if v.Config.Name == "" { + return fmt.Errorf("specify vrf name") + } + + _, err := bgp.ParseRouteDistinguisher(v.Config.Rd) + if err != nil { + return fmt.Errorf("invalid rd for vrf %s: %s", v.Config.Name, v.Config.Rd) + } + + if len(v.Config.ImportRtList) == 0 { + v.Config.ImportRtList = v.Config.BothRtList + } + for _, rtString := range v.Config.ImportRtList { + _, err := bgp.ParseRouteTarget(rtString) + if err != nil { + return fmt.Errorf("invalid import rt for vrf %s: %s", v.Config.Name, rtString) + } + } + + if len(v.Config.ExportRtList) == 0 { + v.Config.ExportRtList = v.Config.BothRtList + } + for _, rtString := range v.Config.ExportRtList { + _, err := bgp.ParseRouteTarget(rtString) + if err != nil { + return fmt.Errorf("invalid export rt for vrf %s: %s", v.Config.Name, rtString) + } + } + + return nil +} + +func SetDefaultConfigValues(b *BgpConfigSet) error { + return setDefaultConfigValuesWithViper(nil, b) +} + +func setDefaultPolicyConfigValuesWithViper(v *viper.Viper, p *PolicyDefinition) error { + stmts, err := extractArray(v.Get("policy.statements")) + if err != nil { + return err + } + for i := range p.Statements { + vv := viper.New() + if len(stmts) > i { + vv.Set("statement", stmts[i]) + } + if !vv.IsSet("statement.actions.route-disposition") { + p.Statements[i].Actions.RouteDisposition = ROUTE_DISPOSITION_NONE + } + } + return nil +} + +func setDefaultConfigValuesWithViper(v *viper.Viper, b *BgpConfigSet) error { + if v == nil { + v = viper.New() + } + + if err := SetDefaultGlobalConfigValues(&b.Global); err != nil { + return err + } + + for idx, server := range b.BmpServers { + if server.Config.Port == 0 { + server.Config.Port = bmp.BMP_DEFAULT_PORT + } + if server.Config.RouteMonitoringPolicy == "" { + server.Config.RouteMonitoringPolicy = BMP_ROUTE_MONITORING_POLICY_TYPE_PRE_POLICY + } + // statistics-timeout is uint16 value and implicitly less than 65536 + if server.Config.StatisticsTimeout != 0 && server.Config.StatisticsTimeout < 15 { + return fmt.Errorf("too small statistics-timeout value: %d", server.Config.StatisticsTimeout) + } + b.BmpServers[idx] = server + } + + vrfNames := make(map[string]struct{}) + vrfIDs := make(map[uint32]struct{}) + for idx, vrf := range b.Vrfs { + if err := setDefaultVrfConfigValues(&vrf); err != nil { + return err + } + + if _, ok := vrfNames[vrf.Config.Name]; ok { + return fmt.Errorf("duplicated vrf name: %s", vrf.Config.Name) + } + vrfNames[vrf.Config.Name] = struct{}{} + + if vrf.Config.Id != 0 { + if _, ok := vrfIDs[vrf.Config.Id]; ok { + return fmt.Errorf("duplicated vrf id: %d", vrf.Config.Id) + } + vrfIDs[vrf.Config.Id] = struct{}{} + } + + b.Vrfs[idx] = vrf + } + // Auto assign VRF identifier + for idx, vrf := range b.Vrfs { + if vrf.Config.Id == 0 { + for id := uint32(1); id < math.MaxUint32; id++ { + if _, ok := vrfIDs[id]; !ok { + vrf.Config.Id = id + vrfIDs[id] = struct{}{} + break + } + } + } + b.Vrfs[idx] = vrf + } + + if b.Zebra.Config.Url == "" { + b.Zebra.Config.Url = "unix:/var/run/quagga/zserv.api" + } + if b.Zebra.Config.Version < 2 { + b.Zebra.Config.Version = 2 + } else if b.Zebra.Config.Version > 4 { + b.Zebra.Config.Version = 4 + } + if !v.IsSet("zebra.config.nexthop-trigger-enable") && !b.Zebra.Config.NexthopTriggerEnable && b.Zebra.Config.Version > 2 { + b.Zebra.Config.NexthopTriggerEnable = true + } + if b.Zebra.Config.NexthopTriggerDelay == 0 { + b.Zebra.Config.NexthopTriggerDelay = 5 + } + + list, err := extractArray(v.Get("neighbors")) + if err != nil { + return err + } + + for idx, n := range b.Neighbors { + vv := viper.New() + if len(list) > idx { + vv.Set("neighbor", list[idx]) + } + + pg, err := b.getPeerGroup(n.Config.PeerGroup) + if err != nil { + return nil + } + + if pg != nil { + identifier := vv.Get("neighbor.config.neighbor-address") + if identifier == nil { + identifier = vv.Get("neighbor.config.neighbor-interface") + } + RegisterConfiguredFields(identifier.(string), list[idx]) + } + + if err := setDefaultNeighborConfigValuesWithViper(vv, &n, &b.Global, pg); err != nil { + return err + } + b.Neighbors[idx] = n + } + + for _, d := range b.DynamicNeighbors { + if err := d.validate(b); err != nil { + return err + } + } + + for idx, r := range b.RpkiServers { + if r.Config.Port == 0 { + b.RpkiServers[idx].Config.Port = rtr.RPKI_DEFAULT_PORT + } + } + + list, err = extractArray(v.Get("policy-definitions")) + if err != nil { + return err + } + + for idx, p := range b.PolicyDefinitions { + vv := viper.New() + if len(list) > idx { + vv.Set("policy", list[idx]) + } + if err := setDefaultPolicyConfigValuesWithViper(vv, &p); err != nil { + return err + } + b.PolicyDefinitions[idx] = p + } + + return nil +} + +func OverwriteNeighborConfigWithPeerGroup(c *Neighbor, pg *PeerGroup) error { + v := viper.New() + + val, ok := configuredFields[c.Config.NeighborAddress] + if ok { + v.Set("neighbor", val) + } else { + v.Set("neighbor.config.peer-group", c.Config.PeerGroup) + } + + overwriteConfig(&c.Config, &pg.Config, "neighbor.config", v) + overwriteConfig(&c.Timers.Config, &pg.Timers.Config, "neighbor.timers.config", v) + overwriteConfig(&c.Transport.Config, &pg.Transport.Config, "neighbor.transport.config", v) + overwriteConfig(&c.ErrorHandling.Config, &pg.ErrorHandling.Config, "neighbor.error-handling.config", v) + overwriteConfig(&c.LoggingOptions.Config, &pg.LoggingOptions.Config, "neighbor.logging-options.config", v) + overwriteConfig(&c.EbgpMultihop.Config, &pg.EbgpMultihop.Config, "neighbor.ebgp-multihop.config", v) + overwriteConfig(&c.RouteReflector.Config, &pg.RouteReflector.Config, "neighbor.route-reflector.config", v) + overwriteConfig(&c.AsPathOptions.Config, &pg.AsPathOptions.Config, "neighbor.as-path-options.config", v) + overwriteConfig(&c.AddPaths.Config, &pg.AddPaths.Config, "neighbor.add-paths.config", v) + overwriteConfig(&c.GracefulRestart.Config, &pg.GracefulRestart.Config, "neighbor.gradeful-restart.config", v) + overwriteConfig(&c.ApplyPolicy.Config, &pg.ApplyPolicy.Config, "neighbor.apply-policy.config", v) + overwriteConfig(&c.UseMultiplePaths.Config, &pg.UseMultiplePaths.Config, "neighbor.use-multiple-paths.config", v) + overwriteConfig(&c.RouteServer.Config, &pg.RouteServer.Config, "neighbor.route-server.config", v) + overwriteConfig(&c.TtlSecurity.Config, &pg.TtlSecurity.Config, "neighbor.ttl-security.config", v) + + if !v.IsSet("neighbor.afi-safis") { + c.AfiSafis = append(c.AfiSafis, pg.AfiSafis...) + } + + return nil +} + +func overwriteConfig(c, pg interface{}, tagPrefix string, v *viper.Viper) { + nValue := reflect.Indirect(reflect.ValueOf(c)) + nType := reflect.Indirect(nValue).Type() + pgValue := reflect.Indirect(reflect.ValueOf(pg)) + pgType := reflect.Indirect(pgValue).Type() + + for i := 0; i < pgType.NumField(); i++ { + field := pgType.Field(i).Name + tag := tagPrefix + "." + nType.Field(i).Tag.Get("mapstructure") + if func() bool { + for _, t := range forcedOverwrittenConfig { + if t == tag { + return true + } + } + return false + }() || !v.IsSet(tag) { + nValue.FieldByName(field).Set(pgValue.FieldByName(field)) + } + } +} diff --git a/internal/pkg/config/default_linux.go b/internal/pkg/config/default_linux.go new file mode 100644 index 00000000..8cfcc501 --- /dev/null +++ b/internal/pkg/config/default_linux.go @@ -0,0 +1,72 @@ +// 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. +// +build linux + +package config + +import ( + "fmt" + "net" + + "github.com/vishvananda/netlink" +) + +func GetIPv6LinkLocalNeighborAddress(ifname string) (string, error) { + ifi, err := net.InterfaceByName(ifname) + if err != nil { + return "", err + } + neighs, err := netlink.NeighList(ifi.Index, netlink.FAMILY_V6) + if err != nil { + return "", err + } + cnt := 0 + var addr net.IP + for _, neigh := range neighs { + local, err := isLocalLinkLocalAddress(ifi.Index, neigh.IP) + if err != nil { + return "", err + } + if neigh.State&netlink.NUD_FAILED == 0 && neigh.IP.IsLinkLocalUnicast() && !local { + addr = neigh.IP + cnt++ + } + } + + if cnt == 0 { + return "", fmt.Errorf("no ipv6 link-local neighbor found") + } else if cnt > 1 { + return "", fmt.Errorf("found %d link-local neighbors. only support p2p link", cnt) + } + + return fmt.Sprintf("%s%%%s", addr, ifname), nil +} + +func isLocalLinkLocalAddress(ifindex int, addr net.IP) (bool, error) { + ifi, err := net.InterfaceByIndex(ifindex) + if err != nil { + return false, err + } + addrs, err := ifi.Addrs() + if err != nil { + return false, err + } + for _, a := range addrs { + if ip, _, _ := net.ParseCIDR(a.String()); addr.Equal(ip) { + return true, nil + } + } + return false, nil +} diff --git a/internal/pkg/config/default_nonlinux.go b/internal/pkg/config/default_nonlinux.go new file mode 100644 index 00000000..fe4705a6 --- /dev/null +++ b/internal/pkg/config/default_nonlinux.go @@ -0,0 +1,25 @@ +// 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. +// +build !linux + +package config + +import ( + "fmt" +) + +func GetIPv6LinkLocalNeighborAddress(ifname string) (string, error) { + return "", fmt.Errorf("unnumbered peering is not supported") +} diff --git a/internal/pkg/config/serve.go b/internal/pkg/config/serve.go new file mode 100644 index 00000000..6b09af6e --- /dev/null +++ b/internal/pkg/config/serve.go @@ -0,0 +1,159 @@ +package config + +import ( + "os" + "os/signal" + "syscall" + + log "github.com/sirupsen/logrus" + "github.com/spf13/viper" +) + +type BgpConfigSet struct { + Global Global `mapstructure:"global"` + Neighbors []Neighbor `mapstructure:"neighbors"` + PeerGroups []PeerGroup `mapstructure:"peer-groups"` + RpkiServers []RpkiServer `mapstructure:"rpki-servers"` + BmpServers []BmpServer `mapstructure:"bmp-servers"` + Vrfs []Vrf `mapstructure:"vrfs"` + MrtDump []Mrt `mapstructure:"mrt-dump"` + Zebra Zebra `mapstructure:"zebra"` + Collector Collector `mapstructure:"collector"` + DefinedSets DefinedSets `mapstructure:"defined-sets"` + PolicyDefinitions []PolicyDefinition `mapstructure:"policy-definitions"` + DynamicNeighbors []DynamicNeighbor `mapstructure:"dynamic-neighbors"` +} + +func ReadConfigfileServe(path, format string, configCh chan *BgpConfigSet) { + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, syscall.SIGHUP) + + // Update config file type, if detectable + format = detectConfigFileType(path, format) + + cnt := 0 + for { + c := &BgpConfigSet{} + v := viper.New() + v.SetConfigFile(path) + v.SetConfigType(format) + var err error + if err = v.ReadInConfig(); err != nil { + goto ERROR + } + if err = v.UnmarshalExact(c); err != nil { + goto ERROR + } + if err = setDefaultConfigValuesWithViper(v, c); err != nil { + goto ERROR + } + if cnt == 0 { + log.WithFields(log.Fields{ + "Topic": "Config", + }).Info("Finished reading the config file") + } + cnt++ + configCh <- c + goto NEXT + ERROR: + if cnt == 0 { + log.WithFields(log.Fields{ + "Topic": "Config", + "Error": err, + }).Fatalf("Can't read config file %s", path) + } else { + log.WithFields(log.Fields{ + "Topic": "Config", + "Error": err, + }).Warningf("Can't read config file %s", path) + } + NEXT: + <-sigCh + log.WithFields(log.Fields{ + "Topic": "Config", + }).Info("Reload the config file") + } +} + +func ConfigSetToRoutingPolicy(c *BgpConfigSet) *RoutingPolicy { + return &RoutingPolicy{ + DefinedSets: c.DefinedSets, + PolicyDefinitions: c.PolicyDefinitions, + } +} + +func UpdatePeerGroupConfig(curC, newC *BgpConfigSet) ([]PeerGroup, []PeerGroup, []PeerGroup) { + addedPg := []PeerGroup{} + deletedPg := []PeerGroup{} + updatedPg := []PeerGroup{} + + for _, n := range newC.PeerGroups { + if idx := existPeerGroup(n.Config.PeerGroupName, curC.PeerGroups); idx < 0 { + addedPg = append(addedPg, n) + } else if !n.Equal(&curC.PeerGroups[idx]) { + log.WithFields(log.Fields{ + "Topic": "Config", + }).Debugf("Current peer-group config:%s", curC.PeerGroups[idx]) + log.WithFields(log.Fields{ + "Topic": "Config", + }).Debugf("New peer-group config:%s", n) + updatedPg = append(updatedPg, n) + } + } + + for _, n := range curC.PeerGroups { + if existPeerGroup(n.Config.PeerGroupName, newC.PeerGroups) < 0 { + deletedPg = append(deletedPg, n) + } + } + return addedPg, deletedPg, updatedPg +} + +func UpdateNeighborConfig(curC, newC *BgpConfigSet) ([]Neighbor, []Neighbor, []Neighbor) { + added := []Neighbor{} + deleted := []Neighbor{} + updated := []Neighbor{} + + for _, n := range newC.Neighbors { + if idx := inSlice(n, curC.Neighbors); idx < 0 { + added = append(added, n) + } else if !n.Equal(&curC.Neighbors[idx]) { + log.WithFields(log.Fields{ + "Topic": "Config", + }).Debugf("Current neighbor config:%s", curC.Neighbors[idx]) + log.WithFields(log.Fields{ + "Topic": "Config", + }).Debugf("New neighbor config:%s", n) + updated = append(updated, n) + } + } + + for _, n := range curC.Neighbors { + if inSlice(n, newC.Neighbors) < 0 { + deleted = append(deleted, n) + } + } + return added, deleted, updated +} + +func CheckPolicyDifference(currentPolicy *RoutingPolicy, newPolicy *RoutingPolicy) bool { + + log.WithFields(log.Fields{ + "Topic": "Config", + }).Debugf("Current policy:%s", currentPolicy) + log.WithFields(log.Fields{ + "Topic": "Config", + }).Debugf("New policy:%s", newPolicy) + + var result bool + if currentPolicy == nil && newPolicy == nil { + result = false + } else { + if currentPolicy != nil && newPolicy != nil { + result = !currentPolicy.Equal(newPolicy) + } else { + result = true + } + } + return result +} diff --git a/internal/pkg/config/util.go b/internal/pkg/config/util.go new file mode 100644 index 00000000..591252b4 --- /dev/null +++ b/internal/pkg/config/util.go @@ -0,0 +1,264 @@ +// Copyright (C) 2015 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 config + +import ( + "fmt" + "net" + "path/filepath" + "regexp" + "strconv" + + "github.com/osrg/gobgp/pkg/packet/bgp" +) + +// Returns config file type by retrieving extension from the given path. +// If no corresponding type found, returns the given def as the default value. +func detectConfigFileType(path, def string) string { + switch ext := filepath.Ext(path); ext { + case ".toml": + return "toml" + case ".yaml", ".yml": + return "yaml" + case ".json": + return "json" + default: + return def + } +} + +// yaml is decoded as []interface{} +// but toml is decoded as []map[string]interface{}. +// currently, viper can't hide this difference. +// handle the difference here. +func extractArray(intf interface{}) ([]interface{}, error) { + if intf != nil { + list, ok := intf.([]interface{}) + if ok { + return list, nil + } + l, ok := intf.([]map[string]interface{}) + if !ok { + return nil, fmt.Errorf("invalid configuration: neither []interface{} nor []map[string]interface{}") + } + list = make([]interface{}, 0, len(l)) + for _, m := range l { + list = append(list, m) + } + return list, nil + } + return nil, nil +} + +func getIPv6LinkLocalAddress(ifname string) (string, error) { + ifi, err := net.InterfaceByName(ifname) + if err != nil { + return "", err + } + addrs, err := ifi.Addrs() + if err != nil { + return "", err + } + for _, addr := range addrs { + ip := addr.(*net.IPNet).IP + if ip.To4() == nil && ip.IsLinkLocalUnicast() { + return fmt.Sprintf("%s%%%s", ip.String(), ifname), nil + } + } + return "", fmt.Errorf("no ipv6 link local address for %s", ifname) +} + +func (b *BgpConfigSet) getPeerGroup(n string) (*PeerGroup, error) { + if n == "" { + return nil, nil + } + for _, pg := range b.PeerGroups { + if n == pg.Config.PeerGroupName { + return &pg, nil + } + } + return nil, fmt.Errorf("no such peer-group: %s", n) +} + +func (d *DynamicNeighbor) validate(b *BgpConfigSet) error { + if d.Config.PeerGroup == "" { + return fmt.Errorf("dynamic neighbor requires the peer group config") + } + + if _, err := b.getPeerGroup(d.Config.PeerGroup); err != nil { + return err + } + if _, _, err := net.ParseCIDR(d.Config.Prefix); err != nil { + return fmt.Errorf("invalid dynamic neighbor prefix %s", d.Config.Prefix) + } + return nil +} + +func (n *Neighbor) IsConfederationMember(g *Global) bool { + for _, member := range g.Confederation.Config.MemberAsList { + if member == n.Config.PeerAs { + return true + } + } + return false +} + +func (n *Neighbor) IsConfederation(g *Global) bool { + if n.Config.PeerAs == g.Config.As { + return true + } + return n.IsConfederationMember(g) +} + +func (n *Neighbor) IsEBGPPeer(g *Global) bool { + return n.Config.PeerAs != g.Config.As +} + +func (n *Neighbor) CreateRfMap() map[bgp.RouteFamily]bgp.BGPAddPathMode { + rfMap := make(map[bgp.RouteFamily]bgp.BGPAddPathMode) + for _, af := range n.AfiSafis { + mode := bgp.BGP_ADD_PATH_NONE + if af.AddPaths.State.Receive { + mode |= bgp.BGP_ADD_PATH_RECEIVE + } + if af.AddPaths.State.SendMax > 0 { + mode |= bgp.BGP_ADD_PATH_SEND + } + rfMap[af.State.Family] = mode + } + return rfMap +} + +func (n *Neighbor) GetAfiSafi(family bgp.RouteFamily) *AfiSafi { + for _, a := range n.AfiSafis { + if string(a.Config.AfiSafiName) == family.String() { + return &a + } + } + return nil +} + +func (n *Neighbor) ExtractNeighborAddress() (string, error) { + addr := n.State.NeighborAddress + if addr == "" { + addr = n.Config.NeighborAddress + if addr == "" { + return "", fmt.Errorf("NeighborAddress is not configured") + } + } + return addr, nil +} + +func (n *Neighbor) IsAddPathReceiveEnabled(family bgp.RouteFamily) bool { + for _, af := range n.AfiSafis { + if af.State.Family == family { + return af.AddPaths.State.Receive + } + } + return false +} + +type AfiSafis []AfiSafi + +func (c AfiSafis) ToRfList() ([]bgp.RouteFamily, error) { + rfs := make([]bgp.RouteFamily, 0, len(c)) + for _, af := range c { + rfs = append(rfs, af.State.Family) + } + return rfs, nil +} + +func inSlice(n Neighbor, b []Neighbor) int { + for i, nb := range b { + if nb.State.NeighborAddress == n.State.NeighborAddress { + return i + } + } + return -1 +} + +func existPeerGroup(n string, b []PeerGroup) int { + for i, nb := range b { + if nb.Config.PeerGroupName == n { + return i + } + } + return -1 +} + +func isAfiSafiChanged(x, y []AfiSafi) bool { + if len(x) != len(y) { + return true + } + m := make(map[string]bool) + for _, e := range x { + m[string(e.Config.AfiSafiName)] = true + } + for _, e := range y { + if !m[string(e.Config.AfiSafiName)] { + return true + } + } + return false +} + +func (n *Neighbor) NeedsResendOpenMessage(new *Neighbor) bool { + return !n.Config.Equal(&new.Config) || + !n.Transport.Config.Equal(&new.Transport.Config) || + !n.AddPaths.Config.Equal(&new.AddPaths.Config) || + !n.GracefulRestart.Config.Equal(&new.GracefulRestart.Config) || + isAfiSafiChanged(n.AfiSafis, new.AfiSafis) +} + +// TODO: these regexp are duplicated in api +var _regexpPrefixMaskLengthRange = regexp.MustCompile(`(\d+)\.\.(\d+)`) + +func ParseMaskLength(prefix, mask string) (int, int, error) { + _, ipNet, err := net.ParseCIDR(prefix) + if err != nil { + return 0, 0, fmt.Errorf("invalid prefix: %s", prefix) + } + if mask == "" { + l, _ := ipNet.Mask.Size() + return l, l, nil + } + elems := _regexpPrefixMaskLengthRange.FindStringSubmatch(mask) + if len(elems) != 3 { + return 0, 0, fmt.Errorf("invalid mask length range: %s", mask) + } + // we've already checked the range is sane by regexp + min, _ := strconv.ParseUint(elems[1], 10, 8) + max, _ := strconv.ParseUint(elems[2], 10, 8) + if min > max { + return 0, 0, fmt.Errorf("invalid mask length range: %s", mask) + } + if ipv4 := ipNet.IP.To4(); ipv4 != nil { + f := func(i uint64) bool { + return i <= 32 + } + if !f(min) || !f(max) { + return 0, 0, fmt.Errorf("ipv4 mask length range outside scope :%s", mask) + } + } else { + f := func(i uint64) bool { + return i <= 128 + } + if !f(min) || !f(max) { + return 0, 0, fmt.Errorf("ipv6 mask length range outside scope :%s", mask) + } + } + return int(min), int(max), nil +} diff --git a/internal/pkg/config/util_test.go b/internal/pkg/config/util_test.go new file mode 100644 index 00000000..a34b02f4 --- /dev/null +++ b/internal/pkg/config/util_test.go @@ -0,0 +1,32 @@ +// Copyright (C) 2017 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 config + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDetectConfigFileType(t *testing.T) { + assert := assert.New(t) + + assert.Equal("toml", detectConfigFileType("bgpd.conf", "toml")) + assert.Equal("toml", detectConfigFileType("bgpd.toml", "xxx")) + assert.Equal("yaml", detectConfigFileType("bgpd.yaml", "xxx")) + assert.Equal("yaml", detectConfigFileType("bgpd.yml", "xxx")) + assert.Equal("json", detectConfigFileType("bgpd.json", "xxx")) +} diff --git a/internal/pkg/table/adj.go b/internal/pkg/table/adj.go new file mode 100644 index 00000000..95fbf6af --- /dev/null +++ b/internal/pkg/table/adj.go @@ -0,0 +1,186 @@ +// Copyright (C) 2015 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 table + +import ( + "fmt" + + "github.com/osrg/gobgp/pkg/packet/bgp" +) + +type AdjRib struct { + accepted map[bgp.RouteFamily]int + table map[bgp.RouteFamily]map[string]*Path +} + +func NewAdjRib(rfList []bgp.RouteFamily) *AdjRib { + table := make(map[bgp.RouteFamily]map[string]*Path) + for _, rf := range rfList { + table[rf] = make(map[string]*Path) + } + return &AdjRib{ + table: table, + accepted: make(map[bgp.RouteFamily]int), + } +} + +func (adj *AdjRib) Update(pathList []*Path) { + for _, path := range pathList { + if path == nil || path.IsEOR() { + continue + } + rf := path.GetRouteFamily() + key := fmt.Sprintf("%d:%s", path.GetNlri().PathIdentifier(), path.getPrefix()) + + old, found := adj.table[rf][key] + if path.IsWithdraw { + if found { + delete(adj.table[rf], key) + if !old.IsAsLooped() { + adj.accepted[rf]-- + } + } + } else { + if found { + if old.IsAsLooped() && !path.IsAsLooped() { + adj.accepted[rf]++ + } else if !old.IsAsLooped() && path.IsAsLooped() { + adj.accepted[rf]-- + } + } else { + if !path.IsAsLooped() { + adj.accepted[rf]++ + } + } + if found && old.Equal(path) { + path.setTimestamp(old.GetTimestamp()) + } + adj.table[rf][key] = path + } + } +} + +func (adj *AdjRib) PathList(rfList []bgp.RouteFamily, accepted bool) []*Path { + pathList := make([]*Path, 0, adj.Count(rfList)) + for _, rf := range rfList { + for _, rr := range adj.table[rf] { + if accepted && rr.IsAsLooped() { + continue + } + pathList = append(pathList, rr) + } + } + return pathList +} + +func (adj *AdjRib) Count(rfList []bgp.RouteFamily) int { + count := 0 + for _, rf := range rfList { + if table, ok := adj.table[rf]; ok { + count += len(table) + } + } + return count +} + +func (adj *AdjRib) Accepted(rfList []bgp.RouteFamily) int { + count := 0 + for _, rf := range rfList { + if n, ok := adj.accepted[rf]; ok { + count += n + } + } + return count +} + +func (adj *AdjRib) Drop(rfList []bgp.RouteFamily) { + for _, rf := range rfList { + if _, ok := adj.table[rf]; ok { + adj.table[rf] = make(map[string]*Path) + adj.accepted[rf] = 0 + } + } +} + +func (adj *AdjRib) DropStale(rfList []bgp.RouteFamily) []*Path { + pathList := make([]*Path, 0, adj.Count(rfList)) + for _, rf := range rfList { + if table, ok := adj.table[rf]; ok { + for k, p := range table { + if p.IsStale() { + delete(table, k) + if !p.IsAsLooped() { + adj.accepted[rf]-- + } + pathList = append(pathList, p.Clone(true)) + } + } + } + } + return pathList +} + +func (adj *AdjRib) StaleAll(rfList []bgp.RouteFamily) []*Path { + pathList := make([]*Path, 0) + for _, rf := range rfList { + if table, ok := adj.table[rf]; ok { + l := make([]*Path, 0, len(table)) + for k, p := range table { + n := p.Clone(false) + n.MarkStale(true) + table[k] = n + l = append(l, n) + } + if len(l) > 0 { + pathList = append(pathList, l...) + } + } + } + return pathList +} + +func (adj *AdjRib) Select(family bgp.RouteFamily, accepted bool, option ...TableSelectOption) (*Table, error) { + m := make(map[string][]*Path) + pl := adj.PathList([]bgp.RouteFamily{family}, accepted) + for _, path := range pl { + key := path.GetNlri().String() + if _, y := m[key]; y { + m[key] = append(m[key], path) + } else { + m[key] = []*Path{path} + } + } + d := make([]*Destination, 0, len(pl)) + for _, l := range m { + d = append(d, NewDestination(l[0].GetNlri(), 0, l...)) + } + tbl := NewTable(family, d...) + option = append(option, TableSelectOption{adj: true}) + return tbl.Select(option...) +} + +func (adj *AdjRib) TableInfo(family bgp.RouteFamily) (*TableInfo, error) { + if _, ok := adj.table[family]; !ok { + return nil, fmt.Errorf("%s unsupported", family) + } + c := adj.Count([]bgp.RouteFamily{family}) + a := adj.Accepted([]bgp.RouteFamily{family}) + return &TableInfo{ + NumDestination: c, + NumPath: c, + NumAccepted: a, + }, nil +} diff --git a/internal/pkg/table/adj_test.go b/internal/pkg/table/adj_test.go new file mode 100644 index 00000000..ac4fc5a0 --- /dev/null +++ b/internal/pkg/table/adj_test.go @@ -0,0 +1,52 @@ +// 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 table + +import ( + "testing" + "time" + + "github.com/osrg/gobgp/pkg/packet/bgp" + + "github.com/stretchr/testify/assert" +) + +func TestStaleAll(t *testing.T) { + pi := &PeerInfo{} + attrs := []bgp.PathAttributeInterface{bgp.NewPathAttributeOrigin(0)} + + nlri1 := bgp.NewIPAddrPrefix(24, "20.20.20.0") + nlri1.SetPathIdentifier(1) + p1 := NewPath(pi, nlri1, false, attrs, time.Now(), false) + nlri2 := bgp.NewIPAddrPrefix(24, "20.20.20.0") + nlri2.SetPathIdentifier(2) + p2 := NewPath(pi, nlri2, false, attrs, time.Now(), false) + family := p1.GetRouteFamily() + families := []bgp.RouteFamily{family} + + adj := NewAdjRib(families) + adj.Update([]*Path{p1, p2}) + assert.Equal(t, len(adj.table[family]), 2) + + adj.StaleAll(families) + + for _, p := range adj.table[family] { + assert.True(t, p.IsStale()) + } + + adj.DropStale(families) + assert.Equal(t, len(adj.table[family]), 0) +} diff --git a/internal/pkg/table/destination.go b/internal/pkg/table/destination.go new file mode 100644 index 00000000..fa61572e --- /dev/null +++ b/internal/pkg/table/destination.go @@ -0,0 +1,1041 @@ +// Copyright (C) 2014 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 table + +import ( + "bytes" + "encoding/binary" + "encoding/json" + "fmt" + "net" + "sort" + + "github.com/osrg/gobgp/internal/pkg/config" + "github.com/osrg/gobgp/pkg/packet/bgp" + + log "github.com/sirupsen/logrus" +) + +var SelectionOptions config.RouteSelectionOptionsConfig +var UseMultiplePaths config.UseMultiplePathsConfig + +type BestPathReason uint8 + +const ( + BPR_UNKNOWN BestPathReason = iota + BPR_DISABLED + BPR_ONLY_PATH + BPR_REACHABLE_NEXT_HOP + BPR_HIGHEST_WEIGHT + BPR_LOCAL_PREF + BPR_LOCAL_ORIGIN + BPR_ASPATH + BPR_ORIGIN + BPR_MED + BPR_ASN + BPR_IGP_COST + BPR_ROUTER_ID + BPR_OLDER + BPR_NON_LLGR_STALE +) + +var BestPathReasonStringMap = map[BestPathReason]string{ + BPR_UNKNOWN: "Unknown", + BPR_DISABLED: "Bestpath selection disabled", + BPR_ONLY_PATH: "Only Path", + BPR_REACHABLE_NEXT_HOP: "Reachable Next Hop", + BPR_HIGHEST_WEIGHT: "Highest Weight", + BPR_LOCAL_PREF: "Local Pref", + BPR_LOCAL_ORIGIN: "Local Origin", + BPR_ASPATH: "AS Path", + BPR_ORIGIN: "Origin", + BPR_MED: "MED", + BPR_ASN: "ASN", + BPR_IGP_COST: "IGP Cost", + BPR_ROUTER_ID: "Router ID", + BPR_OLDER: "Older", + BPR_NON_LLGR_STALE: "no LLGR Stale", +} + +func (r *BestPathReason) String() string { + return BestPathReasonStringMap[*r] +} + +func IpToRadixkey(b []byte, max uint8) string { + var buffer bytes.Buffer + for i := 0; i < len(b) && i < int(max); i++ { + fmt.Fprintf(&buffer, "%08b", b[i]) + } + return buffer.String()[:max] +} + +func CidrToRadixkey(cidr string) string { + _, n, _ := net.ParseCIDR(cidr) + ones, _ := n.Mask.Size() + return IpToRadixkey(n.IP, uint8(ones)) +} + +func AddrToRadixkey(addr bgp.AddrPrefixInterface) string { + var ( + ip net.IP + size uint8 + ) + switch T := addr.(type) { + case *bgp.IPAddrPrefix: + mask := net.CIDRMask(int(T.Length), net.IPv4len*8) + ip, size = T.Prefix.Mask(mask).To4(), uint8(T.Length) + case *bgp.IPv6AddrPrefix: + mask := net.CIDRMask(int(T.Length), net.IPv6len*8) + ip, size = T.Prefix.Mask(mask).To16(), uint8(T.Length) + default: + return CidrToRadixkey(addr.String()) + } + return IpToRadixkey(ip, size) +} + +type PeerInfo struct { + AS uint32 + ID net.IP + LocalAS uint32 + LocalID net.IP + Address net.IP + LocalAddress net.IP + RouteReflectorClient bool + RouteReflectorClusterID net.IP + MultihopTtl uint8 + Confederation bool +} + +func (lhs *PeerInfo) Equal(rhs *PeerInfo) bool { + if lhs == rhs { + return true + } + + if rhs == nil { + return false + } + + if (lhs.AS == rhs.AS) && lhs.ID.Equal(rhs.ID) && lhs.LocalID.Equal(rhs.LocalID) && lhs.Address.Equal(rhs.Address) { + return true + } + return false +} + +func (i *PeerInfo) String() string { + if i.Address == nil { + return "local" + } + s := bytes.NewBuffer(make([]byte, 0, 64)) + s.WriteString(fmt.Sprintf("{ %s | ", i.Address)) + s.WriteString(fmt.Sprintf("as: %d", i.AS)) + s.WriteString(fmt.Sprintf(", id: %s", i.ID)) + if i.RouteReflectorClient { + s.WriteString(fmt.Sprintf(", cluster-id: %s", i.RouteReflectorClusterID)) + } + s.WriteString(" }") + return s.String() +} + +func NewPeerInfo(g *config.Global, p *config.Neighbor) *PeerInfo { + clusterID := net.ParseIP(string(p.RouteReflector.State.RouteReflectorClusterId)).To4() + // exclude zone info + naddr, _ := net.ResolveIPAddr("ip", p.State.NeighborAddress) + return &PeerInfo{ + AS: p.Config.PeerAs, + LocalAS: g.Config.As, + LocalID: net.ParseIP(g.Config.RouterId).To4(), + RouteReflectorClient: p.RouteReflector.Config.RouteReflectorClient, + Address: naddr.IP, + RouteReflectorClusterID: clusterID, + MultihopTtl: p.EbgpMultihop.Config.MultihopTtl, + Confederation: p.IsConfederationMember(g), + } +} + +type Destination struct { + routeFamily bgp.RouteFamily + nlri bgp.AddrPrefixInterface + knownPathList []*Path + localIdMap *Bitmap +} + +func NewDestination(nlri bgp.AddrPrefixInterface, mapSize int, known ...*Path) *Destination { + d := &Destination{ + routeFamily: bgp.AfiSafiToRouteFamily(nlri.AFI(), nlri.SAFI()), + nlri: nlri, + knownPathList: known, + localIdMap: NewBitmap(mapSize), + } + // the id zero means id is not allocated yet. + if mapSize != 0 { + d.localIdMap.Flag(0) + } + return d +} + +func (dd *Destination) Family() bgp.RouteFamily { + return dd.routeFamily +} + +func (dd *Destination) setRouteFamily(routeFamily bgp.RouteFamily) { + dd.routeFamily = routeFamily +} + +func (dd *Destination) GetNlri() bgp.AddrPrefixInterface { + return dd.nlri +} + +func (dd *Destination) setNlri(nlri bgp.AddrPrefixInterface) { + dd.nlri = nlri +} + +func (dd *Destination) GetAllKnownPathList() []*Path { + return dd.knownPathList +} + +func rsFilter(id string, as uint32, path *Path) bool { + isASLoop := func(as uint32, path *Path) bool { + for _, v := range path.GetAsList() { + if as == v { + return true + } + } + return false + } + + if id != GLOBAL_RIB_NAME && (path.GetSource().Address.String() == id || isASLoop(as, path)) { + return true + } + return false +} + +func (dd *Destination) GetKnownPathList(id string, as uint32) []*Path { + list := make([]*Path, 0, len(dd.knownPathList)) + for _, p := range dd.knownPathList { + if rsFilter(id, as, p) { + continue + } + list = append(list, p) + } + return list +} + +func getBestPath(id string, as uint32, pathList []*Path) *Path { + for _, p := range pathList { + if rsFilter(id, as, p) { + continue + } + return p + } + return nil +} + +func (dd *Destination) GetBestPath(id string, as uint32) *Path { + p := getBestPath(id, as, dd.knownPathList) + if p == nil || p.IsNexthopInvalid { + return nil + } + return p +} + +func (dd *Destination) GetMultiBestPath(id string) []*Path { + return getMultiBestPath(id, dd.knownPathList) +} + +// Calculates best-path among known paths for this destination. +// +// Modifies destination's state related to stored paths. Removes withdrawn +// paths from known paths. Also, adds new paths to known paths. +func (dest *Destination) Calculate(newPath *Path) *Update { + oldKnownPathList := make([]*Path, len(dest.knownPathList)) + copy(oldKnownPathList, dest.knownPathList) + + if newPath.IsWithdraw { + p := dest.explicitWithdraw(newPath) + if p != nil { + if id := p.GetNlri().PathLocalIdentifier(); id != 0 { + dest.localIdMap.Unflag(uint(id)) + } + } + } else { + dest.implicitWithdraw(newPath) + dest.knownPathList = append(dest.knownPathList, newPath) + } + + for _, path := range dest.knownPathList { + if path.GetNlri().PathLocalIdentifier() == 0 { + id, err := dest.localIdMap.FindandSetZeroBit() + if err != nil { + dest.localIdMap.Expand() + id, _ = dest.localIdMap.FindandSetZeroBit() + } + path.GetNlri().SetPathLocalIdentifier(uint32(id)) + } + } + // Compute new best path + dest.computeKnownBestPath() + + l := make([]*Path, len(dest.knownPathList)) + copy(l, dest.knownPathList) + return &Update{ + KnownPathList: l, + OldKnownPathList: oldKnownPathList, + } +} + +// Removes withdrawn paths. +// +// Note: +// We may have disproportionate number of withdraws compared to know paths +// since not all paths get installed into the table due to bgp policy and +// we can receive withdraws for such paths and withdrawals may not be +// stopped by the same policies. +// +func (dest *Destination) explicitWithdraw(withdraw *Path) *Path { + log.WithFields(log.Fields{ + "Topic": "Table", + "Key": dest.GetNlri().String(), + }).Debug("Removing withdrawals") + + // If we have some withdrawals and no know-paths, it means it is safe to + // delete these withdraws. + if len(dest.knownPathList) == 0 { + log.WithFields(log.Fields{ + "Topic": "Table", + "Key": dest.GetNlri().String(), + }).Debug("Found withdrawals for path(s) that did not get installed") + return nil + } + + // Match all withdrawals from destination paths. + isFound := -1 + for i, path := range dest.knownPathList { + // We have a match if the source and path-id are same. + if path.GetSource().Equal(withdraw.GetSource()) && path.GetNlri().PathIdentifier() == withdraw.GetNlri().PathIdentifier() { + isFound = i + withdraw.GetNlri().SetPathLocalIdentifier(path.GetNlri().PathLocalIdentifier()) + } + } + + // We do no have any match for this withdraw. + if isFound == -1 { + log.WithFields(log.Fields{ + "Topic": "Table", + "Key": dest.GetNlri().String(), + "Path": withdraw, + }).Warn("No matching path for withdraw found, may be path was not installed into table") + return nil + } else { + p := dest.knownPathList[isFound] + dest.knownPathList = append(dest.knownPathList[:isFound], dest.knownPathList[isFound+1:]...) + return p + } +} + +// Identifies which of known paths are old and removes them. +// +// Known paths will no longer have paths whose new version is present in +// new paths. +func (dest *Destination) implicitWithdraw(newPath *Path) { + found := -1 + for i, path := range dest.knownPathList { + if newPath.NoImplicitWithdraw() { + continue + } + // Here we just check if source is same and not check if path + // version num. as newPaths are implicit withdrawal of old + // paths and when doing RouteRefresh (not EnhancedRouteRefresh) + // we get same paths again. + if newPath.GetSource().Equal(path.GetSource()) && newPath.GetNlri().PathIdentifier() == path.GetNlri().PathIdentifier() { + log.WithFields(log.Fields{ + "Topic": "Table", + "Key": dest.GetNlri().String(), + "Path": path, + }).Debug("Implicit withdrawal of old path, since we have learned new path from the same peer") + + found = i + newPath.GetNlri().SetPathLocalIdentifier(path.GetNlri().PathLocalIdentifier()) + break + } + } + if found != -1 { + dest.knownPathList = append(dest.knownPathList[:found], dest.knownPathList[found+1:]...) + } +} + +func (dest *Destination) computeKnownBestPath() (*Path, BestPathReason, error) { + if SelectionOptions.DisableBestPathSelection { + log.WithFields(log.Fields{ + "Topic": "Table", + }).Debug("computeKnownBestPath skipped") + return nil, BPR_DISABLED, nil + } + + // If we do not have any paths to this destination, then we do not have + // new best path. + if len(dest.knownPathList) == 0 { + return nil, BPR_UNKNOWN, nil + } + + log.WithFields(log.Fields{ + "Topic": "Table", + }).Debugf("computeKnownBestPath knownPathList: %d", len(dest.knownPathList)) + + // We pick the first path as current best path. This helps in breaking + // tie between two new paths learned in one cycle for which best-path + // calculation steps lead to tie. + if len(dest.knownPathList) == 1 { + // If the first path has the invalidated next-hop, which evaluated by + // IGP, returns no path with the reason of the next-hop reachability. + if dest.knownPathList[0].IsNexthopInvalid { + return nil, BPR_REACHABLE_NEXT_HOP, nil + } + return dest.knownPathList[0], BPR_ONLY_PATH, nil + } + dest.sort() + newBest := dest.knownPathList[0] + // If the first path has the invalidated next-hop, which evaluated by IGP, + // returns no path with the reason of the next-hop reachability. + if dest.knownPathList[0].IsNexthopInvalid { + return nil, BPR_REACHABLE_NEXT_HOP, nil + } + return newBest, newBest.reason, nil +} + +func (dst *Destination) sort() { + sort.SliceStable(dst.knownPathList, func(i, j int) bool { + //Compares given paths and returns best path. + // + //Parameters: + // -`path1`: first path to compare + // -`path2`: second path to compare + // + // Best path processing will involve following steps: + // 1. Select a path with a reachable next hop. + // 2. Select the path with the highest weight. + // 3. If path weights are the same, select the path with the highest + // local preference value. + // 4. Prefer locally originated routes (network routes, redistributed + // routes, or aggregated routes) over received routes. + // 5. Select the route with the shortest AS-path length. + // 6. If all paths have the same AS-path length, select the path based + // on origin: IGP is preferred over EGP; EGP is preferred over + // Incomplete. + // 7. If the origins are the same, select the path with lowest MED + // value. + // 8. If the paths have the same MED values, select the path learned + // via EBGP over one learned via IBGP. + // 9. Select the route with the lowest IGP cost to the next hop. + // 10. Select the route received from the peer with the lowest BGP + // router ID. + // + // Returns None if best-path among given paths cannot be computed else best + // path. + // Assumes paths from NC has source equal to None. + // + + path1 := dst.knownPathList[i] + path2 := dst.knownPathList[j] + + var better *Path + reason := BPR_UNKNOWN + + // draft-uttaro-idr-bgp-persistence-02 + if better == nil { + better = compareByLLGRStaleCommunity(path1, path2) + reason = BPR_NON_LLGR_STALE + } + // Follow best path calculation algorithm steps. + // compare by reachability + if better == nil { + better = compareByReachableNexthop(path1, path2) + reason = BPR_REACHABLE_NEXT_HOP + } + if better == nil { + better = compareByHighestWeight(path1, path2) + reason = BPR_HIGHEST_WEIGHT + } + if better == nil { + better = compareByLocalPref(path1, path2) + reason = BPR_LOCAL_PREF + } + if better == nil { + better = compareByLocalOrigin(path1, path2) + reason = BPR_LOCAL_ORIGIN + } + if better == nil { + better = compareByASPath(path1, path2) + reason = BPR_ASPATH + } + if better == nil { + better = compareByOrigin(path1, path2) + reason = BPR_ORIGIN + } + if better == nil { + better = compareByMED(path1, path2) + reason = BPR_MED + } + if better == nil { + better = compareByASNumber(path1, path2) + reason = BPR_ASN + } + if better == nil { + better = compareByIGPCost(path1, path2) + reason = BPR_IGP_COST + } + if better == nil { + better = compareByAge(path1, path2) + reason = BPR_OLDER + } + if better == nil { + var e error = nil + better, e = compareByRouterID(path1, path2) + if e != nil { + log.WithFields(log.Fields{ + "Topic": "Table", + "Error": e, + }).Error("Could not get best path by comparing router ID") + } + reason = BPR_ROUTER_ID + } + if better == nil { + reason = BPR_UNKNOWN + better = path1 + } + + better.reason = reason + + return better == path1 + }) +} + +type Update struct { + KnownPathList []*Path + OldKnownPathList []*Path +} + +func getMultiBestPath(id string, pathList []*Path) []*Path { + list := make([]*Path, 0, len(pathList)) + var best *Path + for _, p := range pathList { + if !p.IsNexthopInvalid { + if best == nil { + best = p + list = append(list, p) + } else if best.Compare(p) == 0 { + list = append(list, p) + } + } + } + return list +} + +func (u *Update) GetWithdrawnPath() []*Path { + if len(u.KnownPathList) == len(u.OldKnownPathList) { + return nil + } + + l := make([]*Path, 0, len(u.OldKnownPathList)) + + for _, p := range u.OldKnownPathList { + y := func() bool { + for _, old := range u.KnownPathList { + if p == old { + return true + } + } + return false + }() + if !y { + l = append(l, p.Clone(true)) + } + } + return l +} + +func (u *Update) GetChanges(id string, as uint32, peerDown bool) (*Path, *Path, []*Path) { + best, old := func(id string) (*Path, *Path) { + old := getBestPath(id, as, u.OldKnownPathList) + best := getBestPath(id, as, u.KnownPathList) + if best != nil && best.Equal(old) { + // RFC4684 3.2. Intra-AS VPN Route Distribution + // When processing RT membership NLRIs received from internal iBGP + // peers, it is necessary to consider all available iBGP paths for a + // given RT prefix, for building the outbound route filter, and not just + // the best path. + if best.GetRouteFamily() == bgp.RF_RTC_UC { + return best, old + } + // For BGP Nexthop Tracking, checks if the nexthop reachability + // was changed or not. + if best.IsNexthopInvalid != old.IsNexthopInvalid { + // If the nexthop of the best path became unreachable, we need + // to withdraw that path. + if best.IsNexthopInvalid { + return best.Clone(true), old + } + return best, old + } + return nil, old + } + if best == nil { + if old == nil { + return nil, nil + } + if peerDown { + // withdraws were generated by peer + // down so paths are not in knowpath + // or adjin. + old.IsWithdraw = true + return old, old + } + return old.Clone(true), old + } + return best, old + }(id) + + var multi []*Path + + if id == GLOBAL_RIB_NAME && UseMultiplePaths.Enabled { + diff := func(lhs, rhs []*Path) bool { + if len(lhs) != len(rhs) { + return true + } + for idx, l := range lhs { + if !l.Equal(rhs[idx]) { + return true + } + } + return false + } + oldM := getMultiBestPath(id, u.OldKnownPathList) + newM := getMultiBestPath(id, u.KnownPathList) + if diff(oldM, newM) { + multi = newM + if len(newM) == 0 { + multi = []*Path{best} + } + } + } + return best, old, multi +} + +func compareByLLGRStaleCommunity(path1, path2 *Path) *Path { + p1 := path1.IsLLGRStale() + p2 := path2.IsLLGRStale() + if p1 == p2 { + return nil + } else if p1 { + return path2 + } + return path1 +} + +func compareByReachableNexthop(path1, path2 *Path) *Path { + // Compares given paths and selects best path based on reachable next-hop. + // + // If no path matches this criteria, return nil. + // For BGP Nexthop Tracking, evaluates next-hop is validated by IGP. + log.WithFields(log.Fields{ + "Topic": "Table", + }).Debugf("enter compareByReachableNexthop -- path1: %s, path2: %s", path1, path2) + + if path1.IsNexthopInvalid && !path2.IsNexthopInvalid { + return path2 + } else if !path1.IsNexthopInvalid && path2.IsNexthopInvalid { + return path1 + } + + return nil +} + +func compareByHighestWeight(path1, path2 *Path) *Path { + // Selects a path with highest weight. + // + // Weight is BGPS specific parameter. It is local to the router on which it + // is configured. + // Return: + // nil if best path among given paths cannot be decided, else best path. + log.WithFields(log.Fields{ + "Topic": "Table", + }).Debugf("enter compareByHighestWeight -- path1: %s, path2: %s", path1, path2) + return nil +} + +func compareByLocalPref(path1, path2 *Path) *Path { + // Selects a path with highest local-preference. + // + // Unlike the weight attribute, which is only relevant to the local + // router, local preference is an attribute that routers exchange in the + // same AS. Highest local-pref is preferred. If we cannot decide, + // we return None. + // + // # Default local-pref values is 100 + log.WithFields(log.Fields{ + "Topic": "Table", + }).Debug("enter compareByLocalPref") + localPref1, _ := path1.GetLocalPref() + localPref2, _ := path2.GetLocalPref() + // Highest local-preference value is preferred. + if localPref1 > localPref2 { + return path1 + } else if localPref1 < localPref2 { + return path2 + } else { + return nil + } +} + +func compareByLocalOrigin(path1, path2 *Path) *Path { + + // Select locally originating path as best path. + // Locally originating routes are network routes, redistributed routes, + // or aggregated routes. + // Returns None if given paths have same source. + // + // If both paths are from same sources we cannot compare them here. + log.WithFields(log.Fields{ + "Topic": "Table", + }).Debug("enter compareByLocalOrigin") + if path1.GetSource().Equal(path2.GetSource()) { + return nil + } + + // Here we consider prefix from NC as locally originating static route. + // Hence it is preferred. + if path1.IsLocal() { + return path1 + } + + if path2.IsLocal() { + return path2 + } + return nil +} + +func compareByASPath(path1, path2 *Path) *Path { + // Calculated the best-paths by comparing as-path lengths. + // + // Shortest as-path length is preferred. If both path have same lengths, + // we return None. + if SelectionOptions.IgnoreAsPathLength { + log.WithFields(log.Fields{ + "Topic": "Table", + }).Debug("compareByASPath -- skip") + return nil + } + log.WithFields(log.Fields{ + "Topic": "Table", + }).Debug("enter compareByASPath") + attribute1 := path1.getPathAttr(bgp.BGP_ATTR_TYPE_AS_PATH) + attribute2 := path2.getPathAttr(bgp.BGP_ATTR_TYPE_AS_PATH) + + // With addpath support, we could compare paths from API don't + // AS_PATH. No need to warn here. + if !path1.IsLocal() && !path2.IsLocal() && (attribute1 == nil || attribute2 == nil) { + log.WithFields(log.Fields{ + "Topic": "Table", + "Key": "compareByASPath", + "ASPath1": attribute1, + "ASPath2": attribute2, + }).Warn("can't compare ASPath because it's not present") + } + + l1 := path1.GetAsPathLen() + l2 := path2.GetAsPathLen() + + log.WithFields(log.Fields{ + "Topic": "Table", + }).Debugf("compareByASPath -- l1: %d, l2: %d", l1, l2) + if l1 > l2 { + return path2 + } else if l1 < l2 { + return path1 + } else { + return nil + } +} + +func compareByOrigin(path1, path2 *Path) *Path { + // Select the best path based on origin attribute. + // + // IGP is preferred over EGP; EGP is preferred over Incomplete. + // If both paths have same origin, we return None. + log.WithFields(log.Fields{ + "Topic": "Table", + }).Debug("enter compareByOrigin") + attribute1 := path1.getPathAttr(bgp.BGP_ATTR_TYPE_ORIGIN) + attribute2 := path2.getPathAttr(bgp.BGP_ATTR_TYPE_ORIGIN) + + if attribute1 == nil || attribute2 == nil { + log.WithFields(log.Fields{ + "Topic": "Table", + "Key": "compareByOrigin", + "Origin1": attribute1, + "Origin2": attribute2, + }).Error("can't compare origin because it's not present") + return nil + } + + origin1 := attribute1.(*bgp.PathAttributeOrigin).Value + origin2 := attribute2.(*bgp.PathAttributeOrigin).Value + log.WithFields(log.Fields{ + "Topic": "Table", + }).Debugf("compareByOrigin -- origin1: %d, origin2: %d", origin1, origin2) + + // If both paths have same origins + if origin1 == origin2 { + return nil + } else if origin1 < origin2 { + return path1 + } else { + return path2 + } +} + +func compareByMED(path1, path2 *Path) *Path { + // Select the path based with lowest MED value. + // + // If both paths have same MED, return None. + // By default, a route that arrives with no MED value is treated as if it + // had a MED of 0, the most preferred value. + // RFC says lower MED is preferred over higher MED value. + // compare MED among not only same AS path but also all path, + // like bgp always-compare-med + + isInternal := func() bool { return path1.GetAsPathLen() == 0 && path2.GetAsPathLen() == 0 }() + + isSameAS := func() bool { + firstAS := func(path *Path) uint32 { + if asPath := path.GetAsPath(); asPath != nil { + for _, v := range asPath.Value { + segType := v.GetType() + asList := v.GetAS() + if len(asList) == 0 { + continue + } + switch segType { + case bgp.BGP_ASPATH_ATTR_TYPE_CONFED_SET, bgp.BGP_ASPATH_ATTR_TYPE_CONFED_SEQ: + continue + } + return asList[0] + } + } + return 0 + } + return firstAS(path1) != 0 && firstAS(path1) == firstAS(path2) + }() + + if SelectionOptions.AlwaysCompareMed || isInternal || isSameAS { + log.WithFields(log.Fields{ + "Topic": "Table", + }).Debug("enter compareByMED") + getMed := func(path *Path) uint32 { + attribute := path.getPathAttr(bgp.BGP_ATTR_TYPE_MULTI_EXIT_DISC) + if attribute == nil { + return 0 + } + med := attribute.(*bgp.PathAttributeMultiExitDisc).Value + return med + } + + med1 := getMed(path1) + med2 := getMed(path2) + log.WithFields(log.Fields{ + "Topic": "Table", + }).Debugf("compareByMED -- med1: %d, med2: %d", med1, med2) + if med1 == med2 { + return nil + } else if med1 < med2 { + return path1 + } + return path2 + } else { + log.WithFields(log.Fields{ + "Topic": "Table", + }).Debugf("skip compareByMED %v %v %v", SelectionOptions.AlwaysCompareMed, isInternal, isSameAS) + return nil + } +} + +func compareByASNumber(path1, path2 *Path) *Path { + + //Select the path based on source (iBGP/eBGP) peer. + // + //eBGP path is preferred over iBGP. If both paths are from same kind of + //peers, return None. + log.WithFields(log.Fields{ + "Topic": "Table", + }).Debug("enter compareByASNumber") + + log.WithFields(log.Fields{ + "Topic": "Table", + }).Debugf("compareByASNumber -- p1Asn: %d, p2Asn: %d", path1.GetSource().AS, path2.GetSource().AS) + // Path from confederation member should be treated as internal (IBGP learned) path. + isIBGP1 := path1.GetSource().Confederation || path1.IsIBGP() + isIBGP2 := path2.GetSource().Confederation || path2.IsIBGP() + // If one path is from ibgp peer and another is from ebgp peer, take the ebgp path. + if isIBGP1 != isIBGP2 { + if isIBGP1 { + return path2 + } + return path1 + } + + // If both paths are from ebgp or ibpg peers, we cannot decide. + return nil +} + +func compareByIGPCost(path1, path2 *Path) *Path { + // Select the route with the lowest IGP cost to the next hop. + // + // Return None if igp cost is same. + // Currently BGPS has no concept of IGP and IGP cost. + log.WithFields(log.Fields{ + "Topic": "Table", + }).Debugf("enter compareByIGPCost -- path1: %v, path2: %v", path1, path2) + return nil +} + +func compareByRouterID(path1, path2 *Path) (*Path, error) { + // Select the route received from the peer with the lowest BGP router ID. + // + // If both paths are eBGP paths, then we do not do any tie breaking, i.e we do + // not pick best-path based on this criteria. + // RFC: http://tools.ietf.org/html/rfc5004 + // We pick best path between two iBGP paths as usual. + log.WithFields(log.Fields{ + "Topic": "Table", + }).Debug("enter compareByRouterID") + + // If both paths are from NC we have same router Id, hence cannot compare. + if path1.IsLocal() && path2.IsLocal() { + return nil, nil + } + + // If both paths are from eBGP peers, then according to RFC we need + // not tie break using router id. + if !SelectionOptions.ExternalCompareRouterId && !path1.IsIBGP() && !path2.IsIBGP() { + return nil, nil + } + + if !SelectionOptions.ExternalCompareRouterId && path1.IsIBGP() != path2.IsIBGP() { + return nil, fmt.Errorf("This method does not support comparing ebgp with ibgp path") + } + + // At least one path is not coming from NC, so we get local bgp id. + id1 := binary.BigEndian.Uint32(path1.GetSource().ID) + id2 := binary.BigEndian.Uint32(path2.GetSource().ID) + + // If both router ids are same/equal we cannot decide. + // This case is possible since router ids are arbitrary. + if id1 == id2 { + return nil, nil + } else if id1 < id2 { + return path1, nil + } else { + return path2, nil + } +} + +func compareByAge(path1, path2 *Path) *Path { + if !path1.IsIBGP() && !path2.IsIBGP() && !SelectionOptions.ExternalCompareRouterId { + age1 := path1.GetTimestamp().UnixNano() + age2 := path2.GetTimestamp().UnixNano() + if age1 == age2 { + return nil + } else if age1 < age2 { + return path1 + } + return path2 + } + return nil +} + +func (dest *Destination) String() string { + return fmt.Sprintf("Destination NLRI: %s", dest.nlri.String()) +} + +type DestinationSelectOption struct { + ID string + AS uint32 + VRF *Vrf + adj bool + Best bool + MultiPath bool +} + +func (d *Destination) MarshalJSON() ([]byte, error) { + return json.Marshal(d.GetAllKnownPathList()) +} + +func (d *Destination) Select(option ...DestinationSelectOption) *Destination { + id := GLOBAL_RIB_NAME + var vrf *Vrf + adj := false + best := false + mp := false + as := uint32(0) + for _, o := range option { + if o.ID != "" { + id = o.ID + } + if o.VRF != nil { + vrf = o.VRF + } + adj = o.adj + best = o.Best + mp = o.MultiPath + as = o.AS + } + var paths []*Path + if adj { + paths = make([]*Path, len(d.knownPathList)) + copy(paths, d.knownPathList) + } else { + paths = d.GetKnownPathList(id, as) + if vrf != nil { + ps := make([]*Path, 0, len(paths)) + for _, p := range paths { + if CanImportToVrf(vrf, p) { + ps = append(ps, p.ToLocal()) + } + } + paths = ps + } + if len(paths) == 0 { + return nil + } + if best { + if !mp { + paths = []*Path{paths[0]} + } else { + ps := make([]*Path, 0, len(paths)) + var best *Path + for _, p := range paths { + if best == nil { + best = p + ps = append(ps, p) + } else if best.Compare(p) == 0 { + ps = append(ps, p) + } + } + paths = ps + } + } + } + return NewDestination(d.nlri, 0, paths...) +} diff --git a/internal/pkg/table/destination_test.go b/internal/pkg/table/destination_test.go new file mode 100644 index 00000000..110278fb --- /dev/null +++ b/internal/pkg/table/destination_test.go @@ -0,0 +1,442 @@ +// Copyright (C) 2014 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 table + +import ( + //"fmt" + "fmt" + "net" + "testing" + "time" + + "github.com/osrg/gobgp/pkg/packet/bgp" + + "github.com/stretchr/testify/assert" +) + +func TestDestinationNewIPv4(t *testing.T) { + peerD := DestCreatePeer() + pathD := DestCreatePath(peerD) + ipv4d := NewDestination(pathD[0].GetNlri(), 0) + assert.NotNil(t, ipv4d) +} +func TestDestinationNewIPv6(t *testing.T) { + peerD := DestCreatePeer() + pathD := DestCreatePath(peerD) + ipv6d := NewDestination(pathD[0].GetNlri(), 0) + assert.NotNil(t, ipv6d) +} + +func TestDestinationSetRouteFamily(t *testing.T) { + dd := &Destination{} + dd.setRouteFamily(bgp.RF_IPv4_UC) + rf := dd.Family() + assert.Equal(t, rf, bgp.RF_IPv4_UC) +} +func TestDestinationGetRouteFamily(t *testing.T) { + dd := &Destination{} + dd.setRouteFamily(bgp.RF_IPv6_UC) + rf := dd.Family() + assert.Equal(t, rf, bgp.RF_IPv6_UC) +} +func TestDestinationSetNlri(t *testing.T) { + dd := &Destination{} + nlri := bgp.NewIPAddrPrefix(24, "13.2.3.1") + dd.setNlri(nlri) + r_nlri := dd.GetNlri() + assert.Equal(t, r_nlri, nlri) +} +func TestDestinationGetNlri(t *testing.T) { + dd := &Destination{} + nlri := bgp.NewIPAddrPrefix(24, "10.110.123.1") + dd.setNlri(nlri) + r_nlri := dd.GetNlri() + assert.Equal(t, r_nlri, nlri) +} + +func TestCalculate2(t *testing.T) { + + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAs4PathParam(2, []uint32{65001})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med} + nlri := bgp.NewIPAddrPrefix(24, "10.10.0.0") + + // peer1 sends normal update message 10.10.0.0/24 + update1 := bgp.NewBGPUpdateMessage(nil, pathAttributes, []*bgp.IPAddrPrefix{nlri}) + peer1 := &PeerInfo{AS: 1, Address: net.IP{1, 1, 1, 1}} + path1 := ProcessMessage(update1, peer1, time.Now())[0] + + d := NewDestination(nlri, 0) + d.Calculate(path1) + + // suppose peer2 sends grammaatically correct but semantically flawed update message + // which has a withdrawal nlri not advertised before + update2 := bgp.NewBGPUpdateMessage([]*bgp.IPAddrPrefix{nlri}, pathAttributes, nil) + peer2 := &PeerInfo{AS: 2, Address: net.IP{2, 2, 2, 2}} + path2 := ProcessMessage(update2, peer2, time.Now())[0] + assert.Equal(t, path2.IsWithdraw, true) + + d.Calculate(path2) + + // we have a path from peer1 here + assert.Equal(t, len(d.knownPathList), 1) + + // after that, new update with the same nlri comes from peer2 + update3 := bgp.NewBGPUpdateMessage(nil, pathAttributes, []*bgp.IPAddrPrefix{nlri}) + path3 := ProcessMessage(update3, peer2, time.Now())[0] + assert.Equal(t, path3.IsWithdraw, false) + + d.Calculate(path3) + + // this time, we have paths from peer1 and peer2 + assert.Equal(t, len(d.knownPathList), 2) + + // now peer3 sends normal update message 10.10.0.0/24 + peer3 := &PeerInfo{AS: 3, Address: net.IP{3, 3, 3, 3}} + update4 := bgp.NewBGPUpdateMessage(nil, pathAttributes, []*bgp.IPAddrPrefix{nlri}) + path4 := ProcessMessage(update4, peer3, time.Now())[0] + + d.Calculate(path4) + + // we must have paths from peer1, peer2 and peer3 + assert.Equal(t, len(d.knownPathList), 3) +} + +func TestMedTieBreaker(t *testing.T) { + nlri := bgp.NewIPAddrPrefix(24, "10.10.0.0") + + p0 := func() *Path { + aspath := bgp.NewPathAttributeAsPath([]bgp.AsPathParamInterface{bgp.NewAs4PathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, []uint32{65001, 65002}), bgp.NewAs4PathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, []uint32{65003, 65004})}) + attrs := []bgp.PathAttributeInterface{aspath, bgp.NewPathAttributeMultiExitDisc(0)} + return NewPath(nil, nlri, false, attrs, time.Now(), false) + }() + + p1 := func() *Path { + aspath := bgp.NewPathAttributeAsPath([]bgp.AsPathParamInterface{bgp.NewAs4PathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, []uint32{65001, 65002}), bgp.NewAs4PathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, []uint32{65003, 65005})}) + attrs := []bgp.PathAttributeInterface{aspath, bgp.NewPathAttributeMultiExitDisc(10)} + return NewPath(nil, nlri, false, attrs, time.Now(), false) + }() + + // same AS + assert.Equal(t, compareByMED(p0, p1), p0) + + p2 := func() *Path { + aspath := bgp.NewPathAttributeAsPath([]bgp.AsPathParamInterface{bgp.NewAs4PathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, []uint32{65003})}) + attrs := []bgp.PathAttributeInterface{aspath, bgp.NewPathAttributeMultiExitDisc(10)} + return NewPath(nil, nlri, false, attrs, time.Now(), false) + }() + + // different AS + assert.Equal(t, compareByMED(p0, p2), (*Path)(nil)) + + p3 := func() *Path { + aspath := bgp.NewPathAttributeAsPath([]bgp.AsPathParamInterface{bgp.NewAs4PathParam(bgp.BGP_ASPATH_ATTR_TYPE_CONFED_SEQ, []uint32{65003, 65004}), bgp.NewAs4PathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, []uint32{65001, 65003})}) + attrs := []bgp.PathAttributeInterface{aspath, bgp.NewPathAttributeMultiExitDisc(0)} + return NewPath(nil, nlri, false, attrs, time.Now(), false) + }() + + p4 := func() *Path { + aspath := bgp.NewPathAttributeAsPath([]bgp.AsPathParamInterface{bgp.NewAs4PathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, []uint32{65001, 65002}), bgp.NewAs4PathParam(bgp.BGP_ASPATH_ATTR_TYPE_CONFED_SEQ, []uint32{65005, 65006})}) + attrs := []bgp.PathAttributeInterface{aspath, bgp.NewPathAttributeMultiExitDisc(10)} + return NewPath(nil, nlri, false, attrs, time.Now(), false) + }() + + // ignore confed + assert.Equal(t, compareByMED(p3, p4), p3) + + p5 := func() *Path { + attrs := []bgp.PathAttributeInterface{bgp.NewPathAttributeMultiExitDisc(0)} + return NewPath(nil, nlri, false, attrs, time.Now(), false) + }() + + p6 := func() *Path { + attrs := []bgp.PathAttributeInterface{bgp.NewPathAttributeMultiExitDisc(10)} + return NewPath(nil, nlri, false, attrs, time.Now(), false) + }() + + // no aspath + assert.Equal(t, compareByMED(p5, p6), p5) +} + +func TestTimeTieBreaker(t *testing.T) { + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAs4PathParam(2, []uint32{65001})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med} + nlri := bgp.NewIPAddrPrefix(24, "10.10.0.0") + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, []*bgp.IPAddrPrefix{nlri}) + peer1 := &PeerInfo{AS: 2, LocalAS: 1, Address: net.IP{1, 1, 1, 1}, ID: net.IP{1, 1, 1, 1}} + path1 := ProcessMessage(updateMsg, peer1, time.Now())[0] + + peer2 := &PeerInfo{AS: 2, LocalAS: 1, Address: net.IP{2, 2, 2, 2}, ID: net.IP{2, 2, 2, 2}} // weaker router-id + path2 := ProcessMessage(updateMsg, peer2, time.Now().Add(-1*time.Hour))[0] // older than path1 + + d := NewDestination(nlri, 0) + d.Calculate(path1) + d.Calculate(path2) + + assert.Equal(t, len(d.knownPathList), 2) + assert.Equal(t, true, d.GetBestPath("", 0).GetSource().ID.Equal(net.IP{2, 2, 2, 2})) // path from peer2 win + + // this option disables tie breaking by age + SelectionOptions.ExternalCompareRouterId = true + d = NewDestination(nlri, 0) + d.Calculate(path1) + d.Calculate(path2) + + assert.Equal(t, len(d.knownPathList), 2) + assert.Equal(t, true, d.GetBestPath("", 0).GetSource().ID.Equal(net.IP{1, 1, 1, 1})) // path from peer1 win +} + +func DestCreatePeer() []*PeerInfo { + peerD1 := &PeerInfo{AS: 65000} + peerD2 := &PeerInfo{AS: 65001} + peerD3 := &PeerInfo{AS: 65002} + peerD := []*PeerInfo{peerD1, peerD2, peerD3} + return peerD +} + +func DestCreatePath(peerD []*PeerInfo) []*Path { + bgpMsgD1 := updateMsgD1() + bgpMsgD2 := updateMsgD2() + bgpMsgD3 := updateMsgD3() + pathD := make([]*Path, 3) + for i, msg := range []*bgp.BGPMessage{bgpMsgD1, bgpMsgD2, bgpMsgD3} { + updateMsgD := msg.Body.(*bgp.BGPUpdate) + nlriList := updateMsgD.NLRI + pathAttributes := updateMsgD.PathAttributes + nlri_info := nlriList[0] + pathD[i] = NewPath(peerD[i], nlri_info, false, pathAttributes, time.Now(), false) + } + return pathD +} + +func updateMsgD1() *bgp.BGPMessage { + + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65000})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("192.168.50.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + + pathAttributes := []bgp.PathAttributeInterface{ + origin, + aspath, + nexthop, + med, + } + + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.10.0")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + UpdatePathAttrs4ByteAs(updateMsg.Body.(*bgp.BGPUpdate)) + return updateMsg +} + +func updateMsgD2() *bgp.BGPMessage { + + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65100})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("192.168.100.1") + med := bgp.NewPathAttributeMultiExitDisc(100) + + pathAttributes := []bgp.PathAttributeInterface{ + origin, + aspath, + nexthop, + med, + } + + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "20.20.20.0")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + UpdatePathAttrs4ByteAs(updateMsg.Body.(*bgp.BGPUpdate)) + return updateMsg +} +func updateMsgD3() *bgp.BGPMessage { + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65100})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("192.168.150.1") + med := bgp.NewPathAttributeMultiExitDisc(100) + + pathAttributes := []bgp.PathAttributeInterface{ + origin, + aspath, + nexthop, + med, + } + + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "30.30.30.0")} + w1 := bgp.NewIPAddrPrefix(23, "40.40.40.0") + withdrawnRoutes := []*bgp.IPAddrPrefix{w1} + updateMsg := bgp.NewBGPUpdateMessage(withdrawnRoutes, pathAttributes, nlri) + UpdatePathAttrs4ByteAs(updateMsg.Body.(*bgp.BGPUpdate)) + return updateMsg +} + +func TestRadixkey(t *testing.T) { + assert.Equal(t, "000010100000001100100000", CidrToRadixkey("10.3.32.0/24")) + assert.Equal(t, "000010100000001100100000", IpToRadixkey(net.ParseIP("10.3.32.0").To4(), 24)) + assert.Equal(t, "000010100000001100100000", IpToRadixkey(net.ParseIP("10.3.32.0").To4(), 24)) + assert.Equal(t, CidrToRadixkey("::ffff:0.0.0.0/96")+"000010100000001100100000", CidrToRadixkey("::ffff:10.3.32.0/120")) +} + +func TestIpToRadixkey(t *testing.T) { + for i := byte(0); i < 255; i += 3 { + for y := byte(1); y < 128; y *= 2 { + ip := net.IPv4(i, i+2, i+3, i-y) + for n := uint8(16); n <= 32; n += 2 { + exp := CidrToRadixkey(fmt.Sprintf("%v/%d", ip.To4(), n)) + got := IpToRadixkey(ip.To4(), n) + if exp != got { + t.Fatalf(`exp %v; got %v`, exp, got) + } + } + for n := uint8(116); n <= 128; n += 2 { + exp := CidrToRadixkey(fmt.Sprintf("::ffff:%v/%d", ip.To16(), n)) + got := IpToRadixkey(ip.To16(), n) + if exp != got { + t.Fatalf(`exp %v; got %v`, exp, got) + } + } + } + } +} + +func TestMultipath(t *testing.T) { + UseMultiplePaths.Enabled = true + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAs4PathParam(2, []uint32{65000})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("192.168.150.1") + med := bgp.NewPathAttributeMultiExitDisc(100) + + pathAttributes := []bgp.PathAttributeInterface{ + origin, + aspath, + nexthop, + med, + } + + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.10.0")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + peer1 := &PeerInfo{AS: 1, Address: net.IP{1, 1, 1, 1}, ID: net.IP{1, 1, 1, 1}} + path1 := ProcessMessage(updateMsg, peer1, time.Now())[0] + peer2 := &PeerInfo{AS: 2, Address: net.IP{2, 2, 2, 2}, ID: net.IP{2, 2, 2, 2}} + + med = bgp.NewPathAttributeMultiExitDisc(100) + nexthop = bgp.NewPathAttributeNextHop("192.168.150.2") + pathAttributes = []bgp.PathAttributeInterface{ + origin, + aspath, + nexthop, + med, + } + updateMsg = bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + path2 := ProcessMessage(updateMsg, peer2, time.Now())[0] + + d := NewDestination(nlri[0], 0) + d.Calculate(path2) + + best, old, multi := d.Calculate(path1).GetChanges(GLOBAL_RIB_NAME, 0, false) + assert.NotNil(t, best) + assert.Equal(t, old, path2) + assert.Equal(t, len(multi), 2) + assert.Equal(t, len(d.GetKnownPathList(GLOBAL_RIB_NAME, 0)), 2) + + path3 := path2.Clone(true) + dd := d.Calculate(path3) + best, old, multi = dd.GetChanges(GLOBAL_RIB_NAME, 0, false) + assert.Nil(t, best) + assert.Equal(t, old, path1) + assert.Equal(t, len(multi), 1) + assert.Equal(t, len(d.GetKnownPathList(GLOBAL_RIB_NAME, 0)), 1) + + peer3 := &PeerInfo{AS: 3, Address: net.IP{3, 3, 3, 3}, ID: net.IP{3, 3, 3, 3}} + med = bgp.NewPathAttributeMultiExitDisc(50) + nexthop = bgp.NewPathAttributeNextHop("192.168.150.3") + pathAttributes = []bgp.PathAttributeInterface{ + origin, + aspath, + nexthop, + med, + } + updateMsg = bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + path4 := ProcessMessage(updateMsg, peer3, time.Now())[0] + dd = d.Calculate(path4) + best, _, multi = dd.GetChanges(GLOBAL_RIB_NAME, 0, false) + assert.NotNil(t, best) + assert.Equal(t, len(multi), 1) + assert.Equal(t, len(d.GetKnownPathList(GLOBAL_RIB_NAME, 0)), 2) + + nexthop = bgp.NewPathAttributeNextHop("192.168.150.2") + pathAttributes = []bgp.PathAttributeInterface{ + origin, + aspath, + nexthop, + med, + } + updateMsg = bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + path5 := ProcessMessage(updateMsg, peer2, time.Now())[0] + best, _, multi = d.Calculate(path5).GetChanges(GLOBAL_RIB_NAME, 0, false) + assert.NotNil(t, best) + assert.Equal(t, len(multi), 2) + assert.Equal(t, len(d.GetKnownPathList(GLOBAL_RIB_NAME, 0)), 3) + + UseMultiplePaths.Enabled = false +} + +func TestIdMap(t *testing.T) { + d := NewDestination(bgp.NewIPAddrPrefix(24, "10.10.0.101"), 64) + for i := 0; ; i++ { + if id, err := d.localIdMap.FindandSetZeroBit(); err == nil { + assert.Equal(t, uint(i+1), id) + } else { + assert.Equal(t, i, 63) + break + } + } + d.localIdMap.Expand() + for i := 0; i < 64; i++ { + id, _ := d.localIdMap.FindandSetZeroBit() + assert.Equal(t, id, uint(64+i)) + } + _, err := d.localIdMap.FindandSetZeroBit() + assert.NotNil(t, err) +} + +func TestGetWithdrawnPath(t *testing.T) { + attrs := []bgp.PathAttributeInterface{ + bgp.NewPathAttributeOrigin(0), + } + p1 := NewPath(nil, bgp.NewIPAddrPrefix(24, "13.2.3.0"), false, attrs, time.Now(), false) + p2 := NewPath(nil, bgp.NewIPAddrPrefix(24, "13.2.4.0"), false, attrs, time.Now(), false) + p3 := NewPath(nil, bgp.NewIPAddrPrefix(24, "13.2.5.0"), false, attrs, time.Now(), false) + + u := &Update{ + KnownPathList: []*Path{p2}, + OldKnownPathList: []*Path{p1, p2, p3}, + } + + l := u.GetWithdrawnPath() + assert.Equal(t, len(l), 2) + assert.Equal(t, l[0].GetNlri(), p1.GetNlri()) +} diff --git a/internal/pkg/table/message.go b/internal/pkg/table/message.go new file mode 100644 index 00000000..31b90596 --- /dev/null +++ b/internal/pkg/table/message.go @@ -0,0 +1,502 @@ +// Copyright (C) 2014 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 table + +import ( + "bytes" + "reflect" + + "github.com/osrg/gobgp/pkg/packet/bgp" + log "github.com/sirupsen/logrus" +) + +func UpdatePathAttrs2ByteAs(msg *bgp.BGPUpdate) error { + ps := msg.PathAttributes + msg.PathAttributes = make([]bgp.PathAttributeInterface, len(ps)) + copy(msg.PathAttributes, ps) + var asAttr *bgp.PathAttributeAsPath + idx := 0 + for i, attr := range msg.PathAttributes { + if a, ok := attr.(*bgp.PathAttributeAsPath); ok { + asAttr = a + idx = i + break + } + } + + if asAttr == nil { + return nil + } + + as4Params := make([]*bgp.As4PathParam, 0, len(asAttr.Value)) + as2Params := make([]bgp.AsPathParamInterface, 0, len(asAttr.Value)) + mkAs4 := false + for _, param := range asAttr.Value { + segType := param.GetType() + asList := param.GetAS() + as2Path := make([]uint16, 0, len(asList)) + for _, as := range asList { + if as > (1<<16)-1 { + mkAs4 = true + as2Path = append(as2Path, bgp.AS_TRANS) + } else { + as2Path = append(as2Path, uint16(as)) + } + } + as2Params = append(as2Params, bgp.NewAsPathParam(segType, as2Path)) + + // RFC 6793 4.2.2 Generating Updates + // + // Whenever the AS path information contains the AS_CONFED_SEQUENCE or + // AS_CONFED_SET path segment, the NEW BGP speaker MUST exclude such + // path segments from the AS4_PATH attribute being constructed. + switch segType { + case bgp.BGP_ASPATH_ATTR_TYPE_CONFED_SEQ, bgp.BGP_ASPATH_ATTR_TYPE_CONFED_SET: + // pass + default: + if as4param, ok := param.(*bgp.As4PathParam); ok { + as4Params = append(as4Params, as4param) + } + } + } + msg.PathAttributes[idx] = bgp.NewPathAttributeAsPath(as2Params) + if mkAs4 { + msg.PathAttributes = append(msg.PathAttributes, bgp.NewPathAttributeAs4Path(as4Params)) + } + return nil +} + +func UpdatePathAttrs4ByteAs(msg *bgp.BGPUpdate) error { + var asAttr *bgp.PathAttributeAsPath + var as4Attr *bgp.PathAttributeAs4Path + asAttrPos := 0 + as4AttrPos := 0 + for i, attr := range msg.PathAttributes { + switch attr.(type) { + case *bgp.PathAttributeAsPath: + asAttr = attr.(*bgp.PathAttributeAsPath) + for j, param := range asAttr.Value { + as2Param, ok := param.(*bgp.AsPathParam) + if ok { + asPath := make([]uint32, 0, len(as2Param.AS)) + for _, as := range as2Param.AS { + asPath = append(asPath, uint32(as)) + } + as4Param := bgp.NewAs4PathParam(as2Param.Type, asPath) + asAttr.Value[j] = as4Param + } + } + asAttrPos = i + msg.PathAttributes[i] = asAttr + case *bgp.PathAttributeAs4Path: + as4AttrPos = i + as4Attr = attr.(*bgp.PathAttributeAs4Path) + } + } + + if as4Attr != nil { + msg.PathAttributes = append(msg.PathAttributes[:as4AttrPos], msg.PathAttributes[as4AttrPos+1:]...) + } + + if asAttr == nil || as4Attr == nil { + return nil + } + + asLen := 0 + asConfedLen := 0 + asParams := make([]bgp.AsPathParamInterface, 0, len(asAttr.Value)) + for _, param := range asAttr.Value { + asLen += param.ASLen() + switch param.GetType() { + case bgp.BGP_ASPATH_ATTR_TYPE_CONFED_SET: + asConfedLen++ + case bgp.BGP_ASPATH_ATTR_TYPE_CONFED_SEQ: + asConfedLen += len(param.GetAS()) + } + asParams = append(asParams, param) + } + + as4Len := 0 + as4Params := make([]bgp.AsPathParamInterface, 0, len(as4Attr.Value)) + if as4Attr != nil { + for _, p := range as4Attr.Value { + // RFC 6793 6. Error Handling + // + // the path segment types AS_CONFED_SEQUENCE and AS_CONFED_SET [RFC5065] + // MUST NOT be carried in the AS4_PATH attribute of an UPDATE message. + // A NEW BGP speaker that receives these path segment types in the AS4_PATH + // attribute of an UPDATE message from an OLD BGP speaker MUST discard + // these path segments, adjust the relevant attribute fields accordingly, + // and continue processing the UPDATE message. + // This case SHOULD be logged locally for analysis. + switch p.Type { + case bgp.BGP_ASPATH_ATTR_TYPE_CONFED_SEQ, bgp.BGP_ASPATH_ATTR_TYPE_CONFED_SET: + typ := "CONFED_SEQ" + if p.Type == bgp.BGP_ASPATH_ATTR_TYPE_CONFED_SET { + typ = "CONFED_SET" + } + log.WithFields(log.Fields{ + "Topic": "Table", + }).Warnf("AS4_PATH contains %s segment %s. ignore", typ, p.String()) + continue + } + as4Len += p.ASLen() + as4Params = append(as4Params, p) + } + } + + if asLen+asConfedLen < as4Len { + log.WithFields(log.Fields{ + "Topic": "Table", + }).Warn("AS4_PATH is longer than AS_PATH. ignore AS4_PATH") + return nil + } + + keepNum := asLen + asConfedLen - as4Len + + newParams := make([]bgp.AsPathParamInterface, 0, len(asAttr.Value)) + for _, param := range asParams { + if keepNum-param.ASLen() >= 0 { + newParams = append(newParams, param) + keepNum -= param.ASLen() + } else { + // only SEQ param reaches here + newParams = append(newParams, bgp.NewAs4PathParam(param.GetType(), param.GetAS()[:keepNum])) + keepNum = 0 + } + + if keepNum <= 0 { + break + } + } + + for _, param := range as4Params { + lastParam := newParams[len(newParams)-1] + lastParamAS := lastParam.GetAS() + paramType := param.GetType() + paramAS := param.GetAS() + if paramType == lastParam.GetType() && paramType == bgp.BGP_ASPATH_ATTR_TYPE_SEQ { + if len(lastParamAS)+len(paramAS) > 255 { + newParams[len(newParams)-1] = bgp.NewAs4PathParam(paramType, append(lastParamAS, paramAS[:255-len(lastParamAS)]...)) + newParams = append(newParams, bgp.NewAs4PathParam(paramType, paramAS[255-len(lastParamAS):])) + } else { + newParams[len(newParams)-1] = bgp.NewAs4PathParam(paramType, append(lastParamAS, paramAS...)) + } + } else { + newParams = append(newParams, param) + } + } + + newIntfParams := make([]bgp.AsPathParamInterface, 0, len(asAttr.Value)) + newIntfParams = append(newIntfParams, newParams...) + + msg.PathAttributes[asAttrPos] = bgp.NewPathAttributeAsPath(newIntfParams) + return nil +} + +func UpdatePathAggregator2ByteAs(msg *bgp.BGPUpdate) { + as := uint32(0) + var addr string + for i, attr := range msg.PathAttributes { + switch attr.(type) { + case *bgp.PathAttributeAggregator: + agg := attr.(*bgp.PathAttributeAggregator) + addr = agg.Value.Address.String() + if agg.Value.AS > (1<<16)-1 { + as = agg.Value.AS + msg.PathAttributes[i] = bgp.NewPathAttributeAggregator(uint16(bgp.AS_TRANS), addr) + } else { + msg.PathAttributes[i] = bgp.NewPathAttributeAggregator(uint16(agg.Value.AS), addr) + } + } + } + if as != 0 { + msg.PathAttributes = append(msg.PathAttributes, bgp.NewPathAttributeAs4Aggregator(as, addr)) + } +} + +func UpdatePathAggregator4ByteAs(msg *bgp.BGPUpdate) error { + var aggAttr *bgp.PathAttributeAggregator + var agg4Attr *bgp.PathAttributeAs4Aggregator + agg4AttrPos := 0 + for i, attr := range msg.PathAttributes { + switch attr.(type) { + case *bgp.PathAttributeAggregator: + attr := attr.(*bgp.PathAttributeAggregator) + if attr.Value.Askind == reflect.Uint16 { + aggAttr = attr + aggAttr.Value.Askind = reflect.Uint32 + } + case *bgp.PathAttributeAs4Aggregator: + agg4Attr = attr.(*bgp.PathAttributeAs4Aggregator) + agg4AttrPos = i + } + } + if aggAttr == nil && agg4Attr == nil { + return nil + } + + if aggAttr == nil && agg4Attr != nil { + return bgp.NewMessageError(bgp.BGP_ERROR_UPDATE_MESSAGE_ERROR, bgp.BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST, nil, "AS4 AGGREGATOR attribute exists, but AGGREGATOR doesn't") + } + + if agg4Attr != nil { + msg.PathAttributes = append(msg.PathAttributes[:agg4AttrPos], msg.PathAttributes[agg4AttrPos+1:]...) + aggAttr.Value.AS = agg4Attr.Value.AS + } + return nil +} + +type cage struct { + attrsBytes []byte + paths []*Path +} + +func newCage(b []byte, path *Path) *cage { + return &cage{ + attrsBytes: b, + paths: []*Path{path}, + } +} + +type packerInterface interface { + add(*Path) + pack(options ...*bgp.MarshallingOption) []*bgp.BGPMessage +} + +type packer struct { + eof bool + family bgp.RouteFamily + total uint32 +} + +type packerMP struct { + packer + paths []*Path + withdrawals []*Path +} + +func (p *packerMP) add(path *Path) { + p.packer.total++ + + if path.IsEOR() { + p.packer.eof = true + return + } + + if path.IsWithdraw { + p.withdrawals = append(p.withdrawals, path) + return + } + + p.paths = append(p.paths, path) +} + +func createMPReachMessage(path *Path) *bgp.BGPMessage { + oattrs := path.GetPathAttrs() + attrs := make([]bgp.PathAttributeInterface, 0, len(oattrs)) + for _, a := range oattrs { + if a.GetType() == bgp.BGP_ATTR_TYPE_MP_REACH_NLRI { + attrs = append(attrs, bgp.NewPathAttributeMpReachNLRI(path.GetNexthop().String(), []bgp.AddrPrefixInterface{path.GetNlri()})) + } else { + attrs = append(attrs, a) + } + } + return bgp.NewBGPUpdateMessage(nil, attrs, nil) +} + +func (p *packerMP) pack(options ...*bgp.MarshallingOption) []*bgp.BGPMessage { + msgs := make([]*bgp.BGPMessage, 0, p.packer.total) + + for _, path := range p.withdrawals { + nlris := []bgp.AddrPrefixInterface{path.GetNlri()} + msgs = append(msgs, bgp.NewBGPUpdateMessage(nil, []bgp.PathAttributeInterface{bgp.NewPathAttributeMpUnreachNLRI(nlris)}, nil)) + } + + for _, path := range p.paths { + msgs = append(msgs, createMPReachMessage(path)) + } + + if p.eof { + msgs = append(msgs, bgp.NewEndOfRib(p.family)) + } + return msgs +} + +func newPackerMP(f bgp.RouteFamily) *packerMP { + return &packerMP{ + packer: packer{ + family: f, + }, + withdrawals: make([]*Path, 0), + paths: make([]*Path, 0), + } +} + +type packerV4 struct { + packer + hashmap map[uint32][]*cage + mpPaths []*Path + withdrawals []*Path +} + +func (p *packerV4) add(path *Path) { + p.packer.total++ + + if path.IsEOR() { + p.packer.eof = true + return + } + + if path.IsWithdraw { + p.withdrawals = append(p.withdrawals, path) + return + } + + if path.GetNexthop().To4() == nil { + // RFC 5549 + p.mpPaths = append(p.mpPaths, path) + return + } + + key := path.GetHash() + attrsB := bytes.NewBuffer(make([]byte, 0)) + for _, v := range path.GetPathAttrs() { + b, _ := v.Serialize() + attrsB.Write(b) + } + + if cages, y := p.hashmap[key]; y { + added := false + for _, c := range cages { + if bytes.Equal(c.attrsBytes, attrsB.Bytes()) { + c.paths = append(c.paths, path) + added = true + break + } + } + if !added { + p.hashmap[key] = append(p.hashmap[key], newCage(attrsB.Bytes(), path)) + } + } else { + p.hashmap[key] = []*cage{newCage(attrsB.Bytes(), path)} + } +} + +func (p *packerV4) pack(options ...*bgp.MarshallingOption) []*bgp.BGPMessage { + split := func(max int, paths []*Path) ([]*bgp.IPAddrPrefix, []*Path) { + nlris := make([]*bgp.IPAddrPrefix, 0, max) + i := 0 + if max > len(paths) { + max = len(paths) + } + for ; i < max; i++ { + nlris = append(nlris, paths[i].GetNlri().(*bgp.IPAddrPrefix)) + } + return nlris, paths[i:] + } + addpathNLRILen := 0 + if bgp.IsAddPathEnabled(false, p.packer.family, options) { + addpathNLRILen = 4 + } + // Header + Update (WithdrawnRoutesLen + + // TotalPathAttributeLen + attributes + maxlen of NLRI). + // the max size of NLRI is 5bytes (plus 4bytes with addpath enabled) + maxNLRIs := func(attrsLen int) int { + return (bgp.BGP_MAX_MESSAGE_LENGTH - (19 + 2 + 2 + attrsLen)) / (5 + addpathNLRILen) + } + + loop := func(attrsLen int, paths []*Path, cb func([]*bgp.IPAddrPrefix)) { + max := maxNLRIs(attrsLen) + var nlris []*bgp.IPAddrPrefix + for { + nlris, paths = split(max, paths) + if len(nlris) == 0 { + break + } + cb(nlris) + } + } + + msgs := make([]*bgp.BGPMessage, 0, p.packer.total) + + loop(0, p.withdrawals, func(nlris []*bgp.IPAddrPrefix) { + msgs = append(msgs, bgp.NewBGPUpdateMessage(nlris, nil, nil)) + }) + + for _, cages := range p.hashmap { + for _, c := range cages { + paths := c.paths + + attrs := paths[0].GetPathAttrs() + attrsLen := 0 + for _, a := range attrs { + attrsLen += a.Len() + } + + loop(attrsLen, paths, func(nlris []*bgp.IPAddrPrefix) { + msgs = append(msgs, bgp.NewBGPUpdateMessage(nil, attrs, nlris)) + }) + } + } + + for _, path := range p.mpPaths { + msgs = append(msgs, createMPReachMessage(path)) + } + + if p.eof { + msgs = append(msgs, bgp.NewEndOfRib(p.family)) + } + return msgs +} + +func newPackerV4(f bgp.RouteFamily) *packerV4 { + return &packerV4{ + packer: packer{ + family: f, + }, + hashmap: make(map[uint32][]*cage), + withdrawals: make([]*Path, 0), + mpPaths: make([]*Path, 0), + } +} + +func newPacker(f bgp.RouteFamily) packerInterface { + switch f { + case bgp.RF_IPv4_UC: + return newPackerV4(bgp.RF_IPv4_UC) + default: + return newPackerMP(f) + } +} + +func CreateUpdateMsgFromPaths(pathList []*Path, options ...*bgp.MarshallingOption) []*bgp.BGPMessage { + msgs := make([]*bgp.BGPMessage, 0, len(pathList)) + + m := make(map[bgp.RouteFamily]packerInterface) + for _, path := range pathList { + f := path.GetRouteFamily() + if _, y := m[f]; !y { + m[f] = newPacker(f) + } + m[f].add(path) + } + + for _, p := range m { + msgs = append(msgs, p.pack(options...)...) + } + return msgs +} diff --git a/internal/pkg/table/message_test.go b/internal/pkg/table/message_test.go new file mode 100644 index 00000000..28a380fe --- /dev/null +++ b/internal/pkg/table/message_test.go @@ -0,0 +1,663 @@ +// Copyright (C) 2014 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 table + +import ( + "fmt" + "reflect" + "testing" + "time" + + "github.com/osrg/gobgp/pkg/packet/bgp" + "github.com/stretchr/testify/assert" +) + +// before: +// as-path : 65000, 4000, 400000, 300000, 40001 +// expected result: +// as-path : 65000, 4000, 23456, 23456, 40001 +// as4-path : 65000, 4000, 400000, 300000, 40001 +func TestAsPathAs2Trans1(t *testing.T) { + as := []uint32{65000, 4000, 400000, 300000, 40001} + params := []bgp.AsPathParamInterface{bgp.NewAs4PathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, as)} + aspath := bgp.NewPathAttributeAsPath(params) + msg := bgp.NewBGPUpdateMessage(nil, []bgp.PathAttributeInterface{aspath}, nil).Body.(*bgp.BGPUpdate) + UpdatePathAttrs2ByteAs(msg) + assert.Equal(t, len(msg.PathAttributes), 2) + assert.Equal(t, len(msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value), 1) + assert.Equal(t, len(msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.AsPathParam).AS), 5) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.AsPathParam).AS[0], uint16(65000)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.AsPathParam).AS[1], uint16(4000)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.AsPathParam).AS[2], uint16(bgp.AS_TRANS)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.AsPathParam).AS[3], uint16(bgp.AS_TRANS)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.AsPathParam).AS[4], uint16(40001)) + assert.Equal(t, len(msg.PathAttributes[1].(*bgp.PathAttributeAs4Path).Value), 1) + assert.Equal(t, len(msg.PathAttributes[1].(*bgp.PathAttributeAs4Path).Value[0].AS), 5) + assert.Equal(t, msg.PathAttributes[1].(*bgp.PathAttributeAs4Path).Value[0].AS[0], uint32(65000)) + assert.Equal(t, msg.PathAttributes[1].(*bgp.PathAttributeAs4Path).Value[0].AS[1], uint32(4000)) + assert.Equal(t, msg.PathAttributes[1].(*bgp.PathAttributeAs4Path).Value[0].AS[2], uint32(400000)) + assert.Equal(t, msg.PathAttributes[1].(*bgp.PathAttributeAs4Path).Value[0].AS[3], uint32(300000)) + assert.Equal(t, msg.PathAttributes[1].(*bgp.PathAttributeAs4Path).Value[0].AS[4], uint32(40001)) +} + +// before: +// as-path : 65000, 4000, 40000, 30000, 40001 +// expected result: +// as-path : 65000, 4000, 40000, 30000, 40001 +func TestAsPathAs2Trans2(t *testing.T) { + as := []uint32{65000, 4000, 40000, 30000, 40001} + params := []bgp.AsPathParamInterface{bgp.NewAs4PathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, as)} + aspath := bgp.NewPathAttributeAsPath(params) + msg := bgp.NewBGPUpdateMessage(nil, []bgp.PathAttributeInterface{aspath}, nil).Body.(*bgp.BGPUpdate) + UpdatePathAttrs2ByteAs(msg) + assert.Equal(t, len(msg.PathAttributes), 1) + assert.Equal(t, len(msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value), 1) + assert.Equal(t, len(msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.AsPathParam).AS), 5) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.AsPathParam).AS[0], uint16(65000)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.AsPathParam).AS[1], uint16(4000)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.AsPathParam).AS[2], uint16(40000)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.AsPathParam).AS[3], uint16(30000)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.AsPathParam).AS[4], uint16(40001)) +} + +// before: +// as-path : 65000, 4000, 23456, 23456, 40001 +// as4-path : 400000, 300000, 40001 +// expected result: +// as-path : 65000, 4000, 400000, 300000, 40001 +func TestAsPathAs4Trans1(t *testing.T) { + as := []uint16{65000, 4000, bgp.AS_TRANS, bgp.AS_TRANS, 40001} + params := []bgp.AsPathParamInterface{bgp.NewAsPathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, as)} + aspath := bgp.NewPathAttributeAsPath(params) + + as4 := []uint32{400000, 300000, 40001} + param4s := []*bgp.As4PathParam{bgp.NewAs4PathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, as4)} + as4path := bgp.NewPathAttributeAs4Path(param4s) + msg := bgp.NewBGPUpdateMessage(nil, []bgp.PathAttributeInterface{aspath, as4path}, nil).Body.(*bgp.BGPUpdate) + UpdatePathAttrs4ByteAs(msg) + assert.Equal(t, len(msg.PathAttributes), 1) + assert.Equal(t, len(msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value), 1) + assert.Equal(t, len(msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS), 5) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[0], uint32(65000)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[1], uint32(4000)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[2], uint32(400000)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[3], uint32(300000)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[4], uint32(40001)) +} + +// before: +// as-path : 65000, 4000, {10, 20, 30}, 23456, 23456, 40001 +// as4-path : 400000, 300000, 40001 +// expected result: +// as-path : 65000, 4000, {10, 20, 30}, 400000, 300000, 40001 +func TestAsPathAs4Trans2(t *testing.T) { + as1 := []uint16{65000, 4000} + param1 := bgp.NewAsPathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, as1) + as2 := []uint16{10, 20, 30} + param2 := bgp.NewAsPathParam(bgp.BGP_ASPATH_ATTR_TYPE_SET, as2) + as3 := []uint16{bgp.AS_TRANS, bgp.AS_TRANS, 40001} + param3 := bgp.NewAsPathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, as3) + params := []bgp.AsPathParamInterface{param1, param2, param3} + aspath := bgp.NewPathAttributeAsPath(params) + + as4 := []uint32{400000, 300000, 40001} + param4s := []*bgp.As4PathParam{bgp.NewAs4PathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, as4)} + as4path := bgp.NewPathAttributeAs4Path(param4s) + msg := bgp.NewBGPUpdateMessage(nil, []bgp.PathAttributeInterface{aspath, as4path}, nil).Body.(*bgp.BGPUpdate) + UpdatePathAttrs4ByteAs(msg) + assert.Equal(t, len(msg.PathAttributes), 1) + assert.Equal(t, len(msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value), 3) + assert.Equal(t, len(msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS), 2) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[0], uint32(65000)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[1], uint32(4000)) + assert.Equal(t, len(msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[1].(*bgp.As4PathParam).AS), 3) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[1].(*bgp.As4PathParam).AS[0], uint32(10)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[1].(*bgp.As4PathParam).AS[1], uint32(20)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[1].(*bgp.As4PathParam).AS[2], uint32(30)) + assert.Equal(t, len(msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[2].(*bgp.As4PathParam).AS), 3) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[2].(*bgp.As4PathParam).AS[0], uint32(400000)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[2].(*bgp.As4PathParam).AS[1], uint32(300000)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[2].(*bgp.As4PathParam).AS[2], uint32(40001)) +} + +// before: +// as-path : 65000, 4000, {10, 20, 30}, 23456, 23456, 40001 +// as4-path : 3000, 400000, 300000, 40001 +// expected result: +// as-path : 65000, 4000, 3000, 400000, 300000, 40001 +func TestAsPathAs4Trans3(t *testing.T) { + as1 := []uint16{65000, 4000} + param1 := bgp.NewAsPathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, as1) + as2 := []uint16{10, 20, 30} + param2 := bgp.NewAsPathParam(bgp.BGP_ASPATH_ATTR_TYPE_SET, as2) + as3 := []uint16{bgp.AS_TRANS, bgp.AS_TRANS, 40001} + param3 := bgp.NewAsPathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, as3) + params := []bgp.AsPathParamInterface{param1, param2, param3} + aspath := bgp.NewPathAttributeAsPath(params) + + as4 := []uint32{3000, 400000, 300000, 40001} + param4s := []*bgp.As4PathParam{bgp.NewAs4PathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, as4)} + as4path := bgp.NewPathAttributeAs4Path(param4s) + msg := bgp.NewBGPUpdateMessage(nil, []bgp.PathAttributeInterface{aspath, as4path}, nil).Body.(*bgp.BGPUpdate) + UpdatePathAttrs4ByteAs(msg) + assert.Equal(t, len(msg.PathAttributes), 1) + assert.Equal(t, len(msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value), 1) + assert.Equal(t, len(msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS), 6) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[0], uint32(65000)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[1], uint32(4000)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[2], uint32(3000)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[3], uint32(400000)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[4], uint32(300000)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[5], uint32(40001)) +} + +// before: +// as-path : 65000, 4000, 23456, 23456, 40001 +// as4-path : 400000, 300000, 40001, {10, 20, 30} +// expected result: +// as-path : 65000, 400000, 300000, 40001, {10, 20, 30} +func TestAsPathAs4Trans4(t *testing.T) { + as := []uint16{65000, 4000, bgp.AS_TRANS, bgp.AS_TRANS, 40001} + params := []bgp.AsPathParamInterface{bgp.NewAsPathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, as)} + aspath := bgp.NewPathAttributeAsPath(params) + + as4 := []uint32{400000, 300000, 40001} + as4param1 := bgp.NewAs4PathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, as4) + as5 := []uint32{10, 20, 30} + as4param2 := bgp.NewAs4PathParam(bgp.BGP_ASPATH_ATTR_TYPE_SET, as5) + param4s := []*bgp.As4PathParam{as4param1, as4param2} + as4path := bgp.NewPathAttributeAs4Path(param4s) + msg := bgp.NewBGPUpdateMessage(nil, []bgp.PathAttributeInterface{aspath, as4path}, nil).Body.(*bgp.BGPUpdate) + UpdatePathAttrs4ByteAs(msg) + assert.Equal(t, len(msg.PathAttributes), 1) + assert.Equal(t, len(msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value), 2) + assert.Equal(t, len(msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS), 4) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[0], uint32(65000)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[1], uint32(400000)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[2], uint32(300000)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[3], uint32(40001)) + assert.Equal(t, len(msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[1].(*bgp.As4PathParam).AS), 3) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[1].(*bgp.As4PathParam).AS[0], uint32(10)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[1].(*bgp.As4PathParam).AS[1], uint32(20)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[1].(*bgp.As4PathParam).AS[2], uint32(30)) +} + +// before: +// as-path : 65000, 4000, 23456, 23456, 40001 +// as4-path : {10, 20, 30} 400000, 300000, 40001 +// expected result: +// as-path : 65000, {10, 20, 30}, 400000, 300000, 40001 +func TestAsPathAs4Trans5(t *testing.T) { + as := []uint16{65000, 4000, bgp.AS_TRANS, bgp.AS_TRANS, 40001} + params := []bgp.AsPathParamInterface{bgp.NewAsPathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, as)} + aspath := bgp.NewPathAttributeAsPath(params) + + as4 := []uint32{400000, 300000, 40001} + as4param1 := bgp.NewAs4PathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, as4) + as5 := []uint32{10, 20, 30} + as4param2 := bgp.NewAs4PathParam(bgp.BGP_ASPATH_ATTR_TYPE_SET, as5) + param4s := []*bgp.As4PathParam{as4param2, as4param1} + as4path := bgp.NewPathAttributeAs4Path(param4s) + msg := bgp.NewBGPUpdateMessage(nil, []bgp.PathAttributeInterface{aspath, as4path}, nil).Body.(*bgp.BGPUpdate) + UpdatePathAttrs4ByteAs(msg) + assert.Equal(t, len(msg.PathAttributes), 1) + assert.Equal(t, len(msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value), 3) + assert.Equal(t, len(msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS), 1) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[0], uint32(65000)) + assert.Equal(t, len(msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[1].(*bgp.As4PathParam).AS), 3) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[1].(*bgp.As4PathParam).AS[0], uint32(10)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[1].(*bgp.As4PathParam).AS[1], uint32(20)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[1].(*bgp.As4PathParam).AS[2], uint32(30)) + assert.Equal(t, len(msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[2].(*bgp.As4PathParam).AS), 3) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[2].(*bgp.As4PathParam).AS[0], uint32(400000)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[2].(*bgp.As4PathParam).AS[1], uint32(300000)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[2].(*bgp.As4PathParam).AS[2], uint32(40001)) +} + +// before: +// as-path : 65000, 4000, 23456, 23456, 40001 +// as4-path : 100000, 65000, 4000, 400000, 300000, 40001 +// expected result: +// as-path : 65000, 4000, 23456, 23456, 40001 +func TestAsPathAs4TransInvalid1(t *testing.T) { + as := []uint16{65000, 4000, bgp.AS_TRANS, bgp.AS_TRANS, 40001} + params := []bgp.AsPathParamInterface{bgp.NewAsPathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, as)} + aspath := bgp.NewPathAttributeAsPath(params) + + as4 := []uint32{100000, 65000, 4000, 400000, 300000, 40001} + as4param1 := bgp.NewAs4PathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, as4) + param4s := []*bgp.As4PathParam{as4param1} + as4path := bgp.NewPathAttributeAs4Path(param4s) + msg := bgp.NewBGPUpdateMessage(nil, []bgp.PathAttributeInterface{aspath, as4path}, nil).Body.(*bgp.BGPUpdate) + UpdatePathAttrs4ByteAs(msg) + assert.Equal(t, len(msg.PathAttributes), 1) + assert.Equal(t, len(msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value), 1) + assert.Equal(t, len(msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS), 5) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[0], uint32(65000)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[1], uint32(4000)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[2], uint32(bgp.AS_TRANS)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[3], uint32(bgp.AS_TRANS)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[4], uint32(40001)) +} + +// before: +// as-path : 65000, 4000, 23456, 23456, 40001 +// as4-path : 300000, 40001 +// expected result: +// as-path : 65000, 4000, 23456, 300000, 40001 +func TestAsPathAs4TransInvalid2(t *testing.T) { + as := []uint16{65000, 4000, bgp.AS_TRANS, bgp.AS_TRANS, 40001} + params := []bgp.AsPathParamInterface{bgp.NewAsPathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, as)} + aspath := bgp.NewPathAttributeAsPath(params) + + as4 := []uint32{300000, 40001} + as4param1 := bgp.NewAs4PathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, as4) + param4s := []*bgp.As4PathParam{as4param1} + as4path := bgp.NewPathAttributeAs4Path(param4s) + msg := bgp.NewBGPUpdateMessage(nil, []bgp.PathAttributeInterface{aspath, as4path}, nil).Body.(*bgp.BGPUpdate) + UpdatePathAttrs4ByteAs(msg) + assert.Equal(t, len(msg.PathAttributes), 1) + assert.Equal(t, len(msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value), 1) + assert.Equal(t, len(msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS), 5) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[0], uint32(65000)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[1], uint32(4000)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[2], uint32(bgp.AS_TRANS)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[3], uint32(300000)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[4], uint32(40001)) +} + +// before: +// as-path : 65000, 4000, 23456, 23456, 40001 +// as4-path : nil +// expected result: +// as-path : 65000, 4000, 23456, 23456, 40001 +func TestAsPathAs4TransInvalid3(t *testing.T) { + as := []uint16{65000, 4000, bgp.AS_TRANS, bgp.AS_TRANS, 40001} + params := []bgp.AsPathParamInterface{bgp.NewAsPathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, as)} + aspath := bgp.NewPathAttributeAsPath(params) + + msg := bgp.NewBGPUpdateMessage(nil, []bgp.PathAttributeInterface{aspath}, nil).Body.(*bgp.BGPUpdate) + UpdatePathAttrs4ByteAs(msg) + assert.Equal(t, len(msg.PathAttributes), 1) + assert.Equal(t, len(msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value), 1) + assert.Equal(t, len(msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS), 5) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[0], uint32(65000)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[1], uint32(4000)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[2], uint32(bgp.AS_TRANS)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[3], uint32(bgp.AS_TRANS)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[4], uint32(40001)) +} + +// before: +// as-path : 65000, 4000, 23456, 23456, 40001 +// as4-path : empty +// expected result: +// as-path : 65000, 4000, 23456, 23456, 40001 +func TestAsPathAs4TransInvalid4(t *testing.T) { + as := []uint16{65000, 4000, bgp.AS_TRANS, bgp.AS_TRANS, 40001} + params := []bgp.AsPathParamInterface{bgp.NewAsPathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, as)} + aspath := bgp.NewPathAttributeAsPath(params) + + as4 := []uint32{} + as4param1 := bgp.NewAs4PathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, as4) + param4s := []*bgp.As4PathParam{as4param1} + as4path := bgp.NewPathAttributeAs4Path(param4s) + msg := bgp.NewBGPUpdateMessage(nil, []bgp.PathAttributeInterface{aspath, as4path}, nil).Body.(*bgp.BGPUpdate) + UpdatePathAttrs4ByteAs(msg) + assert.Equal(t, len(msg.PathAttributes), 1) + assert.Equal(t, len(msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value), 1) + assert.Equal(t, len(msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS), 5) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[0], uint32(65000)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[1], uint32(4000)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[2], uint32(bgp.AS_TRANS)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[3], uint32(bgp.AS_TRANS)) + assert.Equal(t, msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value[0].(*bgp.As4PathParam).AS[4], uint32(40001)) +} + +func TestASPathAs4TransMultipleParams(t *testing.T) { + as1 := []uint16{17676, 2914, 174, 50607} + as2 := []uint16{bgp.AS_TRANS, bgp.AS_TRANS} + params := []bgp.AsPathParamInterface{bgp.NewAsPathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, as1), bgp.NewAsPathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, as2)} + aspath := bgp.NewPathAttributeAsPath(params) + + as41 := []uint32{2914, 174, 50607} + as42 := []uint32{198035, 198035} + as4param1 := bgp.NewAs4PathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, as41) + as4param2 := bgp.NewAs4PathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, as42) + param4s := []*bgp.As4PathParam{as4param1, as4param2} + as4path := bgp.NewPathAttributeAs4Path(param4s) + msg := bgp.NewBGPUpdateMessage(nil, []bgp.PathAttributeInterface{aspath, as4path}, nil).Body.(*bgp.BGPUpdate) + UpdatePathAttrs4ByteAs(msg) + for _, param := range msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value { + p := param.(*bgp.As4PathParam) + assert.Equal(t, p.Num, uint8(len(p.AS))) + } +} + +func TestASPathAs4TransMultipleLargeParams(t *testing.T) { + as1 := make([]uint16, 0, 255) + for i := 0; i < 255-5; i++ { + as1 = append(as1, uint16(i+1)) + } + as1 = append(as1, []uint16{17676, 2914, 174, 50607}...) + as2 := []uint16{bgp.AS_TRANS, bgp.AS_TRANS} + params := []bgp.AsPathParamInterface{bgp.NewAsPathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, as1), bgp.NewAsPathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, as2)} + aspath := bgp.NewPathAttributeAsPath(params) + + as41 := []uint32{2914, 174, 50607} + as42 := []uint32{198035, 198035} + as4param1 := bgp.NewAs4PathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, as41) + as4param2 := bgp.NewAs4PathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, as42) + param4s := []*bgp.As4PathParam{as4param1, as4param2} + as4path := bgp.NewPathAttributeAs4Path(param4s) + msg := bgp.NewBGPUpdateMessage(nil, []bgp.PathAttributeInterface{aspath, as4path}, nil).Body.(*bgp.BGPUpdate) + UpdatePathAttrs4ByteAs(msg) + for _, param := range msg.PathAttributes[0].(*bgp.PathAttributeAsPath).Value { + p := param.(*bgp.As4PathParam) + assert.Equal(t, p.Num, uint8(len(p.AS))) + } +} + +func TestAggregator4BytesASes(t *testing.T) { + getAggr := func(msg *bgp.BGPUpdate) *bgp.PathAttributeAggregator { + for _, attr := range msg.PathAttributes { + switch attr.(type) { + case *bgp.PathAttributeAggregator: + return attr.(*bgp.PathAttributeAggregator) + } + } + return nil + } + + getAggr4 := func(msg *bgp.BGPUpdate) *bgp.PathAttributeAs4Aggregator { + for _, attr := range msg.PathAttributes { + switch attr.(type) { + case *bgp.PathAttributeAs4Aggregator: + return attr.(*bgp.PathAttributeAs4Aggregator) + } + } + return nil + } + + addr := "192.168.0.1" + as4 := uint32(100000) + as := uint32(1000) + msg := bgp.NewBGPUpdateMessage(nil, []bgp.PathAttributeInterface{bgp.NewPathAttributeAggregator(as4, addr)}, nil).Body.(*bgp.BGPUpdate) + + // 4byte capable to 4byte capable for 4 bytes AS + assert.Equal(t, UpdatePathAggregator4ByteAs(msg), nil) + assert.Equal(t, getAggr(msg).Value.AS, as4) + assert.Equal(t, getAggr(msg).Value.Address.String(), addr) + + // 4byte capable to 2byte capable for 4 bytes AS + UpdatePathAggregator2ByteAs(msg) + assert.Equal(t, getAggr(msg).Value.AS, uint32(bgp.AS_TRANS)) + assert.Equal(t, getAggr(msg).Value.Askind, reflect.Uint16) + assert.Equal(t, getAggr4(msg).Value.AS, as4) + assert.Equal(t, getAggr4(msg).Value.Address.String(), addr) + + msg = bgp.NewBGPUpdateMessage(nil, []bgp.PathAttributeInterface{bgp.NewPathAttributeAggregator(uint16(bgp.AS_TRANS), addr), bgp.NewPathAttributeAs4Aggregator(as4, addr)}, nil).Body.(*bgp.BGPUpdate) + assert.Equal(t, getAggr(msg).Value.AS, uint32(bgp.AS_TRANS)) + assert.Equal(t, getAggr(msg).Value.Askind, reflect.Uint16) + + // non 4byte capable to 4byte capable for 4 bytes AS + assert.Equal(t, UpdatePathAggregator4ByteAs(msg), nil) + assert.Equal(t, getAggr(msg).Value.AS, as4) + assert.Equal(t, getAggr(msg).Value.Askind, reflect.Uint32) + assert.Equal(t, getAggr(msg).Value.Address.String(), addr) + assert.Equal(t, getAggr4(msg), (*bgp.PathAttributeAs4Aggregator)(nil)) + + // non 4byte capable to non 4byte capable for 4 bytes AS + UpdatePathAggregator2ByteAs(msg) + assert.Equal(t, getAggr(msg).Value.AS, uint32(bgp.AS_TRANS)) + assert.Equal(t, getAggr(msg).Value.Askind, reflect.Uint16) + assert.Equal(t, getAggr4(msg).Value.AS, as4) + assert.Equal(t, getAggr4(msg).Value.Address.String(), addr) + + msg = bgp.NewBGPUpdateMessage(nil, []bgp.PathAttributeInterface{bgp.NewPathAttributeAggregator(uint32(as), addr)}, nil).Body.(*bgp.BGPUpdate) + // 4byte capable to 4byte capable for 2 bytes AS + assert.Equal(t, getAggr(msg).Value.AS, as) + assert.Equal(t, getAggr(msg).Value.Askind, reflect.Uint32) + assert.Equal(t, UpdatePathAggregator4ByteAs(msg), nil) + assert.Equal(t, getAggr(msg).Value.AS, as) + assert.Equal(t, getAggr(msg).Value.Askind, reflect.Uint32) + + // 4byte capable to non 4byte capable for 2 bytes AS + UpdatePathAggregator2ByteAs(msg) + assert.Equal(t, getAggr4(msg), (*bgp.PathAttributeAs4Aggregator)(nil)) + assert.Equal(t, getAggr(msg).Value.Askind, reflect.Uint16) + assert.Equal(t, getAggr(msg).Value.AS, as) + + msg = bgp.NewBGPUpdateMessage(nil, []bgp.PathAttributeInterface{bgp.NewPathAttributeAggregator(uint16(as), addr)}, nil).Body.(*bgp.BGPUpdate) + // non 4byte capable to 4byte capable for 2 bytes AS + assert.Equal(t, getAggr(msg).Value.AS, as) + assert.Equal(t, getAggr(msg).Value.Askind, reflect.Uint16) + assert.Equal(t, UpdatePathAggregator4ByteAs(msg), nil) + + assert.Equal(t, getAggr(msg).Value.AS, as) + assert.Equal(t, getAggr(msg).Value.Askind, reflect.Uint32) + + // non 4byte capable to non 4byte capable for 2 bytes AS + UpdatePathAggregator2ByteAs(msg) + assert.Equal(t, getAggr(msg).Value.AS, as) + assert.Equal(t, getAggr(msg).Value.Askind, reflect.Uint16) + assert.Equal(t, getAggr4(msg), (*bgp.PathAttributeAs4Aggregator)(nil)) +} + +func TestBMP(t *testing.T) { + aspath1 := []bgp.AsPathParamInterface{ + bgp.NewAs4PathParam(2, []uint32{1000000}), + bgp.NewAs4PathParam(1, []uint32{1000001, 1002}), + bgp.NewAs4PathParam(2, []uint32{1003, 100004}), + } + mp_nlri := []bgp.AddrPrefixInterface{bgp.NewIPv6AddrPrefix(100, + "fe80:1234:1234:5667:8967:af12:8912:1023")} + + p := []bgp.PathAttributeInterface{ + bgp.NewPathAttributeOrigin(3), + bgp.NewPathAttributeAsPath(aspath1), + bgp.NewPathAttributeMpUnreachNLRI(mp_nlri), + } + w := []*bgp.IPAddrPrefix{} + n := []*bgp.IPAddrPrefix{} + + msg := bgp.NewBGPUpdateMessage(w, p, n) + pList := ProcessMessage(msg, peerR1(), time.Now()) + CreateUpdateMsgFromPaths(pList) +} + +func unreachIndex(msgs []*bgp.BGPMessage) int { + for i, _ := range msgs { + for _, a := range msgs[i].Body.(*bgp.BGPUpdate).PathAttributes { + if a.GetType() == bgp.BGP_ATTR_TYPE_MP_UNREACH_NLRI { + return i + } + } + } + // should not be here + return -1 +} + +func TestMixedMPReachMPUnreach(t *testing.T) { + aspath1 := []bgp.AsPathParamInterface{ + bgp.NewAs4PathParam(2, []uint32{100}), + } + nlri1 := []bgp.AddrPrefixInterface{bgp.NewIPv6AddrPrefix(32, "2222::")} + nlri2 := []bgp.AddrPrefixInterface{bgp.NewIPv6AddrPrefix(32, "1111::")} + + p := []bgp.PathAttributeInterface{ + bgp.NewPathAttributeOrigin(0), + bgp.NewPathAttributeAsPath(aspath1), + bgp.NewPathAttributeMpReachNLRI("1::1", nlri1), + bgp.NewPathAttributeMpUnreachNLRI(nlri2), + } + msg := bgp.NewBGPUpdateMessage(nil, p, nil) + pList := ProcessMessage(msg, peerR1(), time.Now()) + assert.Equal(t, len(pList), 2) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.Equal(t, pList[1].IsWithdraw, true) + msgs := CreateUpdateMsgFromPaths(pList) + assert.Equal(t, len(msgs), 2) + + uIndex := unreachIndex(msgs) + rIndex := 0 + if uIndex == 0 { + rIndex = 1 + } + assert.Equal(t, len(msgs[uIndex].Body.(*bgp.BGPUpdate).PathAttributes), 1) + assert.Equal(t, len(msgs[rIndex].Body.(*bgp.BGPUpdate).PathAttributes), 3) +} + +func TestMixedNLRIAndMPUnreach(t *testing.T) { + aspath1 := []bgp.AsPathParamInterface{ + bgp.NewAs4PathParam(2, []uint32{100}), + } + nlri1 := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.0.0.0")} + nlri2 := []bgp.AddrPrefixInterface{bgp.NewIPv6AddrPrefix(32, "1111::")} + + p := []bgp.PathAttributeInterface{ + bgp.NewPathAttributeOrigin(0), + bgp.NewPathAttributeAsPath(aspath1), + bgp.NewPathAttributeNextHop("1.1.1.1"), + bgp.NewPathAttributeMpUnreachNLRI(nlri2), + } + msg := bgp.NewBGPUpdateMessage(nil, p, nlri1) + pList := ProcessMessage(msg, peerR1(), time.Now()) + + assert.Equal(t, len(pList), 2) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.Equal(t, pList[1].IsWithdraw, true) + msgs := CreateUpdateMsgFromPaths(pList) + assert.Equal(t, len(msgs), 2) + + uIndex := unreachIndex(msgs) + rIndex := 0 + if uIndex == 0 { + rIndex = 1 + } + assert.Equal(t, len(msgs[uIndex].Body.(*bgp.BGPUpdate).PathAttributes), 1) + assert.Equal(t, len(msgs[rIndex].Body.(*bgp.BGPUpdate).PathAttributes), 3) +} + +func TestMergeV4NLRIs(t *testing.T) { + aspath1 := []bgp.AsPathParamInterface{ + bgp.NewAs4PathParam(2, []uint32{100}), + } + attrs := []bgp.PathAttributeInterface{ + bgp.NewPathAttributeOrigin(0), + bgp.NewPathAttributeAsPath(aspath1), + bgp.NewPathAttributeNextHop("1.1.1.1"), + } + + nr := 1024 + paths := make([]*Path, 0, nr) + addrs := make([]string, 0, nr) + for i := 0; i < nr; i++ { + addrs = append(addrs, fmt.Sprintf("1.1.%d.%d", i>>8&0xff, i&0xff)) + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(32, addrs[i])} + msg := bgp.NewBGPUpdateMessage(nil, attrs, nlri) + paths = append(paths, ProcessMessage(msg, peerR1(), time.Now())...) + } + msgs := CreateUpdateMsgFromPaths(paths) + assert.Equal(t, len(msgs), 2) + + l := make([]*bgp.IPAddrPrefix, 0, nr) + for _, msg := range msgs { + u := msg.Body.(*bgp.BGPUpdate) + assert.Equal(t, len(u.PathAttributes), 3) + l = append(l, u.NLRI...) + } + + assert.Equal(t, len(l), nr) + for i, addr := range addrs { + assert.Equal(t, addr, l[i].Prefix.String()) + } + for _, msg := range msgs { + d, _ := msg.Serialize() + assert.True(t, len(d) < bgp.BGP_MAX_MESSAGE_LENGTH) + } +} + +func TestNotMergeV4NLRIs(t *testing.T) { + paths := make([]*Path, 0, 2) + + aspath1 := []bgp.AsPathParamInterface{ + bgp.NewAs4PathParam(2, []uint32{100}), + } + attrs1 := []bgp.PathAttributeInterface{ + bgp.NewPathAttributeOrigin(0), + bgp.NewPathAttributeAsPath(aspath1), + bgp.NewPathAttributeNextHop("1.1.1.1"), + } + nlri1 := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(32, "1.1.1.1")} + paths = append(paths, ProcessMessage(bgp.NewBGPUpdateMessage(nil, attrs1, nlri1), peerR1(), time.Now())...) + + attrs2 := []bgp.PathAttributeInterface{ + bgp.NewPathAttributeOrigin(0), + bgp.NewPathAttributeAsPath(aspath1), + bgp.NewPathAttributeNextHop("2.2.2.2"), + } + nlri2 := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(32, "2.2.2.2")} + paths = append(paths, ProcessMessage(bgp.NewBGPUpdateMessage(nil, attrs2, nlri2), peerR1(), time.Now())...) + + assert.NotEmpty(t, paths[0].GetHash(), paths[1].GetHash()) + + msgs := CreateUpdateMsgFromPaths(paths) + assert.Equal(t, len(msgs), 2) + + paths[1].SetHash(paths[0].GetHash()) + msgs = CreateUpdateMsgFromPaths(paths) + assert.Equal(t, len(msgs), 2) +} + +func TestMergeV4Withdraw(t *testing.T) { + nr := 1024 + paths := make([]*Path, 0, nr) + addrs := make([]string, 0, nr) + for i := 0; i < nr; i++ { + addrs = append(addrs, fmt.Sprintf("1.1.%d.%d", i>>8&0xff, i&0xff)) + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(32, addrs[i])} + // use different attribute for each nlri + aspath1 := []bgp.AsPathParamInterface{ + bgp.NewAs4PathParam(2, []uint32{uint32(i)}), + } + attrs := []bgp.PathAttributeInterface{ + bgp.NewPathAttributeOrigin(0), + bgp.NewPathAttributeAsPath(aspath1), + bgp.NewPathAttributeNextHop("1.1.1.1"), + } + msg := bgp.NewBGPUpdateMessage(nlri, attrs, nil) + paths = append(paths, ProcessMessage(msg, peerR1(), time.Now())...) + } + msgs := CreateUpdateMsgFromPaths(paths) + assert.Equal(t, len(msgs), 2) + + l := make([]*bgp.IPAddrPrefix, 0, nr) + for _, msg := range msgs { + u := msg.Body.(*bgp.BGPUpdate) + assert.Equal(t, len(u.PathAttributes), 0) + l = append(l, u.WithdrawnRoutes...) + } + assert.Equal(t, len(l), nr) + for i, addr := range addrs { + assert.Equal(t, addr, l[i].Prefix.String()) + } + + for _, msg := range msgs { + d, _ := msg.Serialize() + assert.True(t, len(d) < bgp.BGP_MAX_MESSAGE_LENGTH) + } +} diff --git a/internal/pkg/table/path.go b/internal/pkg/table/path.go new file mode 100644 index 00000000..14bbe6ae --- /dev/null +++ b/internal/pkg/table/path.go @@ -0,0 +1,1179 @@ +// Copyright (C) 2014 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 table + +import ( + "bytes" + "encoding/json" + "fmt" + "math" + "net" + "sort" + "time" + + "github.com/osrg/gobgp/internal/pkg/config" + "github.com/osrg/gobgp/pkg/packet/bgp" + + log "github.com/sirupsen/logrus" +) + +const ( + DEFAULT_LOCAL_PREF = 100 +) + +type Bitmap struct { + bitmap []uint64 +} + +func (b *Bitmap) Flag(i uint) { + b.bitmap[i/64] |= 1 << uint(i%64) +} + +func (b *Bitmap) Unflag(i uint) { + b.bitmap[i/64] &^= 1 << uint(i%64) +} + +func (b *Bitmap) GetFlag(i uint) bool { + return b.bitmap[i/64]&(1<<uint(i%64)) > 0 +} + +func (b *Bitmap) FindandSetZeroBit() (uint, error) { + for i := 0; i < len(b.bitmap); i++ { + if b.bitmap[i] == math.MaxUint64 { + continue + } + // replace this with TrailingZero64() when gobgp drops go 1.8 support. + for j := 0; j < 64; j++ { + v := ^b.bitmap[i] + if v&(1<<uint64(j)) > 0 { + r := i*64 + j + b.Flag(uint(r)) + return uint(r), nil + } + } + } + return 0, fmt.Errorf("no space") +} + +func (b *Bitmap) Expand() { + old := b.bitmap + new := make([]uint64, len(old)+1) + for i := 0; i < len(old); i++ { + new[i] = old[i] + } + b.bitmap = new +} + +func NewBitmap(size int) *Bitmap { + b := &Bitmap{} + if size != 0 { + b.bitmap = make([]uint64, (size+64-1)/64) + } + return b +} + +type originInfo struct { + nlri bgp.AddrPrefixInterface + source *PeerInfo + timestamp int64 + validation *Validation + noImplicitWithdraw bool + isFromExternal bool + eor bool + stale bool +} + +type RpkiValidationReasonType string + +const ( + RPKI_VALIDATION_REASON_TYPE_NONE RpkiValidationReasonType = "none" + RPKI_VALIDATION_REASON_TYPE_AS RpkiValidationReasonType = "as" + RPKI_VALIDATION_REASON_TYPE_LENGTH RpkiValidationReasonType = "length" +) + +var RpkiValidationReasonTypeToIntMap = map[RpkiValidationReasonType]int{ + RPKI_VALIDATION_REASON_TYPE_NONE: 0, + RPKI_VALIDATION_REASON_TYPE_AS: 1, + RPKI_VALIDATION_REASON_TYPE_LENGTH: 2, +} + +func (v RpkiValidationReasonType) ToInt() int { + i, ok := RpkiValidationReasonTypeToIntMap[v] + if !ok { + return -1 + } + return i +} + +var IntToRpkiValidationReasonTypeMap = map[int]RpkiValidationReasonType{ + 0: RPKI_VALIDATION_REASON_TYPE_NONE, + 1: RPKI_VALIDATION_REASON_TYPE_AS, + 2: RPKI_VALIDATION_REASON_TYPE_LENGTH, +} + +type Validation struct { + Status config.RpkiValidationResultType + Reason RpkiValidationReasonType + Matched []*ROA + UnmatchedAs []*ROA + UnmatchedLength []*ROA +} + +type Path struct { + info *originInfo + parent *Path + pathAttrs []bgp.PathAttributeInterface + dels []bgp.BGPAttrType + attrsHash uint32 + aslooped bool + reason BestPathReason + + // For BGP Nexthop Tracking, this field shows if nexthop is invalidated by IGP. + IsNexthopInvalid bool + IsWithdraw bool +} + +func NewPath(source *PeerInfo, nlri bgp.AddrPrefixInterface, isWithdraw bool, pattrs []bgp.PathAttributeInterface, timestamp time.Time, noImplicitWithdraw bool) *Path { + if !isWithdraw && pattrs == nil { + log.WithFields(log.Fields{ + "Topic": "Table", + "Key": nlri.String(), + }).Error("Need to provide path attributes for non-withdrawn path.") + return nil + } + + return &Path{ + info: &originInfo{ + nlri: nlri, + source: source, + timestamp: timestamp.Unix(), + noImplicitWithdraw: noImplicitWithdraw, + }, + IsWithdraw: isWithdraw, + pathAttrs: pattrs, + } +} + +func NewEOR(family bgp.RouteFamily) *Path { + afi, safi := bgp.RouteFamilyToAfiSafi(family) + nlri, _ := bgp.NewPrefixFromRouteFamily(afi, safi) + return &Path{ + info: &originInfo{ + nlri: nlri, + eor: true, + }, + } +} + +func (path *Path) IsEOR() bool { + if path.info != nil && path.info.eor { + return true + } + return false +} + +func cloneAsPath(asAttr *bgp.PathAttributeAsPath) *bgp.PathAttributeAsPath { + newASparams := make([]bgp.AsPathParamInterface, len(asAttr.Value)) + for i, param := range asAttr.Value { + asList := param.GetAS() + as := make([]uint32, len(asList)) + copy(as, asList) + newASparams[i] = bgp.NewAs4PathParam(param.GetType(), as) + } + return bgp.NewPathAttributeAsPath(newASparams) +} + +func UpdatePathAttrs(global *config.Global, peer *config.Neighbor, info *PeerInfo, original *Path) *Path { + if peer.RouteServer.Config.RouteServerClient { + return original + } + path := original.Clone(original.IsWithdraw) + + for _, a := range path.GetPathAttrs() { + if _, y := bgp.PathAttrFlags[a.GetType()]; !y { + if a.GetFlags()&bgp.BGP_ATTR_FLAG_TRANSITIVE == 0 { + path.delPathAttr(a.GetType()) + } + } else { + switch a.GetType() { + case bgp.BGP_ATTR_TYPE_CLUSTER_LIST, bgp.BGP_ATTR_TYPE_ORIGINATOR_ID: + if !(peer.State.PeerType == config.PEER_TYPE_INTERNAL && peer.RouteReflector.Config.RouteReflectorClient) { + // send these attributes to only rr clients + path.delPathAttr(a.GetType()) + } + } + } + } + + localAddress := info.LocalAddress + nexthop := path.GetNexthop() + if peer.State.PeerType == config.PEER_TYPE_EXTERNAL { + // NEXTHOP handling + if !path.IsLocal() || nexthop.IsUnspecified() { + path.SetNexthop(localAddress) + } + + // remove-private-as handling + path.RemovePrivateAS(peer.Config.LocalAs, peer.State.RemovePrivateAs) + + // AS_PATH handling + confed := peer.IsConfederationMember(global) + path.PrependAsn(peer.Config.LocalAs, 1, confed) + if !confed { + path.removeConfedAs() + } + + // MED Handling + if med := path.getPathAttr(bgp.BGP_ATTR_TYPE_MULTI_EXIT_DISC); med != nil && !path.IsLocal() { + path.delPathAttr(bgp.BGP_ATTR_TYPE_MULTI_EXIT_DISC) + } + + } else if peer.State.PeerType == config.PEER_TYPE_INTERNAL { + // NEXTHOP handling for iBGP + // if the path generated locally set local address as nexthop. + // if not, don't modify it. + // TODO: NEXT-HOP-SELF support + if path.IsLocal() && nexthop.IsUnspecified() { + path.SetNexthop(localAddress) + } + + // AS_PATH handling for iBGP + // if the path has AS_PATH path attribute, don't modify it. + // if not, attach *empty* AS_PATH path attribute. + if nh := path.getPathAttr(bgp.BGP_ATTR_TYPE_AS_PATH); nh == nil { + path.PrependAsn(0, 0, false) + } + + // For iBGP peers we are required to send local-pref attribute + // for connected or local prefixes. + // We set default local-pref 100. + if pref := path.getPathAttr(bgp.BGP_ATTR_TYPE_LOCAL_PREF); pref == nil { + path.setPathAttr(bgp.NewPathAttributeLocalPref(DEFAULT_LOCAL_PREF)) + } + + // RFC4456: BGP Route Reflection + // 8. Avoiding Routing Information Loops + info := path.GetSource() + if peer.RouteReflector.Config.RouteReflectorClient { + // This attribute will carry the BGP Identifier of the originator of the route in the local AS. + // A BGP speaker SHOULD NOT create an ORIGINATOR_ID attribute if one already exists. + // + // RFC4684 3.2 Intra-AS VPN Route Distribution + // When advertising RT membership NLRI to a route-reflector client, + // the Originator attribute shall be set to the router-id of the + // advertiser, and the Next-hop attribute shall be set of the local + // address for that session. + if path.GetRouteFamily() == bgp.RF_RTC_UC { + path.SetNexthop(localAddress) + path.setPathAttr(bgp.NewPathAttributeOriginatorId(info.LocalID.String())) + } else if path.getPathAttr(bgp.BGP_ATTR_TYPE_ORIGINATOR_ID) == nil { + if path.IsLocal() { + path.setPathAttr(bgp.NewPathAttributeOriginatorId(global.Config.RouterId)) + } else { + path.setPathAttr(bgp.NewPathAttributeOriginatorId(info.ID.String())) + } + } + // When an RR reflects a route, it MUST prepend the local CLUSTER_ID to the CLUSTER_LIST. + // If the CLUSTER_LIST is empty, it MUST create a new one. + clusterID := string(peer.RouteReflector.State.RouteReflectorClusterId) + if p := path.getPathAttr(bgp.BGP_ATTR_TYPE_CLUSTER_LIST); p == nil { + path.setPathAttr(bgp.NewPathAttributeClusterList([]string{clusterID})) + } else { + clusterList := p.(*bgp.PathAttributeClusterList) + newClusterList := make([]string, 0, len(clusterList.Value)) + for _, ip := range clusterList.Value { + newClusterList = append(newClusterList, ip.String()) + } + path.setPathAttr(bgp.NewPathAttributeClusterList(append([]string{clusterID}, newClusterList...))) + } + } + + } else { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.State.NeighborAddress, + }).Warnf("invalid peer type: %d", peer.State.PeerType) + } + return path +} + +func (path *Path) GetTimestamp() time.Time { + return time.Unix(path.OriginInfo().timestamp, 0) +} + +func (path *Path) setTimestamp(t time.Time) { + path.OriginInfo().timestamp = t.Unix() +} + +func (path *Path) IsLocal() bool { + return path.GetSource().Address == nil +} + +func (path *Path) IsIBGP() bool { + return path.GetSource().AS == path.GetSource().LocalAS +} + +// create new PathAttributes +func (path *Path) Clone(isWithdraw bool) *Path { + return &Path{ + parent: path, + IsWithdraw: isWithdraw, + IsNexthopInvalid: path.IsNexthopInvalid, + attrsHash: path.attrsHash, + } +} + +func (path *Path) root() *Path { + p := path + for p.parent != nil { + p = p.parent + } + return p +} + +func (path *Path) OriginInfo() *originInfo { + return path.root().info +} + +func (path *Path) NoImplicitWithdraw() bool { + return path.OriginInfo().noImplicitWithdraw +} + +func (path *Path) Validation() *Validation { + return path.OriginInfo().validation +} + +func (path *Path) ValidationStatus() config.RpkiValidationResultType { + if v := path.OriginInfo().validation; v != nil { + return v.Status + } else { + return config.RPKI_VALIDATION_RESULT_TYPE_NONE + } +} + +func (path *Path) SetValidation(v *Validation) { + path.OriginInfo().validation = v +} + +func (path *Path) IsFromExternal() bool { + return path.OriginInfo().isFromExternal +} + +func (path *Path) SetIsFromExternal(y bool) { + path.OriginInfo().isFromExternal = y +} + +func (path *Path) GetRouteFamily() bgp.RouteFamily { + return bgp.AfiSafiToRouteFamily(path.OriginInfo().nlri.AFI(), path.OriginInfo().nlri.SAFI()) +} + +func (path *Path) SetSource(source *PeerInfo) { + path.OriginInfo().source = source +} +func (path *Path) GetSource() *PeerInfo { + return path.OriginInfo().source +} + +func (path *Path) MarkStale(s bool) { + path.OriginInfo().stale = s +} + +func (path *Path) IsStale() bool { + return path.OriginInfo().stale +} + +func (path *Path) IsAsLooped() bool { + return path.aslooped +} + +func (path *Path) SetAsLooped(y bool) { + path.aslooped = y +} + +func (path *Path) IsLLGRStale() bool { + for _, c := range path.GetCommunities() { + if c == uint32(bgp.COMMUNITY_LLGR_STALE) { + return true + } + } + return false +} + +func (path *Path) GetSourceAs() uint32 { + attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_AS_PATH) + if attr != nil { + asPathParam := attr.(*bgp.PathAttributeAsPath).Value + if len(asPathParam) == 0 { + return 0 + } + asList := asPathParam[len(asPathParam)-1].GetAS() + if len(asList) == 0 { + return 0 + } + return asList[len(asList)-1] + } + return 0 +} + +func (path *Path) GetNexthop() net.IP { + attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_NEXT_HOP) + if attr != nil { + return attr.(*bgp.PathAttributeNextHop).Value + } + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_MP_REACH_NLRI) + if attr != nil { + return attr.(*bgp.PathAttributeMpReachNLRI).Nexthop + } + return net.IP{} +} + +func (path *Path) SetNexthop(nexthop net.IP) { + if path.GetRouteFamily() == bgp.RF_IPv4_UC && nexthop.To4() == nil { + path.delPathAttr(bgp.BGP_ATTR_TYPE_NEXT_HOP) + mpreach := bgp.NewPathAttributeMpReachNLRI(nexthop.String(), []bgp.AddrPrefixInterface{path.GetNlri()}) + path.setPathAttr(mpreach) + return + } + attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_NEXT_HOP) + if attr != nil { + path.setPathAttr(bgp.NewPathAttributeNextHop(nexthop.String())) + } + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_MP_REACH_NLRI) + if attr != nil { + oldNlri := attr.(*bgp.PathAttributeMpReachNLRI) + path.setPathAttr(bgp.NewPathAttributeMpReachNLRI(nexthop.String(), oldNlri.Value)) + } +} + +func (path *Path) GetNlri() bgp.AddrPrefixInterface { + return path.OriginInfo().nlri +} + +type PathAttrs []bgp.PathAttributeInterface + +func (a PathAttrs) Len() int { + return len(a) +} + +func (a PathAttrs) Swap(i, j int) { + a[i], a[j] = a[j], a[i] +} + +func (a PathAttrs) Less(i, j int) bool { + return a[i].GetType() < a[j].GetType() +} + +func (path *Path) GetPathAttrs() []bgp.PathAttributeInterface { + deleted := NewBitmap(math.MaxUint8) + modified := make(map[uint]bgp.PathAttributeInterface) + p := path + for { + for _, t := range p.dels { + deleted.Flag(uint(t)) + } + if p.parent == nil { + list := PathAttrs(make([]bgp.PathAttributeInterface, 0, len(p.pathAttrs))) + // we assume that the original pathAttrs are + // in order, that is, other bgp speakers send + // attributes in order. + for _, a := range p.pathAttrs { + typ := uint(a.GetType()) + if m, ok := modified[typ]; ok { + list = append(list, m) + delete(modified, typ) + } else if !deleted.GetFlag(typ) { + list = append(list, a) + } + } + if len(modified) > 0 { + // Huh, some attributes were newly + // added. So we need to sort... + for _, m := range modified { + list = append(list, m) + } + sort.Sort(list) + } + return list + } else { + for _, a := range p.pathAttrs { + typ := uint(a.GetType()) + if _, ok := modified[typ]; !deleted.GetFlag(typ) && !ok { + modified[typ] = a + } + } + } + p = p.parent + } +} + +func (path *Path) getPathAttr(typ bgp.BGPAttrType) bgp.PathAttributeInterface { + p := path + for { + for _, t := range p.dels { + if t == typ { + return nil + } + } + for _, a := range p.pathAttrs { + if a.GetType() == typ { + return a + } + } + if p.parent == nil { + return nil + } + p = p.parent + } +} + +func (path *Path) setPathAttr(a bgp.PathAttributeInterface) { + if len(path.pathAttrs) == 0 { + path.pathAttrs = []bgp.PathAttributeInterface{a} + } else { + for i, b := range path.pathAttrs { + if a.GetType() == b.GetType() { + path.pathAttrs[i] = a + return + } + } + path.pathAttrs = append(path.pathAttrs, a) + } +} + +func (path *Path) delPathAttr(typ bgp.BGPAttrType) { + if len(path.dels) == 0 { + path.dels = []bgp.BGPAttrType{typ} + } else { + path.dels = append(path.dels, typ) + } +} + +// return Path's string representation +func (path *Path) String() string { + s := bytes.NewBuffer(make([]byte, 0, 64)) + if path.IsEOR() { + s.WriteString(fmt.Sprintf("{ %s EOR | src: %s }", path.GetRouteFamily(), path.GetSource())) + return s.String() + } + s.WriteString(fmt.Sprintf("{ %s | ", path.getPrefix())) + s.WriteString(fmt.Sprintf("src: %s", path.GetSource())) + s.WriteString(fmt.Sprintf(", nh: %s", path.GetNexthop())) + if path.IsNexthopInvalid { + s.WriteString(" (not reachable)") + } + if path.IsWithdraw { + s.WriteString(", withdraw") + } + s.WriteString(" }") + return s.String() +} + +func (path *Path) getPrefix() string { + return path.GetNlri().String() +} + +func (path *Path) GetAsPath() *bgp.PathAttributeAsPath { + attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_AS_PATH) + if attr != nil { + return attr.(*bgp.PathAttributeAsPath) + } + return nil +} + +// GetAsPathLen returns the number of AS_PATH +func (path *Path) GetAsPathLen() int { + + var length int = 0 + if aspath := path.GetAsPath(); aspath != nil { + for _, as := range aspath.Value { + length += as.ASLen() + } + } + return length +} + +func (path *Path) GetAsString() string { + s := bytes.NewBuffer(make([]byte, 0, 64)) + if aspath := path.GetAsPath(); aspath != nil { + return bgp.AsPathString(aspath) + } + return s.String() +} + +func (path *Path) GetAsList() []uint32 { + return path.getAsListOfSpecificType(true, true) + +} + +func (path *Path) GetAsSeqList() []uint32 { + return path.getAsListOfSpecificType(true, false) + +} + +func (path *Path) getAsListOfSpecificType(getAsSeq, getAsSet bool) []uint32 { + asList := []uint32{} + if aspath := path.GetAsPath(); aspath != nil { + for _, param := range aspath.Value { + segType := param.GetType() + if getAsSeq && segType == bgp.BGP_ASPATH_ATTR_TYPE_SEQ { + asList = append(asList, param.GetAS()...) + continue + } + if getAsSet && segType == bgp.BGP_ASPATH_ATTR_TYPE_SET { + asList = append(asList, param.GetAS()...) + } else { + asList = append(asList, 0) + } + } + } + return asList +} + +func (path *Path) GetLabelString() string { + return bgp.LabelString(path.GetNlri()) +} + +// PrependAsn prepends AS number. +// This function updates the AS_PATH attribute as follows. +// (If the peer is in the confederation member AS, +// replace AS_SEQUENCE in the following sentence with AS_CONFED_SEQUENCE.) +// 1) if the first path segment of the AS_PATH is of type +// AS_SEQUENCE, the local system prepends the specified AS num as +// the last element of the sequence (put it in the left-most +// position with respect to the position of octets in the +// protocol message) the specified number of times. +// If the act of prepending will cause an overflow in the AS_PATH +// segment (i.e., more than 255 ASes), +// it SHOULD prepend a new segment of type AS_SEQUENCE +// and prepend its own AS number to this new segment. +// +// 2) if the first path segment of the AS_PATH is of other than type +// AS_SEQUENCE, the local system prepends a new path segment of type +// AS_SEQUENCE to the AS_PATH, including the specified AS number in +// that segment. +// +// 3) if the AS_PATH is empty, the local system creates a path +// segment of type AS_SEQUENCE, places the specified AS number +// into that segment, and places that segment into the AS_PATH. +func (path *Path) PrependAsn(asn uint32, repeat uint8, confed bool) { + var segType uint8 + if confed { + segType = bgp.BGP_ASPATH_ATTR_TYPE_CONFED_SEQ + } else { + segType = bgp.BGP_ASPATH_ATTR_TYPE_SEQ + } + + original := path.GetAsPath() + + asns := make([]uint32, repeat) + for i := range asns { + asns[i] = asn + } + + var asPath *bgp.PathAttributeAsPath + if original == nil { + asPath = bgp.NewPathAttributeAsPath([]bgp.AsPathParamInterface{}) + } else { + asPath = cloneAsPath(original) + } + + if len(asPath.Value) > 0 { + param := asPath.Value[0] + asList := param.GetAS() + if param.GetType() == segType { + if int(repeat)+len(asList) > 255 { + repeat = uint8(255 - len(asList)) + } + newAsList := append(asns[:int(repeat)], asList...) + asPath.Value[0] = bgp.NewAs4PathParam(segType, newAsList) + asns = asns[int(repeat):] + } + } + + if len(asns) > 0 { + p := bgp.NewAs4PathParam(segType, asns) + asPath.Value = append([]bgp.AsPathParamInterface{p}, asPath.Value...) + } + path.setPathAttr(asPath) +} + +func isPrivateAS(as uint32) bool { + return (64512 <= as && as <= 65534) || (4200000000 <= as && as <= 4294967294) +} + +func (path *Path) RemovePrivateAS(localAS uint32, option config.RemovePrivateAsOption) { + original := path.GetAsPath() + if original == nil { + return + } + switch option { + case config.REMOVE_PRIVATE_AS_OPTION_ALL, config.REMOVE_PRIVATE_AS_OPTION_REPLACE: + newASParams := make([]bgp.AsPathParamInterface, 0, len(original.Value)) + for _, param := range original.Value { + asList := param.GetAS() + newASParam := make([]uint32, 0, len(asList)) + for _, as := range asList { + if isPrivateAS(as) { + if option == config.REMOVE_PRIVATE_AS_OPTION_REPLACE { + newASParam = append(newASParam, localAS) + } + } else { + newASParam = append(newASParam, as) + } + } + if len(newASParam) > 0 { + newASParams = append(newASParams, bgp.NewAs4PathParam(param.GetType(), newASParam)) + } + } + path.setPathAttr(bgp.NewPathAttributeAsPath(newASParams)) + } +} + +func (path *Path) removeConfedAs() { + original := path.GetAsPath() + if original == nil { + return + } + newAsParams := make([]bgp.AsPathParamInterface, 0, len(original.Value)) + for _, param := range original.Value { + switch param.GetType() { + case bgp.BGP_ASPATH_ATTR_TYPE_SEQ, bgp.BGP_ASPATH_ATTR_TYPE_SET: + newAsParams = append(newAsParams, param) + } + } + path.setPathAttr(bgp.NewPathAttributeAsPath(newAsParams)) +} + +func (path *Path) ReplaceAS(localAS, peerAS uint32) *Path { + original := path.GetAsPath() + if original == nil { + return path + } + newASParams := make([]bgp.AsPathParamInterface, 0, len(original.Value)) + changed := false + for _, param := range original.Value { + segType := param.GetType() + asList := param.GetAS() + newASParam := make([]uint32, 0, len(asList)) + for _, as := range asList { + if as == peerAS { + as = localAS + changed = true + } + newASParam = append(newASParam, as) + } + newASParams = append(newASParams, bgp.NewAs4PathParam(segType, newASParam)) + } + if changed { + path = path.Clone(path.IsWithdraw) + path.setPathAttr(bgp.NewPathAttributeAsPath(newASParams)) + } + return path +} + +func (path *Path) GetCommunities() []uint32 { + communityList := []uint32{} + if attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_COMMUNITIES); attr != nil { + communities := attr.(*bgp.PathAttributeCommunities) + communityList = append(communityList, communities.Value...) + } + return communityList +} + +// SetCommunities adds or replaces communities with new ones. +// If the length of communities is 0 and doReplace is true, it clears communities. +func (path *Path) SetCommunities(communities []uint32, doReplace bool) { + + if len(communities) == 0 && doReplace { + // clear communities + path.delPathAttr(bgp.BGP_ATTR_TYPE_COMMUNITIES) + return + } + + newList := make([]uint32, 0) + attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_COMMUNITIES) + if attr != nil { + c := attr.(*bgp.PathAttributeCommunities) + if doReplace { + newList = append(newList, communities...) + } else { + newList = append(newList, c.Value...) + newList = append(newList, communities...) + } + } else { + newList = append(newList, communities...) + } + path.setPathAttr(bgp.NewPathAttributeCommunities(newList)) + +} + +// RemoveCommunities removes specific communities. +// If the length of communities is 0, it does nothing. +// If all communities are removed, it removes Communities path attribute itself. +func (path *Path) RemoveCommunities(communities []uint32) int { + + if len(communities) == 0 { + // do nothing + return 0 + } + + find := func(val uint32) bool { + for _, com := range communities { + if com == val { + return true + } + } + return false + } + + count := 0 + attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_COMMUNITIES) + if attr != nil { + newList := make([]uint32, 0) + c := attr.(*bgp.PathAttributeCommunities) + + for _, value := range c.Value { + if find(value) { + count += 1 + } else { + newList = append(newList, value) + } + } + + if len(newList) != 0 { + path.setPathAttr(bgp.NewPathAttributeCommunities(newList)) + } else { + path.delPathAttr(bgp.BGP_ATTR_TYPE_COMMUNITIES) + } + } + return count +} + +func (path *Path) GetExtCommunities() []bgp.ExtendedCommunityInterface { + eCommunityList := make([]bgp.ExtendedCommunityInterface, 0) + if attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_EXTENDED_COMMUNITIES); attr != nil { + eCommunities := attr.(*bgp.PathAttributeExtendedCommunities).Value + eCommunityList = append(eCommunityList, eCommunities...) + } + return eCommunityList +} + +func (path *Path) SetExtCommunities(exts []bgp.ExtendedCommunityInterface, doReplace bool) { + attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_EXTENDED_COMMUNITIES) + if attr != nil { + l := attr.(*bgp.PathAttributeExtendedCommunities).Value + if doReplace { + l = exts + } else { + l = append(l, exts...) + } + path.setPathAttr(bgp.NewPathAttributeExtendedCommunities(l)) + } else { + path.setPathAttr(bgp.NewPathAttributeExtendedCommunities(exts)) + } +} + +func (path *Path) GetLargeCommunities() []*bgp.LargeCommunity { + if a := path.getPathAttr(bgp.BGP_ATTR_TYPE_LARGE_COMMUNITY); a != nil { + v := a.(*bgp.PathAttributeLargeCommunities).Values + ret := make([]*bgp.LargeCommunity, 0, len(v)) + ret = append(ret, v...) + return ret + } + return nil +} + +func (path *Path) SetLargeCommunities(cs []*bgp.LargeCommunity, doReplace bool) { + a := path.getPathAttr(bgp.BGP_ATTR_TYPE_LARGE_COMMUNITY) + if a == nil || doReplace { + path.setPathAttr(bgp.NewPathAttributeLargeCommunities(cs)) + } else { + l := a.(*bgp.PathAttributeLargeCommunities).Values + path.setPathAttr(bgp.NewPathAttributeLargeCommunities(append(l, cs...))) + } +} + +func (path *Path) GetMed() (uint32, error) { + attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_MULTI_EXIT_DISC) + if attr == nil { + return 0, fmt.Errorf("no med path attr") + } + return attr.(*bgp.PathAttributeMultiExitDisc).Value, nil +} + +// SetMed replace, add or subtraction med with new ones. +func (path *Path) SetMed(med int64, doReplace bool) error { + parseMed := func(orgMed uint32, med int64, doReplace bool) (*bgp.PathAttributeMultiExitDisc, error) { + if doReplace { + return bgp.NewPathAttributeMultiExitDisc(uint32(med)), nil + } + + medVal := int64(orgMed) + med + if medVal < 0 { + return nil, fmt.Errorf("med value invalid. it's underflow threshold: %v", medVal) + } else if medVal > int64(math.MaxUint32) { + return nil, fmt.Errorf("med value invalid. it's overflow threshold: %v", medVal) + } + + return bgp.NewPathAttributeMultiExitDisc(uint32(int64(orgMed) + med)), nil + } + + m := uint32(0) + if attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_MULTI_EXIT_DISC); attr != nil { + m = attr.(*bgp.PathAttributeMultiExitDisc).Value + } + newMed, err := parseMed(m, med, doReplace) + if err != nil { + return err + } + path.setPathAttr(newMed) + return nil +} + +func (path *Path) RemoveLocalPref() { + if path.getPathAttr(bgp.BGP_ATTR_TYPE_LOCAL_PREF) != nil { + path.delPathAttr(bgp.BGP_ATTR_TYPE_LOCAL_PREF) + } +} + +func (path *Path) GetOriginatorID() net.IP { + if attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_ORIGINATOR_ID); attr != nil { + return attr.(*bgp.PathAttributeOriginatorId).Value + } + return nil +} + +func (path *Path) GetClusterList() []net.IP { + if attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_CLUSTER_LIST); attr != nil { + return attr.(*bgp.PathAttributeClusterList).Value + } + return nil +} + +func (path *Path) GetOrigin() (uint8, error) { + if attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_ORIGIN); attr != nil { + return attr.(*bgp.PathAttributeOrigin).Value, nil + } + return 0, fmt.Errorf("no origin path attr") +} + +func (path *Path) GetLocalPref() (uint32, error) { + lp := uint32(DEFAULT_LOCAL_PREF) + attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_LOCAL_PREF) + if attr != nil { + lp = attr.(*bgp.PathAttributeLocalPref).Value + } + return lp, nil +} + +func (lhs *Path) Equal(rhs *Path) bool { + if rhs == nil { + return false + } + + if lhs.GetSource() != rhs.GetSource() { + return false + } + + pattrs := func(arg []bgp.PathAttributeInterface) []byte { + ret := make([]byte, 0) + for _, a := range arg { + aa, _ := a.Serialize() + ret = append(ret, aa...) + } + return ret + } + return bytes.Equal(pattrs(lhs.GetPathAttrs()), pattrs(rhs.GetPathAttrs())) +} + +func (path *Path) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Nlri bgp.AddrPrefixInterface `json:"nlri"` + PathAttrs []bgp.PathAttributeInterface `json:"attrs"` + Age int64 `json:"age"` + Withdrawal bool `json:"withdrawal,omitempty"` + Validation string `json:"validation,omitempty"` + SourceID net.IP `json:"source-id,omitempty"` + NeighborIP net.IP `json:"neighbor-ip,omitempty"` + Stale bool `json:"stale,omitempty"` + UUID string `json:"uuid,omitempty"` + ID uint32 `json:"id,omitempty"` + }{ + Nlri: path.GetNlri(), + PathAttrs: path.GetPathAttrs(), + Age: path.GetTimestamp().Unix(), + Withdrawal: path.IsWithdraw, + Validation: string(path.ValidationStatus()), + SourceID: path.GetSource().ID, + NeighborIP: path.GetSource().Address, + Stale: path.IsStale(), + ID: path.GetNlri().PathIdentifier(), + }) +} + +func (lhs *Path) Compare(rhs *Path) int { + if lhs.IsLocal() && !rhs.IsLocal() { + return 1 + } else if !lhs.IsLocal() && rhs.IsLocal() { + return -1 + } + + if !lhs.IsIBGP() && rhs.IsIBGP() { + return 1 + } else if lhs.IsIBGP() && !rhs.IsIBGP() { + return -1 + } + + lp1, _ := lhs.GetLocalPref() + lp2, _ := rhs.GetLocalPref() + if lp1 != lp2 { + return int(lp1 - lp2) + } + + l1 := lhs.GetAsPathLen() + l2 := rhs.GetAsPathLen() + if l1 != l2 { + return int(l2 - l1) + } + + o1, _ := lhs.GetOrigin() + o2, _ := rhs.GetOrigin() + if o1 != o2 { + return int(o2 - o1) + } + + m1, _ := lhs.GetMed() + m2, _ := rhs.GetMed() + return int(m2 - m1) +} + +func (v *Vrf) ToGlobalPath(path *Path) error { + nlri := path.GetNlri() + switch rf := path.GetRouteFamily(); rf { + case bgp.RF_IPv4_UC: + n := nlri.(*bgp.IPAddrPrefix) + pathIdentifier := path.GetNlri().PathIdentifier() + path.OriginInfo().nlri = bgp.NewLabeledVPNIPAddrPrefix(n.Length, n.Prefix.String(), *bgp.NewMPLSLabelStack(0), v.Rd) + path.GetNlri().SetPathIdentifier(pathIdentifier) + case bgp.RF_IPv6_UC: + n := nlri.(*bgp.IPv6AddrPrefix) + pathIdentifier := path.GetNlri().PathIdentifier() + path.OriginInfo().nlri = bgp.NewLabeledVPNIPv6AddrPrefix(n.Length, n.Prefix.String(), *bgp.NewMPLSLabelStack(0), v.Rd) + path.GetNlri().SetPathIdentifier(pathIdentifier) + case bgp.RF_EVPN: + n := nlri.(*bgp.EVPNNLRI) + switch n.RouteType { + case bgp.EVPN_ROUTE_TYPE_MAC_IP_ADVERTISEMENT: + n.RouteTypeData.(*bgp.EVPNMacIPAdvertisementRoute).RD = v.Rd + case bgp.EVPN_INCLUSIVE_MULTICAST_ETHERNET_TAG: + n.RouteTypeData.(*bgp.EVPNMulticastEthernetTagRoute).RD = v.Rd + } + default: + return fmt.Errorf("unsupported route family for vrf: %s", rf) + } + path.SetExtCommunities(v.ExportRt, false) + return nil +} + +func (p *Path) ToGlobal(vrf *Vrf) *Path { + nlri := p.GetNlri() + nh := p.GetNexthop() + pathId := nlri.PathIdentifier() + switch rf := p.GetRouteFamily(); rf { + case bgp.RF_IPv4_UC: + n := nlri.(*bgp.IPAddrPrefix) + nlri = bgp.NewLabeledVPNIPAddrPrefix(n.Length, n.Prefix.String(), *bgp.NewMPLSLabelStack(0), vrf.Rd) + nlri.SetPathIdentifier(pathId) + case bgp.RF_IPv6_UC: + n := nlri.(*bgp.IPv6AddrPrefix) + nlri = bgp.NewLabeledVPNIPv6AddrPrefix(n.Length, n.Prefix.String(), *bgp.NewMPLSLabelStack(0), vrf.Rd) + nlri.SetPathIdentifier(pathId) + case bgp.RF_EVPN: + n := nlri.(*bgp.EVPNNLRI) + switch n.RouteType { + case bgp.EVPN_ROUTE_TYPE_MAC_IP_ADVERTISEMENT: + old := n.RouteTypeData.(*bgp.EVPNMacIPAdvertisementRoute) + new := &bgp.EVPNMacIPAdvertisementRoute{ + RD: vrf.Rd, + ESI: old.ESI, + ETag: old.ETag, + MacAddressLength: old.MacAddressLength, + MacAddress: old.MacAddress, + IPAddressLength: old.IPAddressLength, + IPAddress: old.IPAddress, + Labels: old.Labels, + } + nlri = bgp.NewEVPNNLRI(n.RouteType, new) + case bgp.EVPN_INCLUSIVE_MULTICAST_ETHERNET_TAG: + old := n.RouteTypeData.(*bgp.EVPNMulticastEthernetTagRoute) + new := &bgp.EVPNMulticastEthernetTagRoute{ + RD: vrf.Rd, + ETag: old.ETag, + IPAddressLength: old.IPAddressLength, + IPAddress: old.IPAddress, + } + nlri = bgp.NewEVPNNLRI(n.RouteType, new) + } + default: + return p + } + path := NewPath(p.OriginInfo().source, nlri, p.IsWithdraw, p.GetPathAttrs(), p.GetTimestamp(), false) + path.SetExtCommunities(vrf.ExportRt, false) + path.delPathAttr(bgp.BGP_ATTR_TYPE_NEXT_HOP) + path.setPathAttr(bgp.NewPathAttributeMpReachNLRI(nh.String(), []bgp.AddrPrefixInterface{nlri})) + path.IsNexthopInvalid = p.IsNexthopInvalid + return path +} + +func (p *Path) ToLocal() *Path { + nlri := p.GetNlri() + f := p.GetRouteFamily() + pathId := nlri.PathLocalIdentifier() + switch f { + case bgp.RF_IPv4_VPN: + n := nlri.(*bgp.LabeledVPNIPAddrPrefix) + _, c, _ := net.ParseCIDR(n.IPPrefix()) + ones, _ := c.Mask.Size() + nlri = bgp.NewIPAddrPrefix(uint8(ones), c.IP.String()) + nlri.SetPathLocalIdentifier(pathId) + case bgp.RF_IPv6_VPN: + n := nlri.(*bgp.LabeledVPNIPv6AddrPrefix) + _, c, _ := net.ParseCIDR(n.IPPrefix()) + ones, _ := c.Mask.Size() + nlri = bgp.NewIPv6AddrPrefix(uint8(ones), c.IP.String()) + nlri.SetPathLocalIdentifier(pathId) + default: + return p + } + path := NewPath(p.OriginInfo().source, nlri, p.IsWithdraw, p.GetPathAttrs(), p.GetTimestamp(), false) + path.delPathAttr(bgp.BGP_ATTR_TYPE_EXTENDED_COMMUNITIES) + + if f == bgp.RF_IPv4_VPN { + nh := path.GetNexthop() + path.delPathAttr(bgp.BGP_ATTR_TYPE_MP_REACH_NLRI) + path.setPathAttr(bgp.NewPathAttributeNextHop(nh.String())) + } + path.IsNexthopInvalid = p.IsNexthopInvalid + return path +} + +func (p *Path) SetHash(v uint32) { + p.attrsHash = v +} + +func (p *Path) GetHash() uint32 { + return p.attrsHash +} diff --git a/internal/pkg/table/path_test.go b/internal/pkg/table/path_test.go new file mode 100644 index 00000000..449c4a8e --- /dev/null +++ b/internal/pkg/table/path_test.go @@ -0,0 +1,365 @@ +// path_test.go +package table + +import ( + "testing" + "time" + + "github.com/osrg/gobgp/internal/pkg/config" + "github.com/osrg/gobgp/pkg/packet/bgp" + + "github.com/stretchr/testify/assert" +) + +func TestPathNewIPv4(t *testing.T) { + peerP := PathCreatePeer() + pathP := PathCreatePath(peerP) + ipv4p := NewPath(pathP[0].GetSource(), pathP[0].GetNlri(), true, pathP[0].GetPathAttrs(), time.Now(), false) + assert.NotNil(t, ipv4p) +} + +func TestPathNewIPv6(t *testing.T) { + peerP := PathCreatePeer() + pathP := PathCreatePath(peerP) + ipv6p := NewPath(pathP[0].GetSource(), pathP[0].GetNlri(), true, pathP[0].GetPathAttrs(), time.Now(), false) + assert.NotNil(t, ipv6p) +} + +func TestPathGetNlri(t *testing.T) { + nlri := bgp.NewIPAddrPrefix(24, "13.2.3.2") + pd := &Path{ + info: &originInfo{ + nlri: nlri, + }, + } + r_nlri := pd.GetNlri() + assert.Equal(t, r_nlri, nlri) +} + +func TestPathCreatePath(t *testing.T) { + peerP := PathCreatePeer() + msg := updateMsgP1() + updateMsgP := msg.Body.(*bgp.BGPUpdate) + nlriList := updateMsgP.NLRI + pathAttributes := updateMsgP.PathAttributes + nlri_info := nlriList[0] + path := NewPath(peerP[0], nlri_info, false, pathAttributes, time.Now(), false) + assert.NotNil(t, path) + +} + +func TestPathGetPrefix(t *testing.T) { + peerP := PathCreatePeer() + pathP := PathCreatePath(peerP) + prefix := "10.10.10.0/24" + r_prefix := pathP[0].getPrefix() + assert.Equal(t, r_prefix, prefix) +} + +func TestPathGetAttribute(t *testing.T) { + peerP := PathCreatePeer() + pathP := PathCreatePath(peerP) + nh := "192.168.50.1" + pa := pathP[0].getPathAttr(bgp.BGP_ATTR_TYPE_NEXT_HOP) + r_nh := pa.(*bgp.PathAttributeNextHop).Value.String() + assert.Equal(t, r_nh, nh) +} + +func TestASPathLen(t *testing.T) { + assert := assert.New(t) + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{ + bgp.NewAsPathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, []uint16{65001, 65002, 65003, 65004, 65004, 65004, 65004, 65004, 65005}), + bgp.NewAsPathParam(bgp.BGP_ASPATH_ATTR_TYPE_SET, []uint16{65001, 65002, 65003, 65004, 65005}), + bgp.NewAsPathParam(bgp.BGP_ASPATH_ATTR_TYPE_CONFED_SEQ, []uint16{65100, 65101, 65102}), + bgp.NewAsPathParam(bgp.BGP_ASPATH_ATTR_TYPE_CONFED_SET, []uint16{65100, 65101})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("192.168.50.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + + pathAttributes := []bgp.PathAttributeInterface{ + origin, + aspath, + nexthop, + med, + } + + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.10.0")} + bgpmsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + update := bgpmsg.Body.(*bgp.BGPUpdate) + UpdatePathAttrs4ByteAs(update) + peer := PathCreatePeer() + p := NewPath(peer[0], update.NLRI[0], false, update.PathAttributes, time.Now(), false) + assert.Equal(10, p.GetAsPathLen()) +} + +func TestPathPrependAsnToExistingSeqAttr(t *testing.T) { + assert := assert.New(t) + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{ + bgp.NewAsPathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, []uint16{65001, 65002, 65003, 65004, 65005}), + bgp.NewAsPathParam(bgp.BGP_ASPATH_ATTR_TYPE_SET, []uint16{65001, 65002, 65003, 65004, 65005}), + bgp.NewAsPathParam(bgp.BGP_ASPATH_ATTR_TYPE_CONFED_SEQ, []uint16{65100, 65101, 65102}), + bgp.NewAsPathParam(bgp.BGP_ASPATH_ATTR_TYPE_CONFED_SET, []uint16{65100, 65101})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("192.168.50.1") + + pathAttributes := []bgp.PathAttributeInterface{ + origin, + aspath, + nexthop, + } + + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.10.0")} + bgpmsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + update := bgpmsg.Body.(*bgp.BGPUpdate) + UpdatePathAttrs4ByteAs(update) + peer := PathCreatePeer() + p := NewPath(peer[0], update.NLRI[0], false, update.PathAttributes, time.Now(), false) + + p.PrependAsn(65000, 1, false) + assert.Equal([]uint32{65000, 65001, 65002, 65003, 65004, 65005, 0, 0, 0}, p.GetAsSeqList()) +} + +func TestPathPrependAsnToNewAsPathAttr(t *testing.T) { + assert := assert.New(t) + origin := bgp.NewPathAttributeOrigin(0) + nexthop := bgp.NewPathAttributeNextHop("192.168.50.1") + + pathAttributes := []bgp.PathAttributeInterface{ + origin, + nexthop, + } + + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.10.0")} + bgpmsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + update := bgpmsg.Body.(*bgp.BGPUpdate) + UpdatePathAttrs4ByteAs(update) + peer := PathCreatePeer() + p := NewPath(peer[0], update.NLRI[0], false, update.PathAttributes, time.Now(), false) + + asn := uint32(65000) + p.PrependAsn(asn, 1, false) + assert.Equal([]uint32{asn}, p.GetAsSeqList()) +} + +func TestPathPrependAsnToNewAsPathSeq(t *testing.T) { + assert := assert.New(t) + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{ + bgp.NewAsPathParam(bgp.BGP_ASPATH_ATTR_TYPE_SET, []uint16{65001, 65002, 65003, 65004, 65005}), + bgp.NewAsPathParam(bgp.BGP_ASPATH_ATTR_TYPE_CONFED_SEQ, []uint16{65100, 65101, 65102}), + bgp.NewAsPathParam(bgp.BGP_ASPATH_ATTR_TYPE_CONFED_SET, []uint16{65100, 65101})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("192.168.50.1") + + pathAttributes := []bgp.PathAttributeInterface{ + origin, + aspath, + nexthop, + } + + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.10.0")} + bgpmsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + update := bgpmsg.Body.(*bgp.BGPUpdate) + UpdatePathAttrs4ByteAs(update) + peer := PathCreatePeer() + p := NewPath(peer[0], update.NLRI[0], false, update.PathAttributes, time.Now(), false) + + asn := uint32(65000) + p.PrependAsn(asn, 1, false) + assert.Equal([]uint32{asn, 0, 0, 0}, p.GetAsSeqList()) +} + +func TestPathPrependAsnToEmptyAsPathAttr(t *testing.T) { + assert := assert.New(t) + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{ + bgp.NewAsPathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, []uint16{}), + bgp.NewAsPathParam(bgp.BGP_ASPATH_ATTR_TYPE_SET, []uint16{65001, 65002, 65003, 65004, 65005}), + bgp.NewAsPathParam(bgp.BGP_ASPATH_ATTR_TYPE_CONFED_SEQ, []uint16{65100, 65101, 65102}), + bgp.NewAsPathParam(bgp.BGP_ASPATH_ATTR_TYPE_CONFED_SET, []uint16{65100, 65101})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("192.168.50.1") + + pathAttributes := []bgp.PathAttributeInterface{ + origin, + aspath, + nexthop, + } + + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.10.0")} + bgpmsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + update := bgpmsg.Body.(*bgp.BGPUpdate) + UpdatePathAttrs4ByteAs(update) + peer := PathCreatePeer() + p := NewPath(peer[0], update.NLRI[0], false, update.PathAttributes, time.Now(), false) + + asn := uint32(65000) + p.PrependAsn(asn, 1, false) + assert.Equal([]uint32{asn, 0, 0, 0}, p.GetAsSeqList()) +} + +func TestPathPrependAsnToFullPathAttr(t *testing.T) { + assert := assert.New(t) + origin := bgp.NewPathAttributeOrigin(0) + + asns := make([]uint16, 255) + for i, _ := range asns { + asns[i] = 65000 + uint16(i) + } + + aspathParam := []bgp.AsPathParamInterface{ + bgp.NewAsPathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, asns), + bgp.NewAsPathParam(bgp.BGP_ASPATH_ATTR_TYPE_SET, []uint16{65001, 65002, 65003, 65004, 65005}), + bgp.NewAsPathParam(bgp.BGP_ASPATH_ATTR_TYPE_CONFED_SEQ, []uint16{65100, 65101, 65102}), + bgp.NewAsPathParam(bgp.BGP_ASPATH_ATTR_TYPE_CONFED_SET, []uint16{65100, 65101})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("192.168.50.1") + + pathAttributes := []bgp.PathAttributeInterface{ + origin, + aspath, + nexthop, + } + + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.10.0")} + bgpmsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + update := bgpmsg.Body.(*bgp.BGPUpdate) + UpdatePathAttrs4ByteAs(update) + peer := PathCreatePeer() + p := NewPath(peer[0], update.NLRI[0], false, update.PathAttributes, time.Now(), false) + + expected := []uint32{65000, 65000} + for _, v := range asns { + expected = append(expected, uint32(v)) + } + p.PrependAsn(65000, 2, false) + assert.Equal(append(expected, []uint32{0, 0, 0}...), p.GetAsSeqList()) +} + +func TestGetPathAttrs(t *testing.T) { + paths := PathCreatePath(PathCreatePeer()) + path0 := paths[0] + path1 := path0.Clone(false) + path1.delPathAttr(bgp.BGP_ATTR_TYPE_NEXT_HOP) + path2 := path1.Clone(false) + path2.setPathAttr(bgp.NewPathAttributeNextHop("192.168.50.1")) + assert.NotNil(t, path2.getPathAttr(bgp.BGP_ATTR_TYPE_NEXT_HOP)) +} + +func PathCreatePeer() []*PeerInfo { + peerP1 := &PeerInfo{AS: 65000} + peerP2 := &PeerInfo{AS: 65001} + peerP3 := &PeerInfo{AS: 65002} + peerP := []*PeerInfo{peerP1, peerP2, peerP3} + return peerP +} + +func PathCreatePath(peerP []*PeerInfo) []*Path { + bgpMsgP1 := updateMsgP1() + bgpMsgP2 := updateMsgP2() + bgpMsgP3 := updateMsgP3() + pathP := make([]*Path, 3) + for i, msg := range []*bgp.BGPMessage{bgpMsgP1, bgpMsgP2, bgpMsgP3} { + updateMsgP := msg.Body.(*bgp.BGPUpdate) + nlriList := updateMsgP.NLRI + pathAttributes := updateMsgP.PathAttributes + nlri_info := nlriList[0] + pathP[i] = NewPath(peerP[i], nlri_info, false, pathAttributes, time.Now(), false) + } + return pathP +} + +func updateMsgP1() *bgp.BGPMessage { + + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65000})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("192.168.50.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + + pathAttributes := []bgp.PathAttributeInterface{ + origin, + aspath, + nexthop, + med, + } + + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.10.0")} + return bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) +} + +func updateMsgP2() *bgp.BGPMessage { + + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65100})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("192.168.100.1") + med := bgp.NewPathAttributeMultiExitDisc(100) + + pathAttributes := []bgp.PathAttributeInterface{ + origin, + aspath, + nexthop, + med, + } + + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "20.20.20.0")} + return bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) +} + +func updateMsgP3() *bgp.BGPMessage { + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65100})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("192.168.150.1") + med := bgp.NewPathAttributeMultiExitDisc(100) + + pathAttributes := []bgp.PathAttributeInterface{ + origin, + aspath, + nexthop, + med, + } + + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "30.30.30.0")} + w1 := bgp.NewIPAddrPrefix(23, "40.40.40.0") + withdrawnRoutes := []*bgp.IPAddrPrefix{w1} + return bgp.NewBGPUpdateMessage(withdrawnRoutes, pathAttributes, nlri) +} + +func TestRemovePrivateAS(t *testing.T) { + aspathParam := []bgp.AsPathParamInterface{bgp.NewAs4PathParam(2, []uint32{64512, 64513, 1, 2})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nlri := bgp.NewIPAddrPrefix(24, "30.30.30.0") + path := NewPath(nil, nlri, false, []bgp.PathAttributeInterface{aspath}, time.Now(), false) + path.RemovePrivateAS(10, config.REMOVE_PRIVATE_AS_OPTION_ALL) + list := path.GetAsList() + assert.Equal(t, len(list), 2) + assert.Equal(t, list[0], uint32(1)) + assert.Equal(t, list[1], uint32(2)) + + path = NewPath(nil, nlri, false, []bgp.PathAttributeInterface{aspath}, time.Now(), false) + path.RemovePrivateAS(10, config.REMOVE_PRIVATE_AS_OPTION_REPLACE) + list = path.GetAsList() + assert.Equal(t, len(list), 4) + assert.Equal(t, list[0], uint32(10)) + assert.Equal(t, list[1], uint32(10)) + assert.Equal(t, list[2], uint32(1)) + assert.Equal(t, list[3], uint32(2)) +} + +func TestReplaceAS(t *testing.T) { + aspathParam := []bgp.AsPathParamInterface{bgp.NewAs4PathParam(2, []uint32{64512, 64513, 1, 2})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nlri := bgp.NewIPAddrPrefix(24, "30.30.30.0") + path := NewPath(nil, nlri, false, []bgp.PathAttributeInterface{aspath}, time.Now(), false) + path = path.ReplaceAS(10, 1) + list := path.GetAsList() + assert.Equal(t, len(list), 4) + assert.Equal(t, list[0], uint32(64512)) + assert.Equal(t, list[1], uint32(64513)) + assert.Equal(t, list[2], uint32(10)) + assert.Equal(t, list[3], uint32(2)) +} diff --git a/internal/pkg/table/policy.go b/internal/pkg/table/policy.go new file mode 100644 index 00000000..23c2110c --- /dev/null +++ b/internal/pkg/table/policy.go @@ -0,0 +1,3994 @@ +// 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 table + +import ( + "bytes" + "encoding/json" + "fmt" + "net" + "reflect" + "regexp" + "sort" + "strconv" + "strings" + "sync" + + "github.com/osrg/gobgp/internal/pkg/config" + "github.com/osrg/gobgp/pkg/packet/bgp" + + radix "github.com/armon/go-radix" + log "github.com/sirupsen/logrus" +) + +type PolicyOptions struct { + Info *PeerInfo + ValidationResult *Validation + OldNextHop net.IP +} + +type DefinedType int + +const ( + DEFINED_TYPE_PREFIX DefinedType = iota + DEFINED_TYPE_NEIGHBOR + DEFINED_TYPE_TAG + DEFINED_TYPE_AS_PATH + DEFINED_TYPE_COMMUNITY + DEFINED_TYPE_EXT_COMMUNITY + DEFINED_TYPE_LARGE_COMMUNITY + DEFINED_TYPE_NEXT_HOP +) + +type RouteType int + +const ( + ROUTE_TYPE_NONE RouteType = iota + ROUTE_TYPE_ACCEPT + ROUTE_TYPE_REJECT +) + +func (t RouteType) String() string { + switch t { + case ROUTE_TYPE_NONE: + return "continue" + case ROUTE_TYPE_ACCEPT: + return "accept" + case ROUTE_TYPE_REJECT: + return "reject" + } + return fmt.Sprintf("unknown(%d)", t) +} + +type PolicyDirection int + +const ( + POLICY_DIRECTION_NONE PolicyDirection = iota + POLICY_DIRECTION_IN + POLICY_DIRECTION_IMPORT + POLICY_DIRECTION_EXPORT +) + +func (d PolicyDirection) String() string { + switch d { + case POLICY_DIRECTION_IN: + return "in" + case POLICY_DIRECTION_IMPORT: + return "import" + case POLICY_DIRECTION_EXPORT: + return "export" + } + return fmt.Sprintf("unknown(%d)", d) +} + +type MatchOption int + +const ( + MATCH_OPTION_ANY MatchOption = iota + MATCH_OPTION_ALL + MATCH_OPTION_INVERT +) + +func (o MatchOption) String() string { + switch o { + case MATCH_OPTION_ANY: + return "any" + case MATCH_OPTION_ALL: + return "all" + case MATCH_OPTION_INVERT: + return "invert" + default: + return fmt.Sprintf("MatchOption(%d)", o) + } +} + +func (o MatchOption) ConvertToMatchSetOptionsRestrictedType() config.MatchSetOptionsRestrictedType { + switch o { + case MATCH_OPTION_ANY: + return config.MATCH_SET_OPTIONS_RESTRICTED_TYPE_ANY + case MATCH_OPTION_INVERT: + return config.MATCH_SET_OPTIONS_RESTRICTED_TYPE_INVERT + } + return "unknown" +} + +type MedActionType int + +const ( + MED_ACTION_MOD MedActionType = iota + MED_ACTION_REPLACE +) + +var CommunityOptionNameMap = map[config.BgpSetCommunityOptionType]string{ + config.BGP_SET_COMMUNITY_OPTION_TYPE_ADD: "add", + config.BGP_SET_COMMUNITY_OPTION_TYPE_REMOVE: "remove", + config.BGP_SET_COMMUNITY_OPTION_TYPE_REPLACE: "replace", +} + +var CommunityOptionValueMap = map[string]config.BgpSetCommunityOptionType{ + CommunityOptionNameMap[config.BGP_SET_COMMUNITY_OPTION_TYPE_ADD]: config.BGP_SET_COMMUNITY_OPTION_TYPE_ADD, + CommunityOptionNameMap[config.BGP_SET_COMMUNITY_OPTION_TYPE_REMOVE]: config.BGP_SET_COMMUNITY_OPTION_TYPE_REMOVE, + CommunityOptionNameMap[config.BGP_SET_COMMUNITY_OPTION_TYPE_REPLACE]: config.BGP_SET_COMMUNITY_OPTION_TYPE_REPLACE, +} + +type ConditionType int + +const ( + CONDITION_PREFIX ConditionType = iota + CONDITION_NEIGHBOR + CONDITION_AS_PATH + CONDITION_COMMUNITY + CONDITION_EXT_COMMUNITY + CONDITION_AS_PATH_LENGTH + CONDITION_RPKI + CONDITION_ROUTE_TYPE + CONDITION_LARGE_COMMUNITY + CONDITION_NEXT_HOP + CONDITION_AFI_SAFI_IN +) + +type ActionType int + +const ( + ACTION_ROUTING ActionType = iota + ACTION_COMMUNITY + ACTION_EXT_COMMUNITY + ACTION_MED + ACTION_AS_PATH_PREPEND + ACTION_NEXTHOP + ACTION_LOCAL_PREF + ACTION_LARGE_COMMUNITY +) + +func NewMatchOption(c interface{}) (MatchOption, error) { + switch t := c.(type) { + case config.MatchSetOptionsType: + t = t.DefaultAsNeeded() + switch t { + case config.MATCH_SET_OPTIONS_TYPE_ANY: + return MATCH_OPTION_ANY, nil + case config.MATCH_SET_OPTIONS_TYPE_ALL: + return MATCH_OPTION_ALL, nil + case config.MATCH_SET_OPTIONS_TYPE_INVERT: + return MATCH_OPTION_INVERT, nil + } + case config.MatchSetOptionsRestrictedType: + t = t.DefaultAsNeeded() + switch t { + case config.MATCH_SET_OPTIONS_RESTRICTED_TYPE_ANY: + return MATCH_OPTION_ANY, nil + case config.MATCH_SET_OPTIONS_RESTRICTED_TYPE_INVERT: + return MATCH_OPTION_INVERT, nil + } + } + return MATCH_OPTION_ANY, fmt.Errorf("invalid argument to create match option: %v", c) +} + +type AttributeComparison int + +const ( + // "== comparison" + ATTRIBUTE_EQ AttributeComparison = iota + // ">= comparison" + ATTRIBUTE_GE + // "<= comparison" + ATTRIBUTE_LE +) + +func (c AttributeComparison) String() string { + switch c { + case ATTRIBUTE_EQ: + return "=" + case ATTRIBUTE_GE: + return ">=" + case ATTRIBUTE_LE: + return "<=" + } + return "?" +} + +const ( + ASPATH_REGEXP_MAGIC = "(^|[,{}() ]|$)" +) + +type DefinedSet interface { + Type() DefinedType + Name() string + Append(DefinedSet) error + Remove(DefinedSet) error + Replace(DefinedSet) error + String() string + List() []string +} + +type DefinedSetMap map[DefinedType]map[string]DefinedSet + +type DefinedSetList []DefinedSet + +func (l DefinedSetList) Len() int { + return len(l) +} + +func (l DefinedSetList) Swap(i, j int) { + l[i], l[j] = l[j], l[i] +} + +func (l DefinedSetList) Less(i, j int) bool { + if l[i].Type() != l[j].Type() { + return l[i].Type() < l[j].Type() + } + return l[i].Name() < l[j].Name() +} + +type Prefix struct { + Prefix *net.IPNet + AddressFamily bgp.RouteFamily + MasklengthRangeMax uint8 + MasklengthRangeMin uint8 +} + +func (p *Prefix) Match(path *Path) bool { + rf := path.GetRouteFamily() + if rf != p.AddressFamily { + return false + } + + var pAddr net.IP + var pMasklen uint8 + switch rf { + case bgp.RF_IPv4_UC: + pAddr = path.GetNlri().(*bgp.IPAddrPrefix).Prefix + pMasklen = path.GetNlri().(*bgp.IPAddrPrefix).Length + case bgp.RF_IPv6_UC: + pAddr = path.GetNlri().(*bgp.IPv6AddrPrefix).Prefix + pMasklen = path.GetNlri().(*bgp.IPv6AddrPrefix).Length + default: + return false + } + + return (p.MasklengthRangeMin <= pMasklen && pMasklen <= p.MasklengthRangeMax) && p.Prefix.Contains(pAddr) +} + +func (lhs *Prefix) Equal(rhs *Prefix) bool { + if lhs == rhs { + return true + } + if rhs == nil { + return false + } + return lhs.Prefix.String() == rhs.Prefix.String() && lhs.MasklengthRangeMin == rhs.MasklengthRangeMin && lhs.MasklengthRangeMax == rhs.MasklengthRangeMax +} + +func (p *Prefix) PrefixString() string { + isZeros := func(p net.IP) bool { + for i := 0; i < len(p); i++ { + if p[i] != 0 { + return false + } + } + return true + } + + ip := p.Prefix.IP + if p.AddressFamily == bgp.RF_IPv6_UC && isZeros(ip[0:10]) && ip[10] == 0xff && ip[11] == 0xff { + m, _ := p.Prefix.Mask.Size() + return fmt.Sprintf("::FFFF:%s/%d", ip.To16(), m) + } + return p.Prefix.String() +} + +var _regexpPrefixRange = regexp.MustCompile(`(\d+)\.\.(\d+)`) + +func NewPrefix(c config.Prefix) (*Prefix, error) { + _, prefix, err := net.ParseCIDR(c.IpPrefix) + if err != nil { + return nil, err + } + + rf := bgp.RF_IPv4_UC + if strings.Contains(c.IpPrefix, ":") { + rf = bgp.RF_IPv6_UC + } + p := &Prefix{ + Prefix: prefix, + AddressFamily: rf, + } + maskRange := c.MasklengthRange + + if maskRange == "" { + l, _ := prefix.Mask.Size() + maskLength := uint8(l) + p.MasklengthRangeMax = maskLength + p.MasklengthRangeMin = maskLength + return p, nil + } + + elems := _regexpPrefixRange.FindStringSubmatch(maskRange) + if len(elems) != 3 { + log.WithFields(log.Fields{ + "Topic": "Policy", + "Type": "Prefix", + "MaskRangeFormat": maskRange, + }).Warn("mask length range format is invalid.") + return nil, fmt.Errorf("mask length range format is invalid") + } + + // we've already checked the range is sane by regexp + min, _ := strconv.ParseUint(elems[1], 10, 8) + max, _ := strconv.ParseUint(elems[2], 10, 8) + p.MasklengthRangeMin = uint8(min) + p.MasklengthRangeMax = uint8(max) + return p, nil +} + +type PrefixSet struct { + name string + tree *radix.Tree + family bgp.RouteFamily +} + +func (s *PrefixSet) Name() string { + return s.name +} + +func (s *PrefixSet) Type() DefinedType { + return DEFINED_TYPE_PREFIX +} + +func (lhs *PrefixSet) Append(arg DefinedSet) error { + rhs, ok := arg.(*PrefixSet) + if !ok { + return fmt.Errorf("type cast failed") + } + // if either is empty, family can be ignored. + if lhs.tree.Len() != 0 && rhs.tree.Len() != 0 { + _, w, _ := lhs.tree.Minimum() + l := w.([]*Prefix) + _, v, _ := rhs.tree.Minimum() + r := v.([]*Prefix) + if l[0].AddressFamily != r[0].AddressFamily { + return fmt.Errorf("can't append different family") + } + } + rhs.tree.Walk(func(key string, v interface{}) bool { + w, ok := lhs.tree.Get(key) + if ok { + r := v.([]*Prefix) + l := w.([]*Prefix) + lhs.tree.Insert(key, append(l, r...)) + } else { + lhs.tree.Insert(key, v) + } + return false + }) + _, w, _ := lhs.tree.Minimum() + lhs.family = w.([]*Prefix)[0].AddressFamily + return nil +} + +func (lhs *PrefixSet) Remove(arg DefinedSet) error { + rhs, ok := arg.(*PrefixSet) + if !ok { + return fmt.Errorf("type cast failed") + } + rhs.tree.Walk(func(key string, v interface{}) bool { + w, ok := lhs.tree.Get(key) + if !ok { + return false + } + r := v.([]*Prefix) + l := w.([]*Prefix) + new := make([]*Prefix, 0, len(l)) + for _, lp := range l { + delete := false + for _, rp := range r { + if lp.Equal(rp) { + delete = true + break + } + } + if !delete { + new = append(new, lp) + } + } + if len(new) == 0 { + lhs.tree.Delete(key) + } else { + lhs.tree.Insert(key, new) + } + return false + }) + return nil +} + +func (lhs *PrefixSet) Replace(arg DefinedSet) error { + rhs, ok := arg.(*PrefixSet) + if !ok { + return fmt.Errorf("type cast failed") + } + lhs.tree = rhs.tree + lhs.family = rhs.family + return nil +} + +func (s *PrefixSet) List() []string { + var list []string + s.tree.Walk(func(s string, v interface{}) bool { + ps := v.([]*Prefix) + for _, p := range ps { + list = append(list, fmt.Sprintf("%s %d..%d", p.PrefixString(), p.MasklengthRangeMin, p.MasklengthRangeMax)) + } + return false + }) + return list +} + +func (s *PrefixSet) ToConfig() *config.PrefixSet { + list := make([]config.Prefix, 0, s.tree.Len()) + s.tree.Walk(func(s string, v interface{}) bool { + ps := v.([]*Prefix) + for _, p := range ps { + list = append(list, config.Prefix{IpPrefix: p.PrefixString(), MasklengthRange: fmt.Sprintf("%d..%d", p.MasklengthRangeMin, p.MasklengthRangeMax)}) + } + return false + }) + return &config.PrefixSet{ + PrefixSetName: s.name, + PrefixList: list, + } +} + +func (s *PrefixSet) String() string { + return strings.Join(s.List(), "\n") +} + +func (s *PrefixSet) MarshalJSON() ([]byte, error) { + return json.Marshal(s.ToConfig()) +} + +func NewPrefixSetFromApiStruct(name string, prefixes []*Prefix) (*PrefixSet, error) { + if name == "" { + return nil, fmt.Errorf("empty prefix set name") + } + tree := radix.New() + var family bgp.RouteFamily + for i, x := range prefixes { + if i == 0 { + family = x.AddressFamily + } else if family != x.AddressFamily { + return nil, fmt.Errorf("multiple families") + } + key := CidrToRadixkey(x.Prefix.String()) + d, ok := tree.Get(key) + if ok { + ps := d.([]*Prefix) + tree.Insert(key, append(ps, x)) + } else { + tree.Insert(key, []*Prefix{x}) + } + } + return &PrefixSet{ + name: name, + tree: tree, + family: family, + }, nil +} + +func NewPrefixSet(c config.PrefixSet) (*PrefixSet, error) { + name := c.PrefixSetName + if name == "" { + if len(c.PrefixList) == 0 { + return nil, nil + } + return nil, fmt.Errorf("empty prefix set name") + } + tree := radix.New() + var family bgp.RouteFamily + for i, x := range c.PrefixList { + y, err := NewPrefix(x) + if err != nil { + return nil, err + } + if i == 0 { + family = y.AddressFamily + } else if family != y.AddressFamily { + return nil, fmt.Errorf("multiple families") + } + key := CidrToRadixkey(y.Prefix.String()) + d, ok := tree.Get(key) + if ok { + ps := d.([]*Prefix) + tree.Insert(key, append(ps, y)) + } else { + tree.Insert(key, []*Prefix{y}) + } + } + return &PrefixSet{ + name: name, + tree: tree, + family: family, + }, nil +} + +type NextHopSet struct { + list []net.IPNet +} + +func (s *NextHopSet) Name() string { + return "NextHopSet: NO NAME" +} + +func (s *NextHopSet) Type() DefinedType { + return DEFINED_TYPE_NEXT_HOP +} + +func (lhs *NextHopSet) Append(arg DefinedSet) error { + rhs, ok := arg.(*NextHopSet) + if !ok { + return fmt.Errorf("type cast failed") + } + lhs.list = append(lhs.list, rhs.list...) + return nil +} + +func (lhs *NextHopSet) Remove(arg DefinedSet) error { + rhs, ok := arg.(*NextHopSet) + if !ok { + return fmt.Errorf("type cast failed") + } + ps := make([]net.IPNet, 0, len(lhs.list)) + for _, x := range lhs.list { + found := false + for _, y := range rhs.list { + if x.String() == y.String() { + found = true + break + } + } + if !found { + ps = append(ps, x) + } + } + lhs.list = ps + return nil +} + +func (lhs *NextHopSet) Replace(arg DefinedSet) error { + rhs, ok := arg.(*NextHopSet) + if !ok { + return fmt.Errorf("type cast failed") + } + lhs.list = rhs.list + return nil +} + +func (s *NextHopSet) List() []string { + list := make([]string, 0, len(s.list)) + for _, n := range s.list { + list = append(list, n.String()) + } + return list +} + +func (s *NextHopSet) ToConfig() []string { + return s.List() +} + +func (s *NextHopSet) String() string { + return "[ " + strings.Join(s.List(), ", ") + " ]" +} + +func (s *NextHopSet) MarshalJSON() ([]byte, error) { + return json.Marshal(s.ToConfig()) +} + +func NewNextHopSetFromApiStruct(name string, list []net.IPNet) (*NextHopSet, error) { + return &NextHopSet{ + list: list, + }, nil +} + +func NewNextHopSet(c []string) (*NextHopSet, error) { + list := make([]net.IPNet, 0, len(c)) + for _, x := range c { + _, cidr, err := net.ParseCIDR(x) + if err != nil { + addr := net.ParseIP(x) + if addr == nil { + return nil, fmt.Errorf("invalid address or prefix: %s", x) + } + mask := net.CIDRMask(32, 32) + if addr.To4() == nil { + mask = net.CIDRMask(128, 128) + } + cidr = &net.IPNet{ + IP: addr, + Mask: mask, + } + } + list = append(list, *cidr) + } + return &NextHopSet{ + list: list, + }, nil +} + +type NeighborSet struct { + name string + list []net.IPNet +} + +func (s *NeighborSet) Name() string { + return s.name +} + +func (s *NeighborSet) Type() DefinedType { + return DEFINED_TYPE_NEIGHBOR +} + +func (lhs *NeighborSet) Append(arg DefinedSet) error { + rhs, ok := arg.(*NeighborSet) + if !ok { + return fmt.Errorf("type cast failed") + } + lhs.list = append(lhs.list, rhs.list...) + return nil +} + +func (lhs *NeighborSet) Remove(arg DefinedSet) error { + rhs, ok := arg.(*NeighborSet) + if !ok { + return fmt.Errorf("type cast failed") + } + ps := make([]net.IPNet, 0, len(lhs.list)) + for _, x := range lhs.list { + found := false + for _, y := range rhs.list { + if x.String() == y.String() { + found = true + break + } + } + if !found { + ps = append(ps, x) + } + } + lhs.list = ps + return nil +} + +func (lhs *NeighborSet) Replace(arg DefinedSet) error { + rhs, ok := arg.(*NeighborSet) + if !ok { + return fmt.Errorf("type cast failed") + } + lhs.list = rhs.list + return nil +} + +func (s *NeighborSet) List() []string { + list := make([]string, 0, len(s.list)) + for _, n := range s.list { + list = append(list, n.String()) + } + return list +} + +func (s *NeighborSet) ToConfig() *config.NeighborSet { + return &config.NeighborSet{ + NeighborSetName: s.name, + NeighborInfoList: s.List(), + } +} + +func (s *NeighborSet) String() string { + return strings.Join(s.List(), "\n") +} + +func (s *NeighborSet) MarshalJSON() ([]byte, error) { + return json.Marshal(s.ToConfig()) +} + +func NewNeighborSetFromApiStruct(name string, list []net.IPNet) (*NeighborSet, error) { + return &NeighborSet{ + name: name, + list: list, + }, nil +} + +func NewNeighborSet(c config.NeighborSet) (*NeighborSet, error) { + name := c.NeighborSetName + if name == "" { + if len(c.NeighborInfoList) == 0 { + return nil, nil + } + return nil, fmt.Errorf("empty neighbor set name") + } + list := make([]net.IPNet, 0, len(c.NeighborInfoList)) + for _, x := range c.NeighborInfoList { + _, cidr, err := net.ParseCIDR(x) + if err != nil { + addr := net.ParseIP(x) + if addr == nil { + return nil, fmt.Errorf("invalid address or prefix: %s", x) + } + mask := net.CIDRMask(32, 32) + if addr.To4() == nil { + mask = net.CIDRMask(128, 128) + } + cidr = &net.IPNet{ + IP: addr, + Mask: mask, + } + } + list = append(list, *cidr) + } + return &NeighborSet{ + name: name, + list: list, + }, nil +} + +type singleAsPathMatchMode int + +const ( + INCLUDE singleAsPathMatchMode = iota + LEFT_MOST + ORIGIN + ONLY +) + +type singleAsPathMatch struct { + asn uint32 + mode singleAsPathMatchMode +} + +func (lhs *singleAsPathMatch) Equal(rhs *singleAsPathMatch) bool { + return lhs.asn == rhs.asn && lhs.mode == rhs.mode +} + +func (lhs *singleAsPathMatch) String() string { + switch lhs.mode { + case INCLUDE: + return fmt.Sprintf("_%d_", lhs.asn) + case LEFT_MOST: + return fmt.Sprintf("^%d_", lhs.asn) + case ORIGIN: + return fmt.Sprintf("_%d$", lhs.asn) + case ONLY: + return fmt.Sprintf("^%d$", lhs.asn) + } + return "" +} + +func (m *singleAsPathMatch) Match(aspath []uint32) bool { + if len(aspath) == 0 { + return false + } + switch m.mode { + case INCLUDE: + for _, asn := range aspath { + if m.asn == asn { + return true + } + } + case LEFT_MOST: + if m.asn == aspath[0] { + return true + } + case ORIGIN: + if m.asn == aspath[len(aspath)-1] { + return true + } + case ONLY: + if len(aspath) == 1 && m.asn == aspath[0] { + return true + } + } + return false +} + +var ( + _regexpLeftMostRe = regexp.MustCompile(`$\^([0-9]+)_^`) + _regexpOriginRe = regexp.MustCompile(`^_([0-9]+)\$$`) + _regexpIncludeRe = regexp.MustCompile("^_([0-9]+)_$") + _regexpOnlyRe = regexp.MustCompile(`^\^([0-9]+)\$$`) +) + +func NewSingleAsPathMatch(arg string) *singleAsPathMatch { + switch { + case _regexpLeftMostRe.MatchString(arg): + asn, _ := strconv.ParseUint(_regexpLeftMostRe.FindStringSubmatch(arg)[1], 10, 32) + return &singleAsPathMatch{ + asn: uint32(asn), + mode: LEFT_MOST, + } + case _regexpOriginRe.MatchString(arg): + asn, _ := strconv.ParseUint(_regexpOriginRe.FindStringSubmatch(arg)[1], 10, 32) + return &singleAsPathMatch{ + asn: uint32(asn), + mode: ORIGIN, + } + case _regexpIncludeRe.MatchString(arg): + asn, _ := strconv.ParseUint(_regexpIncludeRe.FindStringSubmatch(arg)[1], 10, 32) + return &singleAsPathMatch{ + asn: uint32(asn), + mode: INCLUDE, + } + case _regexpOnlyRe.MatchString(arg): + asn, _ := strconv.ParseUint(_regexpOnlyRe.FindStringSubmatch(arg)[1], 10, 32) + return &singleAsPathMatch{ + asn: uint32(asn), + mode: ONLY, + } + } + return nil +} + +type AsPathSet struct { + typ DefinedType + name string + list []*regexp.Regexp + singleList []*singleAsPathMatch +} + +func (s *AsPathSet) Name() string { + return s.name +} + +func (s *AsPathSet) Type() DefinedType { + return s.typ +} + +func (lhs *AsPathSet) Append(arg DefinedSet) error { + if lhs.Type() != arg.Type() { + return fmt.Errorf("can't append to different type of defined-set") + } + lhs.list = append(lhs.list, arg.(*AsPathSet).list...) + lhs.singleList = append(lhs.singleList, arg.(*AsPathSet).singleList...) + return nil +} + +func (lhs *AsPathSet) Remove(arg DefinedSet) error { + if lhs.Type() != arg.Type() { + return fmt.Errorf("can't append to different type of defined-set") + } + newList := make([]*regexp.Regexp, 0, len(lhs.list)) + for _, x := range lhs.list { + found := false + for _, y := range arg.(*AsPathSet).list { + if x.String() == y.String() { + found = true + break + } + } + if !found { + newList = append(newList, x) + } + } + lhs.list = newList + newSingleList := make([]*singleAsPathMatch, 0, len(lhs.singleList)) + for _, x := range lhs.singleList { + found := false + for _, y := range arg.(*AsPathSet).singleList { + if x.Equal(y) { + found = true + break + } + } + if !found { + newSingleList = append(newSingleList, x) + } + } + lhs.singleList = newSingleList + return nil +} + +func (lhs *AsPathSet) Replace(arg DefinedSet) error { + rhs, ok := arg.(*AsPathSet) + if !ok { + return fmt.Errorf("type cast failed") + } + lhs.list = rhs.list + lhs.singleList = rhs.singleList + return nil +} + +func (s *AsPathSet) List() []string { + list := make([]string, 0, len(s.list)+len(s.singleList)) + for _, exp := range s.singleList { + list = append(list, exp.String()) + } + for _, exp := range s.list { + list = append(list, exp.String()) + } + return list +} + +func (s *AsPathSet) ToConfig() *config.AsPathSet { + return &config.AsPathSet{ + AsPathSetName: s.name, + AsPathList: s.List(), + } +} + +func (s *AsPathSet) String() string { + return strings.Join(s.List(), "\n") +} + +func (s *AsPathSet) MarshalJSON() ([]byte, error) { + return json.Marshal(s.ToConfig()) +} + +func NewAsPathSet(c config.AsPathSet) (*AsPathSet, error) { + name := c.AsPathSetName + if name == "" { + if len(c.AsPathList) == 0 { + return nil, nil + } + return nil, fmt.Errorf("empty as-path set name") + } + list := make([]*regexp.Regexp, 0, len(c.AsPathList)) + singleList := make([]*singleAsPathMatch, 0, len(c.AsPathList)) + for _, x := range c.AsPathList { + if s := NewSingleAsPathMatch(x); s != nil { + singleList = append(singleList, s) + } else { + exp, err := regexp.Compile(strings.Replace(x, "_", ASPATH_REGEXP_MAGIC, -1)) + if err != nil { + return nil, fmt.Errorf("invalid regular expression: %s", x) + } + list = append(list, exp) + } + } + return &AsPathSet{ + typ: DEFINED_TYPE_AS_PATH, + name: name, + list: list, + singleList: singleList, + }, nil +} + +type regExpSet struct { + typ DefinedType + name string + list []*regexp.Regexp +} + +func (s *regExpSet) Name() string { + return s.name +} + +func (s *regExpSet) Type() DefinedType { + return s.typ +} + +func (lhs *regExpSet) Append(arg DefinedSet) error { + if lhs.Type() != arg.Type() { + return fmt.Errorf("can't append to different type of defined-set") + } + var list []*regexp.Regexp + switch lhs.Type() { + case DEFINED_TYPE_AS_PATH: + list = arg.(*AsPathSet).list + case DEFINED_TYPE_COMMUNITY: + list = arg.(*CommunitySet).list + case DEFINED_TYPE_EXT_COMMUNITY: + list = arg.(*ExtCommunitySet).list + case DEFINED_TYPE_LARGE_COMMUNITY: + list = arg.(*LargeCommunitySet).list + default: + return fmt.Errorf("invalid defined-set type: %d", lhs.Type()) + } + lhs.list = append(lhs.list, list...) + return nil +} + +func (lhs *regExpSet) Remove(arg DefinedSet) error { + if lhs.Type() != arg.Type() { + return fmt.Errorf("can't append to different type of defined-set") + } + var list []*regexp.Regexp + switch lhs.Type() { + case DEFINED_TYPE_AS_PATH: + list = arg.(*AsPathSet).list + case DEFINED_TYPE_COMMUNITY: + list = arg.(*CommunitySet).list + case DEFINED_TYPE_EXT_COMMUNITY: + list = arg.(*ExtCommunitySet).list + case DEFINED_TYPE_LARGE_COMMUNITY: + list = arg.(*LargeCommunitySet).list + default: + return fmt.Errorf("invalid defined-set type: %d", lhs.Type()) + } + ps := make([]*regexp.Regexp, 0, len(lhs.list)) + for _, x := range lhs.list { + found := false + for _, y := range list { + if x.String() == y.String() { + found = true + break + } + } + if !found { + ps = append(ps, x) + } + } + lhs.list = ps + return nil +} + +func (lhs *regExpSet) Replace(arg DefinedSet) error { + switch c := arg.(type) { + case *CommunitySet: + lhs.list = c.list + case *ExtCommunitySet: + lhs.list = c.list + case *LargeCommunitySet: + lhs.list = c.list + default: + return fmt.Errorf("type cast failed") + } + return nil +} + +type CommunitySet struct { + regExpSet +} + +func (s *CommunitySet) List() []string { + list := make([]string, 0, len(s.list)) + for _, exp := range s.list { + list = append(list, exp.String()) + } + return list +} + +func (s *CommunitySet) ToConfig() *config.CommunitySet { + return &config.CommunitySet{ + CommunitySetName: s.name, + CommunityList: s.List(), + } +} + +func (s *CommunitySet) String() string { + return strings.Join(s.List(), "\n") +} + +func (s *CommunitySet) MarshalJSON() ([]byte, error) { + return json.Marshal(s.ToConfig()) +} + +var _regexpCommunity = regexp.MustCompile(`(\d+):(\d+)`) + +func ParseCommunity(arg string) (uint32, error) { + i, err := strconv.ParseUint(arg, 10, 32) + if err == nil { + return uint32(i), nil + } + + elems := _regexpCommunity.FindStringSubmatch(arg) + if len(elems) == 3 { + fst, _ := strconv.ParseUint(elems[1], 10, 16) + snd, _ := strconv.ParseUint(elems[2], 10, 16) + return uint32(fst<<16 | snd), nil + } + for i, v := range bgp.WellKnownCommunityNameMap { + if arg == v { + return uint32(i), nil + } + } + return 0, fmt.Errorf("failed to parse %s as community", arg) +} + +func ParseExtCommunity(arg string) (bgp.ExtendedCommunityInterface, error) { + var subtype bgp.ExtendedCommunityAttrSubType + var value string + elems := strings.SplitN(arg, ":", 2) + + isValidationState := func(s string) bool { + s = strings.ToLower(s) + r := s == bgp.VALIDATION_STATE_VALID.String() + r = r || s == bgp.VALIDATION_STATE_NOT_FOUND.String() + return r || s == bgp.VALIDATION_STATE_INVALID.String() + } + if len(elems) < 2 && (len(elems) < 1 && !isValidationState(elems[0])) { + return nil, fmt.Errorf("invalid ext-community (rt|soo):<value> | valid | not-found | invalid") + } + if isValidationState(elems[0]) { + subtype = bgp.EC_SUBTYPE_ORIGIN_VALIDATION + value = elems[0] + } else { + switch strings.ToLower(elems[0]) { + case "rt": + subtype = bgp.EC_SUBTYPE_ROUTE_TARGET + case "soo": + subtype = bgp.EC_SUBTYPE_ROUTE_ORIGIN + default: + return nil, fmt.Errorf("invalid ext-community (rt|soo):<value> | valid | not-found | invalid") + } + value = elems[1] + } + return bgp.ParseExtendedCommunity(subtype, value) +} + +var _regexpCommunity2 = regexp.MustCompile(`(\d+.)*\d+:\d+`) + +func ParseCommunityRegexp(arg string) (*regexp.Regexp, error) { + i, err := strconv.ParseUint(arg, 10, 32) + if err == nil { + return regexp.Compile(fmt.Sprintf("^%d:%d$", i>>16, i&0x0000ffff)) + } + + if _regexpCommunity2.MatchString(arg) { + return regexp.Compile(fmt.Sprintf("^%s$", arg)) + } + + for i, v := range bgp.WellKnownCommunityNameMap { + if strings.Replace(strings.ToLower(arg), "_", "-", -1) == v { + return regexp.Compile(fmt.Sprintf("^%d:%d$", i>>16, i&0x0000ffff)) + } + } + + return regexp.Compile(arg) +} + +func ParseExtCommunityRegexp(arg string) (bgp.ExtendedCommunityAttrSubType, *regexp.Regexp, error) { + var subtype bgp.ExtendedCommunityAttrSubType + elems := strings.SplitN(arg, ":", 2) + if len(elems) < 2 { + return subtype, nil, fmt.Errorf("invalid ext-community format([rt|soo]:<value>)") + } + switch strings.ToLower(elems[0]) { + case "rt": + subtype = bgp.EC_SUBTYPE_ROUTE_TARGET + case "soo": + subtype = bgp.EC_SUBTYPE_ROUTE_ORIGIN + default: + return subtype, nil, fmt.Errorf("unknown ext-community subtype. rt, soo is supported") + } + exp, err := ParseCommunityRegexp(elems[1]) + return subtype, exp, err +} + +func NewCommunitySet(c config.CommunitySet) (*CommunitySet, error) { + name := c.CommunitySetName + if name == "" { + if len(c.CommunityList) == 0 { + return nil, nil + } + return nil, fmt.Errorf("empty community set name") + } + list := make([]*regexp.Regexp, 0, len(c.CommunityList)) + for _, x := range c.CommunityList { + exp, err := ParseCommunityRegexp(x) + if err != nil { + return nil, err + } + list = append(list, exp) + } + return &CommunitySet{ + regExpSet: regExpSet{ + typ: DEFINED_TYPE_COMMUNITY, + name: name, + list: list, + }, + }, nil +} + +type ExtCommunitySet struct { + regExpSet + subtypeList []bgp.ExtendedCommunityAttrSubType +} + +func (s *ExtCommunitySet) List() []string { + list := make([]string, 0, len(s.list)) + f := func(idx int, arg string) string { + switch s.subtypeList[idx] { + case bgp.EC_SUBTYPE_ROUTE_TARGET: + return fmt.Sprintf("rt:%s", arg) + case bgp.EC_SUBTYPE_ROUTE_ORIGIN: + return fmt.Sprintf("soo:%s", arg) + case bgp.EC_SUBTYPE_ORIGIN_VALIDATION: + return arg + default: + return fmt.Sprintf("%d:%s", s.subtypeList[idx], arg) + } + } + for idx, exp := range s.list { + list = append(list, f(idx, exp.String())) + } + return list +} + +func (s *ExtCommunitySet) ToConfig() *config.ExtCommunitySet { + return &config.ExtCommunitySet{ + ExtCommunitySetName: s.name, + ExtCommunityList: s.List(), + } +} + +func (s *ExtCommunitySet) String() string { + return strings.Join(s.List(), "\n") +} + +func (s *ExtCommunitySet) MarshalJSON() ([]byte, error) { + return json.Marshal(s.ToConfig()) +} + +func NewExtCommunitySet(c config.ExtCommunitySet) (*ExtCommunitySet, error) { + name := c.ExtCommunitySetName + if name == "" { + if len(c.ExtCommunityList) == 0 { + return nil, nil + } + return nil, fmt.Errorf("empty ext-community set name") + } + list := make([]*regexp.Regexp, 0, len(c.ExtCommunityList)) + subtypeList := make([]bgp.ExtendedCommunityAttrSubType, 0, len(c.ExtCommunityList)) + for _, x := range c.ExtCommunityList { + subtype, exp, err := ParseExtCommunityRegexp(x) + if err != nil { + return nil, err + } + list = append(list, exp) + subtypeList = append(subtypeList, subtype) + } + return &ExtCommunitySet{ + regExpSet: regExpSet{ + typ: DEFINED_TYPE_EXT_COMMUNITY, + name: name, + list: list, + }, + subtypeList: subtypeList, + }, nil +} + +func (s *ExtCommunitySet) Append(arg DefinedSet) error { + err := s.regExpSet.Append(arg) + if err != nil { + return err + } + sList := arg.(*ExtCommunitySet).subtypeList + s.subtypeList = append(s.subtypeList, sList...) + return nil +} + +type LargeCommunitySet struct { + regExpSet +} + +func (s *LargeCommunitySet) List() []string { + list := make([]string, 0, len(s.list)) + for _, exp := range s.list { + list = append(list, exp.String()) + } + return list +} + +func (s *LargeCommunitySet) ToConfig() *config.LargeCommunitySet { + return &config.LargeCommunitySet{ + LargeCommunitySetName: s.name, + LargeCommunityList: s.List(), + } +} + +func (s *LargeCommunitySet) String() string { + return strings.Join(s.List(), "\n") +} + +func (s *LargeCommunitySet) MarshalJSON() ([]byte, error) { + return json.Marshal(s.ToConfig()) +} + +var _regexpCommunityLarge = regexp.MustCompile(`\d+:\d+:\d+`) + +func ParseLargeCommunityRegexp(arg string) (*regexp.Regexp, error) { + if _regexpCommunityLarge.MatchString(arg) { + return regexp.Compile(fmt.Sprintf("^%s$", arg)) + } + exp, err := regexp.Compile(arg) + if err != nil { + return nil, fmt.Errorf("invalid large-community format: %v", err) + } + + return exp, nil +} + +func NewLargeCommunitySet(c config.LargeCommunitySet) (*LargeCommunitySet, error) { + name := c.LargeCommunitySetName + if name == "" { + if len(c.LargeCommunityList) == 0 { + return nil, nil + } + return nil, fmt.Errorf("empty large community set name") + } + list := make([]*regexp.Regexp, 0, len(c.LargeCommunityList)) + for _, x := range c.LargeCommunityList { + exp, err := ParseLargeCommunityRegexp(x) + if err != nil { + return nil, err + } + list = append(list, exp) + } + return &LargeCommunitySet{ + regExpSet: regExpSet{ + typ: DEFINED_TYPE_LARGE_COMMUNITY, + name: name, + list: list, + }, + }, nil +} + +type Condition interface { + Name() string + Type() ConditionType + Evaluate(*Path, *PolicyOptions) bool + Set() DefinedSet +} + +type NextHopCondition struct { + set *NextHopSet +} + +func (c *NextHopCondition) Type() ConditionType { + return CONDITION_NEXT_HOP +} + +func (c *NextHopCondition) Set() DefinedSet { + return c.set +} + +func (c *NextHopCondition) Name() string { return "" } + +func (c *NextHopCondition) String() string { + return c.set.String() +} + +// compare next-hop ipaddress of this condition and source address of path +// and, subsequent comparisons are skipped if that matches the conditions. +// If NextHopSet's length is zero, return true. +func (c *NextHopCondition) Evaluate(path *Path, options *PolicyOptions) bool { + if len(c.set.list) == 0 { + log.WithFields(log.Fields{ + "Topic": "Policy", + }).Debug("NextHop doesn't have elements") + return true + } + + nexthop := path.GetNexthop() + + // In cases where we advertise routes from iBGP to eBGP, we want to filter + // on the "original" nexthop. The current paths' nexthop has already been + // set and is ready to be advertised as per: + // https://tools.ietf.org/html/rfc4271#section-5.1.3 + if options != nil && options.OldNextHop != nil && + !options.OldNextHop.IsUnspecified() && !options.OldNextHop.Equal(nexthop) { + nexthop = options.OldNextHop + } + + if nexthop == nil { + return false + } + + for _, n := range c.set.list { + if n.Contains(nexthop) { + return true + } + } + + return false +} + +func NewNextHopCondition(c []string) (*NextHopCondition, error) { + if len(c) == 0 { + return nil, nil + } + + list, err := NewNextHopSet(c) + if err != nil { + return nil, nil + } + + return &NextHopCondition{ + set: list, + }, nil +} + +type PrefixCondition struct { + set *PrefixSet + option MatchOption +} + +func (c *PrefixCondition) Type() ConditionType { + return CONDITION_PREFIX +} + +func (c *PrefixCondition) Set() DefinedSet { + return c.set +} + +func (c *PrefixCondition) Option() MatchOption { + return c.option +} + +// compare prefixes in this condition and nlri of path and +// subsequent comparison is skipped if that matches the conditions. +// If PrefixList's length is zero, return true. +func (c *PrefixCondition) Evaluate(path *Path, _ *PolicyOptions) bool { + var key string + var masklen uint8 + keyf := func(ip net.IP, ones int) string { + var buffer bytes.Buffer + for i := 0; i < len(ip) && i < ones; i++ { + buffer.WriteString(fmt.Sprintf("%08b", ip[i])) + } + return buffer.String()[:ones] + } + family := path.GetRouteFamily() + switch family { + case bgp.RF_IPv4_UC: + masklen = path.GetNlri().(*bgp.IPAddrPrefix).Length + key = keyf(path.GetNlri().(*bgp.IPAddrPrefix).Prefix, int(masklen)) + case bgp.RF_IPv6_UC: + masklen = path.GetNlri().(*bgp.IPv6AddrPrefix).Length + key = keyf(path.GetNlri().(*bgp.IPv6AddrPrefix).Prefix, int(masklen)) + default: + return false + } + if family != c.set.family { + return false + } + + result := false + _, ps, ok := c.set.tree.LongestPrefix(key) + if ok { + for _, p := range ps.([]*Prefix) { + if p.MasklengthRangeMin <= masklen && masklen <= p.MasklengthRangeMax { + result = true + break + } + } + } + + if c.option == MATCH_OPTION_INVERT { + result = !result + } + + return result +} + +func (c *PrefixCondition) Name() string { return c.set.name } + +func NewPrefixCondition(c config.MatchPrefixSet) (*PrefixCondition, error) { + if c.PrefixSet == "" { + return nil, nil + } + o, err := NewMatchOption(c.MatchSetOptions) + if err != nil { + return nil, err + } + return &PrefixCondition{ + set: &PrefixSet{ + name: c.PrefixSet, + }, + option: o, + }, nil +} + +type NeighborCondition struct { + set *NeighborSet + option MatchOption +} + +func (c *NeighborCondition) Type() ConditionType { + return CONDITION_NEIGHBOR +} + +func (c *NeighborCondition) Set() DefinedSet { + return c.set +} + +func (c *NeighborCondition) Option() MatchOption { + return c.option +} + +// compare neighbor ipaddress of this condition and source address of path +// and, subsequent comparisons are skipped if that matches the conditions. +// If NeighborList's length is zero, return true. +func (c *NeighborCondition) Evaluate(path *Path, options *PolicyOptions) bool { + if len(c.set.list) == 0 { + log.WithFields(log.Fields{ + "Topic": "Policy", + }).Debug("NeighborList doesn't have elements") + return true + } + + neighbor := path.GetSource().Address + if options != nil && options.Info != nil && options.Info.Address != nil { + neighbor = options.Info.Address + } + + if neighbor == nil { + return false + } + result := false + for _, n := range c.set.list { + if n.Contains(neighbor) { + result = true + break + } + } + + if c.option == MATCH_OPTION_INVERT { + result = !result + } + + return result +} + +func (c *NeighborCondition) Name() string { return c.set.name } + +func NewNeighborCondition(c config.MatchNeighborSet) (*NeighborCondition, error) { + if c.NeighborSet == "" { + return nil, nil + } + o, err := NewMatchOption(c.MatchSetOptions) + if err != nil { + return nil, err + } + return &NeighborCondition{ + set: &NeighborSet{ + name: c.NeighborSet, + }, + option: o, + }, nil +} + +type AsPathCondition struct { + set *AsPathSet + option MatchOption +} + +func (c *AsPathCondition) Type() ConditionType { + return CONDITION_AS_PATH +} + +func (c *AsPathCondition) Set() DefinedSet { + return c.set +} + +func (c *AsPathCondition) Option() MatchOption { + return c.option +} + +func (c *AsPathCondition) Evaluate(path *Path, _ *PolicyOptions) bool { + if len(c.set.singleList) > 0 { + aspath := path.GetAsSeqList() + for _, m := range c.set.singleList { + result := m.Match(aspath) + if c.option == MATCH_OPTION_ALL && !result { + return false + } + if c.option == MATCH_OPTION_ANY && result { + return true + } + if c.option == MATCH_OPTION_INVERT && result { + return false + } + } + } + if len(c.set.list) > 0 { + aspath := path.GetAsString() + for _, r := range c.set.list { + result := r.MatchString(aspath) + if c.option == MATCH_OPTION_ALL && !result { + return false + } + if c.option == MATCH_OPTION_ANY && result { + return true + } + if c.option == MATCH_OPTION_INVERT && result { + return false + } + } + } + if c.option == MATCH_OPTION_ANY { + return false + } + return true +} + +func (c *AsPathCondition) Name() string { return c.set.name } + +func NewAsPathCondition(c config.MatchAsPathSet) (*AsPathCondition, error) { + if c.AsPathSet == "" { + return nil, nil + } + o, err := NewMatchOption(c.MatchSetOptions) + if err != nil { + return nil, err + } + return &AsPathCondition{ + set: &AsPathSet{ + name: c.AsPathSet, + }, + option: o, + }, nil +} + +type CommunityCondition struct { + set *CommunitySet + option MatchOption +} + +func (c *CommunityCondition) Type() ConditionType { + return CONDITION_COMMUNITY +} + +func (c *CommunityCondition) Set() DefinedSet { + return c.set +} + +func (c *CommunityCondition) Option() MatchOption { + return c.option +} + +func (c *CommunityCondition) Evaluate(path *Path, _ *PolicyOptions) bool { + cs := path.GetCommunities() + result := false + for _, x := range c.set.list { + result = false + for _, y := range cs { + if x.MatchString(fmt.Sprintf("%d:%d", y>>16, y&0x0000ffff)) { + result = true + break + } + } + if c.option == MATCH_OPTION_ALL && !result { + break + } + if (c.option == MATCH_OPTION_ANY || c.option == MATCH_OPTION_INVERT) && result { + break + } + } + if c.option == MATCH_OPTION_INVERT { + result = !result + } + return result +} + +func (c *CommunityCondition) Name() string { return c.set.name } + +func NewCommunityCondition(c config.MatchCommunitySet) (*CommunityCondition, error) { + if c.CommunitySet == "" { + return nil, nil + } + o, err := NewMatchOption(c.MatchSetOptions) + if err != nil { + return nil, err + } + return &CommunityCondition{ + set: &CommunitySet{ + regExpSet: regExpSet{ + name: c.CommunitySet, + }, + }, + option: o, + }, nil +} + +type ExtCommunityCondition struct { + set *ExtCommunitySet + option MatchOption +} + +func (c *ExtCommunityCondition) Type() ConditionType { + return CONDITION_EXT_COMMUNITY +} + +func (c *ExtCommunityCondition) Set() DefinedSet { + return c.set +} + +func (c *ExtCommunityCondition) Option() MatchOption { + return c.option +} + +func (c *ExtCommunityCondition) Evaluate(path *Path, _ *PolicyOptions) bool { + es := path.GetExtCommunities() + result := false + for _, x := range es { + result = false + typ, subtype := x.GetTypes() + // match only with transitive community. see RFC7153 + if typ >= 0x3f { + continue + } + for idx, y := range c.set.list { + if subtype == c.set.subtypeList[idx] && y.MatchString(x.String()) { + result = true + break + } + } + if c.option == MATCH_OPTION_ALL && !result { + break + } + if c.option == MATCH_OPTION_ANY && result { + break + } + } + if c.option == MATCH_OPTION_INVERT { + result = !result + } + return result +} + +func (c *ExtCommunityCondition) Name() string { return c.set.name } + +func NewExtCommunityCondition(c config.MatchExtCommunitySet) (*ExtCommunityCondition, error) { + if c.ExtCommunitySet == "" { + return nil, nil + } + o, err := NewMatchOption(c.MatchSetOptions) + if err != nil { + return nil, err + } + return &ExtCommunityCondition{ + set: &ExtCommunitySet{ + regExpSet: regExpSet{ + name: c.ExtCommunitySet, + }, + }, + option: o, + }, nil +} + +type LargeCommunityCondition struct { + set *LargeCommunitySet + option MatchOption +} + +func (c *LargeCommunityCondition) Type() ConditionType { + return CONDITION_LARGE_COMMUNITY +} + +func (c *LargeCommunityCondition) Set() DefinedSet { + return c.set +} + +func (c *LargeCommunityCondition) Option() MatchOption { + return c.option +} + +func (c *LargeCommunityCondition) Evaluate(path *Path, _ *PolicyOptions) bool { + result := false + cs := path.GetLargeCommunities() + for _, x := range c.set.list { + result = false + for _, y := range cs { + if x.MatchString(y.String()) { + result = true + break + } + } + if c.option == MATCH_OPTION_ALL && !result { + break + } + if (c.option == MATCH_OPTION_ANY || c.option == MATCH_OPTION_INVERT) && result { + break + } + } + if c.option == MATCH_OPTION_INVERT { + result = !result + } + return result +} + +func (c *LargeCommunityCondition) Name() string { return c.set.name } + +func NewLargeCommunityCondition(c config.MatchLargeCommunitySet) (*LargeCommunityCondition, error) { + if c.LargeCommunitySet == "" { + return nil, nil + } + o, err := NewMatchOption(c.MatchSetOptions) + if err != nil { + return nil, err + } + return &LargeCommunityCondition{ + set: &LargeCommunitySet{ + regExpSet: regExpSet{ + name: c.LargeCommunitySet, + }, + }, + option: o, + }, nil +} + +type AsPathLengthCondition struct { + length uint32 + operator AttributeComparison +} + +func (c *AsPathLengthCondition) Type() ConditionType { + return CONDITION_AS_PATH_LENGTH +} + +// compare AS_PATH length in the message's AS_PATH attribute with +// the one in condition. +func (c *AsPathLengthCondition) Evaluate(path *Path, _ *PolicyOptions) bool { + + length := uint32(path.GetAsPathLen()) + result := false + switch c.operator { + case ATTRIBUTE_EQ: + result = c.length == length + case ATTRIBUTE_GE: + result = c.length <= length + case ATTRIBUTE_LE: + result = c.length >= length + } + + return result +} + +func (c *AsPathLengthCondition) Set() DefinedSet { + return nil +} + +func (c *AsPathLengthCondition) Name() string { return "" } + +func (c *AsPathLengthCondition) String() string { + return fmt.Sprintf("%s%d", c.operator, c.length) +} + +func NewAsPathLengthCondition(c config.AsPathLength) (*AsPathLengthCondition, error) { + if c.Value == 0 && c.Operator == "" { + return nil, nil + } + var op AttributeComparison + if i := c.Operator.ToInt(); i < 0 { + return nil, fmt.Errorf("invalid as path length operator: %s", c.Operator) + } else { + // take mod 3 because we have extended openconfig attribute-comparison + // for simple configuration. see config.AttributeComparison definition + op = AttributeComparison(i % 3) + } + return &AsPathLengthCondition{ + length: c.Value, + operator: op, + }, nil +} + +type RpkiValidationCondition struct { + result config.RpkiValidationResultType +} + +func (c *RpkiValidationCondition) Type() ConditionType { + return CONDITION_RPKI +} + +func (c *RpkiValidationCondition) Evaluate(path *Path, options *PolicyOptions) bool { + if options != nil && options.ValidationResult != nil { + return c.result == options.ValidationResult.Status + } + return false +} + +func (c *RpkiValidationCondition) Set() DefinedSet { + return nil +} + +func (c *RpkiValidationCondition) Name() string { return "" } + +func (c *RpkiValidationCondition) String() string { + return string(c.result) +} + +func NewRpkiValidationCondition(c config.RpkiValidationResultType) (*RpkiValidationCondition, error) { + if c == config.RpkiValidationResultType("") || c == config.RPKI_VALIDATION_RESULT_TYPE_NONE { + return nil, nil + } + return &RpkiValidationCondition{ + result: c, + }, nil +} + +type RouteTypeCondition struct { + typ config.RouteType +} + +func (c *RouteTypeCondition) Type() ConditionType { + return CONDITION_ROUTE_TYPE +} + +func (c *RouteTypeCondition) Evaluate(path *Path, _ *PolicyOptions) bool { + switch c.typ { + case config.ROUTE_TYPE_LOCAL: + return path.IsLocal() + case config.ROUTE_TYPE_INTERNAL: + return !path.IsLocal() && path.IsIBGP() + case config.ROUTE_TYPE_EXTERNAL: + return !path.IsLocal() && !path.IsIBGP() + } + return false +} + +func (c *RouteTypeCondition) Set() DefinedSet { + return nil +} + +func (c *RouteTypeCondition) Name() string { return "" } + +func (c *RouteTypeCondition) String() string { + return string(c.typ) +} + +func NewRouteTypeCondition(c config.RouteType) (*RouteTypeCondition, error) { + if string(c) == "" || c == config.ROUTE_TYPE_NONE { + return nil, nil + } + if err := c.Validate(); err != nil { + return nil, err + } + return &RouteTypeCondition{ + typ: c, + }, nil +} + +type AfiSafiInCondition struct { + routeFamilies []bgp.RouteFamily +} + +func (c *AfiSafiInCondition) Type() ConditionType { + return CONDITION_AFI_SAFI_IN +} + +func (c *AfiSafiInCondition) Evaluate(path *Path, _ *PolicyOptions) bool { + for _, rf := range c.routeFamilies { + if path.GetRouteFamily() == rf { + return true + } + } + return false +} + +func (c *AfiSafiInCondition) Set() DefinedSet { + return nil +} + +func (c *AfiSafiInCondition) Name() string { return "" } + +func (c *AfiSafiInCondition) String() string { + tmp := make([]string, 0, len(c.routeFamilies)) + for _, afiSafi := range c.routeFamilies { + tmp = append(tmp, afiSafi.String()) + } + return strings.Join(tmp, " ") +} + +func NewAfiSafiInCondition(afiSafInConfig []config.AfiSafiType) (*AfiSafiInCondition, error) { + if afiSafInConfig == nil { + return nil, nil + } + + routeFamilies := make([]bgp.RouteFamily, 0, len(afiSafInConfig)) + for _, afiSafiValue := range afiSafInConfig { + if err := afiSafiValue.Validate(); err != nil { + return nil, err + } + rf, err := bgp.GetRouteFamily(string(afiSafiValue)) + if err != nil { + return nil, err + } + routeFamilies = append(routeFamilies, rf) + } + return &AfiSafiInCondition{ + routeFamilies: routeFamilies, + }, nil +} + +type Action interface { + Type() ActionType + Apply(*Path, *PolicyOptions) *Path + String() string +} + +type RoutingAction struct { + AcceptRoute bool +} + +func (a *RoutingAction) Type() ActionType { + return ACTION_ROUTING +} + +func (a *RoutingAction) Apply(path *Path, _ *PolicyOptions) *Path { + if a.AcceptRoute { + return path + } + return nil +} + +func (a *RoutingAction) String() string { + action := "reject" + if a.AcceptRoute { + action = "accept" + } + return action +} + +func NewRoutingAction(c config.RouteDisposition) (*RoutingAction, error) { + var accept bool + switch c { + case config.RouteDisposition(""), config.ROUTE_DISPOSITION_NONE: + return nil, nil + case config.ROUTE_DISPOSITION_ACCEPT_ROUTE: + accept = true + case config.ROUTE_DISPOSITION_REJECT_ROUTE: + accept = false + default: + return nil, fmt.Errorf("invalid route disposition") + } + return &RoutingAction{ + AcceptRoute: accept, + }, nil +} + +type CommunityAction struct { + action config.BgpSetCommunityOptionType + list []uint32 + removeList []*regexp.Regexp +} + +func RegexpRemoveCommunities(path *Path, exps []*regexp.Regexp) { + comms := path.GetCommunities() + newComms := make([]uint32, 0, len(comms)) + for _, comm := range comms { + c := fmt.Sprintf("%d:%d", comm>>16, comm&0x0000ffff) + match := false + for _, exp := range exps { + if exp.MatchString(c) { + match = true + break + } + } + if !match { + newComms = append(newComms, comm) + } + } + path.SetCommunities(newComms, true) +} + +func RegexpRemoveExtCommunities(path *Path, exps []*regexp.Regexp, subtypes []bgp.ExtendedCommunityAttrSubType) { + comms := path.GetExtCommunities() + newComms := make([]bgp.ExtendedCommunityInterface, 0, len(comms)) + for _, comm := range comms { + match := false + typ, subtype := comm.GetTypes() + // match only with transitive community. see RFC7153 + if typ >= 0x3f { + continue + } + for idx, exp := range exps { + if subtype == subtypes[idx] && exp.MatchString(comm.String()) { + match = true + break + } + } + if !match { + newComms = append(newComms, comm) + } + } + path.SetExtCommunities(newComms, true) +} + +func RegexpRemoveLargeCommunities(path *Path, exps []*regexp.Regexp) { + comms := path.GetLargeCommunities() + newComms := make([]*bgp.LargeCommunity, 0, len(comms)) + for _, comm := range comms { + c := comm.String() + match := false + for _, exp := range exps { + if exp.MatchString(c) { + match = true + break + } + } + if !match { + newComms = append(newComms, comm) + } + } + path.SetLargeCommunities(newComms, true) +} + +func (a *CommunityAction) Type() ActionType { + return ACTION_COMMUNITY +} + +func (a *CommunityAction) Apply(path *Path, _ *PolicyOptions) *Path { + switch a.action { + case config.BGP_SET_COMMUNITY_OPTION_TYPE_ADD: + path.SetCommunities(a.list, false) + case config.BGP_SET_COMMUNITY_OPTION_TYPE_REMOVE: + RegexpRemoveCommunities(path, a.removeList) + case config.BGP_SET_COMMUNITY_OPTION_TYPE_REPLACE: + path.SetCommunities(a.list, true) + } + return path +} + +func (a *CommunityAction) ToConfig() *config.SetCommunity { + cs := make([]string, 0, len(a.list)+len(a.removeList)) + for _, comm := range a.list { + c := fmt.Sprintf("%d:%d", comm>>16, comm&0x0000ffff) + cs = append(cs, c) + } + for _, exp := range a.removeList { + cs = append(cs, exp.String()) + } + return &config.SetCommunity{ + Options: string(a.action), + SetCommunityMethod: config.SetCommunityMethod{CommunitiesList: cs}, + } +} + +func (a *CommunityAction) MarshalJSON() ([]byte, error) { + return json.Marshal(a.ToConfig()) +} + +// TODO: this is not efficient use of regexp, probably slow +var _regexpCommunityReplaceString = regexp.MustCompile(`[\^\$]`) + +func (a *CommunityAction) String() string { + list := a.ToConfig().SetCommunityMethod.CommunitiesList + l := _regexpCommunityReplaceString.ReplaceAllString(strings.Join(list, ", "), "") + return fmt.Sprintf("%s[%s]", a.action, l) +} + +func NewCommunityAction(c config.SetCommunity) (*CommunityAction, error) { + a, ok := CommunityOptionValueMap[strings.ToLower(c.Options)] + if !ok { + if len(c.SetCommunityMethod.CommunitiesList) == 0 { + return nil, nil + } + return nil, fmt.Errorf("invalid option name: %s", c.Options) + } + var list []uint32 + var removeList []*regexp.Regexp + if a == config.BGP_SET_COMMUNITY_OPTION_TYPE_REMOVE { + removeList = make([]*regexp.Regexp, 0, len(c.SetCommunityMethod.CommunitiesList)) + } else { + list = make([]uint32, 0, len(c.SetCommunityMethod.CommunitiesList)) + } + for _, x := range c.SetCommunityMethod.CommunitiesList { + if a == config.BGP_SET_COMMUNITY_OPTION_TYPE_REMOVE { + exp, err := ParseCommunityRegexp(x) + if err != nil { + return nil, err + } + removeList = append(removeList, exp) + } else { + comm, err := ParseCommunity(x) + if err != nil { + return nil, err + } + list = append(list, comm) + } + } + return &CommunityAction{ + action: a, + list: list, + removeList: removeList, + }, nil +} + +type ExtCommunityAction struct { + action config.BgpSetCommunityOptionType + list []bgp.ExtendedCommunityInterface + removeList []*regexp.Regexp + subtypeList []bgp.ExtendedCommunityAttrSubType +} + +func (a *ExtCommunityAction) Type() ActionType { + return ACTION_EXT_COMMUNITY +} + +func (a *ExtCommunityAction) Apply(path *Path, _ *PolicyOptions) *Path { + switch a.action { + case config.BGP_SET_COMMUNITY_OPTION_TYPE_ADD: + path.SetExtCommunities(a.list, false) + case config.BGP_SET_COMMUNITY_OPTION_TYPE_REMOVE: + RegexpRemoveExtCommunities(path, a.removeList, a.subtypeList) + case config.BGP_SET_COMMUNITY_OPTION_TYPE_REPLACE: + path.SetExtCommunities(a.list, true) + } + return path +} + +func (a *ExtCommunityAction) ToConfig() *config.SetExtCommunity { + cs := make([]string, 0, len(a.list)+len(a.removeList)) + f := func(idx int, arg string) string { + switch a.subtypeList[idx] { + case bgp.EC_SUBTYPE_ROUTE_TARGET: + return fmt.Sprintf("rt:%s", arg) + case bgp.EC_SUBTYPE_ROUTE_ORIGIN: + return fmt.Sprintf("soo:%s", arg) + case bgp.EC_SUBTYPE_ORIGIN_VALIDATION: + return arg + default: + return fmt.Sprintf("%d:%s", a.subtypeList[idx], arg) + } + } + for idx, c := range a.list { + cs = append(cs, f(idx, c.String())) + } + for idx, exp := range a.removeList { + cs = append(cs, f(idx, exp.String())) + } + return &config.SetExtCommunity{ + Options: string(a.action), + SetExtCommunityMethod: config.SetExtCommunityMethod{ + CommunitiesList: cs, + }, + } +} + +func (a *ExtCommunityAction) String() string { + list := a.ToConfig().SetExtCommunityMethod.CommunitiesList + l := _regexpCommunityReplaceString.ReplaceAllString(strings.Join(list, ", "), "") + return fmt.Sprintf("%s[%s]", a.action, l) +} + +func (a *ExtCommunityAction) MarshalJSON() ([]byte, error) { + return json.Marshal(a.ToConfig()) +} + +func NewExtCommunityAction(c config.SetExtCommunity) (*ExtCommunityAction, error) { + a, ok := CommunityOptionValueMap[strings.ToLower(c.Options)] + if !ok { + if len(c.SetExtCommunityMethod.CommunitiesList) == 0 { + return nil, nil + } + return nil, fmt.Errorf("invalid option name: %s", c.Options) + } + var list []bgp.ExtendedCommunityInterface + var removeList []*regexp.Regexp + subtypeList := make([]bgp.ExtendedCommunityAttrSubType, 0, len(c.SetExtCommunityMethod.CommunitiesList)) + if a == config.BGP_SET_COMMUNITY_OPTION_TYPE_REMOVE { + removeList = make([]*regexp.Regexp, 0, len(c.SetExtCommunityMethod.CommunitiesList)) + } else { + list = make([]bgp.ExtendedCommunityInterface, 0, len(c.SetExtCommunityMethod.CommunitiesList)) + } + for _, x := range c.SetExtCommunityMethod.CommunitiesList { + if a == config.BGP_SET_COMMUNITY_OPTION_TYPE_REMOVE { + subtype, exp, err := ParseExtCommunityRegexp(x) + if err != nil { + return nil, err + } + removeList = append(removeList, exp) + subtypeList = append(subtypeList, subtype) + } else { + comm, err := ParseExtCommunity(x) + if err != nil { + return nil, err + } + list = append(list, comm) + _, subtype := comm.GetTypes() + subtypeList = append(subtypeList, subtype) + } + } + return &ExtCommunityAction{ + action: a, + list: list, + removeList: removeList, + subtypeList: subtypeList, + }, nil +} + +type LargeCommunityAction struct { + action config.BgpSetCommunityOptionType + list []*bgp.LargeCommunity + removeList []*regexp.Regexp +} + +func (a *LargeCommunityAction) Type() ActionType { + return ACTION_LARGE_COMMUNITY +} + +func (a *LargeCommunityAction) Apply(path *Path, _ *PolicyOptions) *Path { + switch a.action { + case config.BGP_SET_COMMUNITY_OPTION_TYPE_ADD: + path.SetLargeCommunities(a.list, false) + case config.BGP_SET_COMMUNITY_OPTION_TYPE_REMOVE: + RegexpRemoveLargeCommunities(path, a.removeList) + case config.BGP_SET_COMMUNITY_OPTION_TYPE_REPLACE: + path.SetLargeCommunities(a.list, true) + } + return path +} + +func (a *LargeCommunityAction) ToConfig() *config.SetLargeCommunity { + cs := make([]string, 0, len(a.list)+len(a.removeList)) + for _, comm := range a.list { + cs = append(cs, comm.String()) + } + for _, exp := range a.removeList { + cs = append(cs, exp.String()) + } + return &config.SetLargeCommunity{ + SetLargeCommunityMethod: config.SetLargeCommunityMethod{CommunitiesList: cs}, + Options: config.BgpSetCommunityOptionType(a.action), + } +} + +func (a *LargeCommunityAction) String() string { + list := a.ToConfig().SetLargeCommunityMethod.CommunitiesList + l := _regexpCommunityReplaceString.ReplaceAllString(strings.Join(list, ", "), "") + return fmt.Sprintf("%s[%s]", a.action, l) +} + +func (a *LargeCommunityAction) MarshalJSON() ([]byte, error) { + return json.Marshal(a.ToConfig()) +} + +func NewLargeCommunityAction(c config.SetLargeCommunity) (*LargeCommunityAction, error) { + a, ok := CommunityOptionValueMap[strings.ToLower(string(c.Options))] + if !ok { + if len(c.SetLargeCommunityMethod.CommunitiesList) == 0 { + return nil, nil + } + return nil, fmt.Errorf("invalid option name: %s", c.Options) + } + var list []*bgp.LargeCommunity + var removeList []*regexp.Regexp + if a == config.BGP_SET_COMMUNITY_OPTION_TYPE_REMOVE { + removeList = make([]*regexp.Regexp, 0, len(c.SetLargeCommunityMethod.CommunitiesList)) + } else { + list = make([]*bgp.LargeCommunity, 0, len(c.SetLargeCommunityMethod.CommunitiesList)) + } + for _, x := range c.SetLargeCommunityMethod.CommunitiesList { + if a == config.BGP_SET_COMMUNITY_OPTION_TYPE_REMOVE { + exp, err := ParseLargeCommunityRegexp(x) + if err != nil { + return nil, err + } + removeList = append(removeList, exp) + } else { + comm, err := bgp.ParseLargeCommunity(x) + if err != nil { + return nil, err + } + list = append(list, comm) + } + } + return &LargeCommunityAction{ + action: a, + list: list, + removeList: removeList, + }, nil + +} + +type MedAction struct { + value int64 + action MedActionType +} + +func (a *MedAction) Type() ActionType { + return ACTION_MED +} + +func (a *MedAction) Apply(path *Path, _ *PolicyOptions) *Path { + var err error + switch a.action { + case MED_ACTION_MOD: + err = path.SetMed(a.value, false) + case MED_ACTION_REPLACE: + err = path.SetMed(a.value, true) + } + + if err != nil { + log.WithFields(log.Fields{ + "Topic": "Policy", + "Type": "Med Action", + "Error": err, + }).Warn("Could not set Med on path") + } + return path +} + +func (a *MedAction) ToConfig() config.BgpSetMedType { + if a.action == MED_ACTION_MOD && a.value > 0 { + return config.BgpSetMedType(fmt.Sprintf("+%d", a.value)) + } + return config.BgpSetMedType(fmt.Sprintf("%d", a.value)) +} + +func (a *MedAction) String() string { + return string(a.ToConfig()) +} + +func (a *MedAction) MarshalJSON() ([]byte, error) { + return json.Marshal(a.ToConfig()) +} + +var _regexpParseMedAction = regexp.MustCompile(`^(\+|\-)?(\d+)$`) + +func NewMedAction(c config.BgpSetMedType) (*MedAction, error) { + if string(c) == "" { + return nil, nil + } + + elems := _regexpParseMedAction.FindStringSubmatch(string(c)) + if len(elems) != 3 { + return nil, fmt.Errorf("invalid med action format") + } + action := MED_ACTION_REPLACE + switch elems[1] { + case "+", "-": + action = MED_ACTION_MOD + } + value, _ := strconv.ParseInt(string(c), 10, 64) + return &MedAction{ + value: value, + action: action, + }, nil +} + +func NewMedActionFromApiStruct(action MedActionType, value int64) *MedAction { + return &MedAction{action: action, value: value} +} + +type LocalPrefAction struct { + value uint32 +} + +func (a *LocalPrefAction) Type() ActionType { + return ACTION_LOCAL_PREF +} + +func (a *LocalPrefAction) Apply(path *Path, _ *PolicyOptions) *Path { + path.setPathAttr(bgp.NewPathAttributeLocalPref(a.value)) + return path +} + +func (a *LocalPrefAction) ToConfig() uint32 { + return a.value +} + +func (a *LocalPrefAction) String() string { + return fmt.Sprintf("%d", a.value) +} + +func (a *LocalPrefAction) MarshalJSON() ([]byte, error) { + return json.Marshal(a.ToConfig()) +} + +func NewLocalPrefAction(value uint32) (*LocalPrefAction, error) { + if value == 0 { + return nil, nil + } + return &LocalPrefAction{ + value: value, + }, nil +} + +type AsPathPrependAction struct { + asn uint32 + useLeftMost bool + repeat uint8 +} + +func (a *AsPathPrependAction) Type() ActionType { + return ACTION_AS_PATH_PREPEND +} + +func (a *AsPathPrependAction) Apply(path *Path, option *PolicyOptions) *Path { + var asn uint32 + if a.useLeftMost { + aspath := path.GetAsSeqList() + if len(aspath) == 0 { + log.WithFields(log.Fields{ + "Topic": "Policy", + "Type": "AsPathPrepend Action", + }).Warn("aspath length is zero.") + return path + } + asn = aspath[0] + if asn == 0 { + log.WithFields(log.Fields{ + "Topic": "Policy", + "Type": "AsPathPrepend Action", + }).Warn("left-most ASN is not seq") + return path + } + } else { + asn = a.asn + } + + confed := option != nil && option.Info.Confederation + path.PrependAsn(asn, a.repeat, confed) + + return path +} + +func (a *AsPathPrependAction) ToConfig() *config.SetAsPathPrepend { + return &config.SetAsPathPrepend{ + RepeatN: uint8(a.repeat), + As: func() string { + if a.useLeftMost { + return "last-as" + } + return fmt.Sprintf("%d", a.asn) + }(), + } +} + +func (a *AsPathPrependAction) String() string { + c := a.ToConfig() + return fmt.Sprintf("prepend %s %d times", c.As, c.RepeatN) +} + +func (a *AsPathPrependAction) MarshalJSON() ([]byte, error) { + return json.Marshal(a.ToConfig()) +} + +// NewAsPathPrependAction creates AsPathPrependAction object. +// If ASN cannot be parsed, nil will be returned. +func NewAsPathPrependAction(action config.SetAsPathPrepend) (*AsPathPrependAction, error) { + a := &AsPathPrependAction{ + repeat: action.RepeatN, + } + switch action.As { + case "": + if a.repeat == 0 { + return nil, nil + } + return nil, fmt.Errorf("specify as to prepend") + case "last-as": + a.useLeftMost = true + default: + asn, err := strconv.ParseUint(action.As, 10, 32) + if err != nil { + return nil, fmt.Errorf("As number string invalid") + } + a.asn = uint32(asn) + } + return a, nil +} + +type NexthopAction struct { + value net.IP + self bool +} + +func (a *NexthopAction) Type() ActionType { + return ACTION_NEXTHOP +} + +func (a *NexthopAction) Apply(path *Path, options *PolicyOptions) *Path { + if a.self { + if options != nil && options.Info != nil && options.Info.LocalAddress != nil { + path.SetNexthop(options.Info.LocalAddress) + } + return path + } + path.SetNexthop(a.value) + return path +} + +func (a *NexthopAction) ToConfig() config.BgpNextHopType { + if a.self { + return config.BgpNextHopType("self") + } + return config.BgpNextHopType(a.value.String()) +} + +func (a *NexthopAction) String() string { + return string(a.ToConfig()) +} + +func (a *NexthopAction) MarshalJSON() ([]byte, error) { + return json.Marshal(a.ToConfig()) +} + +func NewNexthopAction(c config.BgpNextHopType) (*NexthopAction, error) { + switch strings.ToLower(string(c)) { + case "": + return nil, nil + case "self": + return &NexthopAction{ + self: true, + }, nil + } + addr := net.ParseIP(string(c)) + if addr == nil { + return nil, fmt.Errorf("invalid ip address format: %s", string(c)) + } + return &NexthopAction{ + value: addr, + }, nil +} + +type Statement struct { + Name string + Conditions []Condition + RouteAction Action + ModActions []Action +} + +// evaluate each condition in the statement according to MatchSetOptions +func (s *Statement) Evaluate(p *Path, options *PolicyOptions) bool { + for _, c := range s.Conditions { + if !c.Evaluate(p, options) { + return false + } + } + return true +} + +func (s *Statement) Apply(path *Path, options *PolicyOptions) (RouteType, *Path) { + result := s.Evaluate(path, options) + if result { + if len(s.ModActions) != 0 { + // apply all modification actions + path = path.Clone(path.IsWithdraw) + for _, action := range s.ModActions { + path = action.Apply(path, options) + } + } + //Routing action + if s.RouteAction == nil || reflect.ValueOf(s.RouteAction).IsNil() { + return ROUTE_TYPE_NONE, path + } + p := s.RouteAction.Apply(path, options) + if p == nil { + return ROUTE_TYPE_REJECT, path + } + return ROUTE_TYPE_ACCEPT, path + } + return ROUTE_TYPE_NONE, path +} + +func (s *Statement) ToConfig() *config.Statement { + return &config.Statement{ + Name: s.Name, + Conditions: func() config.Conditions { + cond := config.Conditions{} + for _, c := range s.Conditions { + switch c.(type) { + case *PrefixCondition: + v := c.(*PrefixCondition) + cond.MatchPrefixSet = config.MatchPrefixSet{PrefixSet: v.set.Name(), MatchSetOptions: v.option.ConvertToMatchSetOptionsRestrictedType()} + case *NeighborCondition: + v := c.(*NeighborCondition) + cond.MatchNeighborSet = config.MatchNeighborSet{NeighborSet: v.set.Name(), MatchSetOptions: v.option.ConvertToMatchSetOptionsRestrictedType()} + case *AsPathLengthCondition: + v := c.(*AsPathLengthCondition) + cond.BgpConditions.AsPathLength = config.AsPathLength{Operator: config.IntToAttributeComparisonMap[int(v.operator)], Value: v.length} + case *AsPathCondition: + v := c.(*AsPathCondition) + cond.BgpConditions.MatchAsPathSet = config.MatchAsPathSet{AsPathSet: v.set.Name(), MatchSetOptions: config.IntToMatchSetOptionsTypeMap[int(v.option)]} + case *CommunityCondition: + v := c.(*CommunityCondition) + cond.BgpConditions.MatchCommunitySet = config.MatchCommunitySet{CommunitySet: v.set.Name(), MatchSetOptions: config.IntToMatchSetOptionsTypeMap[int(v.option)]} + case *ExtCommunityCondition: + v := c.(*ExtCommunityCondition) + cond.BgpConditions.MatchExtCommunitySet = config.MatchExtCommunitySet{ExtCommunitySet: v.set.Name(), MatchSetOptions: config.IntToMatchSetOptionsTypeMap[int(v.option)]} + case *LargeCommunityCondition: + v := c.(*LargeCommunityCondition) + cond.BgpConditions.MatchLargeCommunitySet = config.MatchLargeCommunitySet{LargeCommunitySet: v.set.Name(), MatchSetOptions: config.IntToMatchSetOptionsTypeMap[int(v.option)]} + case *NextHopCondition: + v := c.(*NextHopCondition) + cond.BgpConditions.NextHopInList = v.set.List() + case *RpkiValidationCondition: + v := c.(*RpkiValidationCondition) + cond.BgpConditions.RpkiValidationResult = v.result + case *RouteTypeCondition: + v := c.(*RouteTypeCondition) + cond.BgpConditions.RouteType = v.typ + case *AfiSafiInCondition: + v := c.(*AfiSafiInCondition) + res := make([]config.AfiSafiType, 0, len(v.routeFamilies)) + for _, rf := range v.routeFamilies { + res = append(res, config.AfiSafiType(rf.String())) + } + cond.BgpConditions.AfiSafiInList = res + } + } + return cond + }(), + Actions: func() config.Actions { + act := config.Actions{} + if s.RouteAction != nil && !reflect.ValueOf(s.RouteAction).IsNil() { + a := s.RouteAction.(*RoutingAction) + if a.AcceptRoute { + act.RouteDisposition = config.ROUTE_DISPOSITION_ACCEPT_ROUTE + } else { + act.RouteDisposition = config.ROUTE_DISPOSITION_REJECT_ROUTE + } + } else { + act.RouteDisposition = config.ROUTE_DISPOSITION_NONE + } + for _, a := range s.ModActions { + switch a.(type) { + case *AsPathPrependAction: + act.BgpActions.SetAsPathPrepend = *a.(*AsPathPrependAction).ToConfig() + case *CommunityAction: + act.BgpActions.SetCommunity = *a.(*CommunityAction).ToConfig() + case *ExtCommunityAction: + act.BgpActions.SetExtCommunity = *a.(*ExtCommunityAction).ToConfig() + case *LargeCommunityAction: + act.BgpActions.SetLargeCommunity = *a.(*LargeCommunityAction).ToConfig() + case *MedAction: + act.BgpActions.SetMed = a.(*MedAction).ToConfig() + case *LocalPrefAction: + act.BgpActions.SetLocalPref = a.(*LocalPrefAction).ToConfig() + case *NexthopAction: + act.BgpActions.SetNextHop = a.(*NexthopAction).ToConfig() + } + } + return act + }(), + } +} + +func (s *Statement) MarshalJSON() ([]byte, error) { + return json.Marshal(s.ToConfig()) +} + +type opType int + +const ( + ADD opType = iota + REMOVE + REPLACE +) + +func (lhs *Statement) mod(op opType, rhs *Statement) error { + cs := make([]Condition, len(lhs.Conditions)) + copy(cs, lhs.Conditions) + ra := lhs.RouteAction + as := make([]Action, len(lhs.ModActions)) + copy(as, lhs.ModActions) + for _, x := range rhs.Conditions { + var c Condition + i := 0 + for idx, y := range lhs.Conditions { + if x.Type() == y.Type() { + c = y + i = idx + break + } + } + switch op { + case ADD: + if c != nil { + return fmt.Errorf("condition %d is already set", x.Type()) + } + if cs == nil { + cs = make([]Condition, 0, len(rhs.Conditions)) + } + cs = append(cs, x) + case REMOVE: + if c == nil { + return fmt.Errorf("condition %d is not set", x.Type()) + } + cs = append(cs[:i], cs[i+1:]...) + if len(cs) == 0 { + cs = nil + } + case REPLACE: + if c == nil { + return fmt.Errorf("condition %d is not set", x.Type()) + } + cs[i] = x + } + } + if rhs.RouteAction != nil && !reflect.ValueOf(rhs.RouteAction).IsNil() { + switch op { + case ADD: + if lhs.RouteAction != nil && !reflect.ValueOf(lhs.RouteAction).IsNil() { + return fmt.Errorf("route action is already set") + } + ra = rhs.RouteAction + case REMOVE: + if lhs.RouteAction == nil || reflect.ValueOf(lhs.RouteAction).IsNil() { + return fmt.Errorf("route action is not set") + } + ra = nil + case REPLACE: + if lhs.RouteAction == nil || reflect.ValueOf(lhs.RouteAction).IsNil() { + return fmt.Errorf("route action is not set") + } + ra = rhs.RouteAction + } + } + for _, x := range rhs.ModActions { + var a Action + i := 0 + for idx, y := range lhs.ModActions { + if x.Type() == y.Type() { + a = y + i = idx + break + } + } + switch op { + case ADD: + if a != nil { + return fmt.Errorf("action %d is already set", x.Type()) + } + if as == nil { + as = make([]Action, 0, len(rhs.ModActions)) + } + as = append(as, x) + case REMOVE: + if a == nil { + return fmt.Errorf("action %d is not set", x.Type()) + } + as = append(as[:i], as[i+1:]...) + if len(as) == 0 { + as = nil + } + case REPLACE: + if a == nil { + return fmt.Errorf("action %d is not set", x.Type()) + } + as[i] = x + } + } + lhs.Conditions = cs + lhs.RouteAction = ra + lhs.ModActions = as + return nil +} + +func (lhs *Statement) Add(rhs *Statement) error { + return lhs.mod(ADD, rhs) +} + +func (lhs *Statement) Remove(rhs *Statement) error { + return lhs.mod(REMOVE, rhs) +} + +func (lhs *Statement) Replace(rhs *Statement) error { + return lhs.mod(REPLACE, rhs) +} + +func NewStatement(c config.Statement) (*Statement, error) { + if c.Name == "" { + return nil, fmt.Errorf("empty statement name") + } + var ra Action + var as []Action + var cs []Condition + var err error + cfs := []func() (Condition, error){ + func() (Condition, error) { + return NewPrefixCondition(c.Conditions.MatchPrefixSet) + }, + func() (Condition, error) { + return NewNeighborCondition(c.Conditions.MatchNeighborSet) + }, + func() (Condition, error) { + return NewAsPathLengthCondition(c.Conditions.BgpConditions.AsPathLength) + }, + func() (Condition, error) { + return NewRpkiValidationCondition(c.Conditions.BgpConditions.RpkiValidationResult) + }, + func() (Condition, error) { + return NewRouteTypeCondition(c.Conditions.BgpConditions.RouteType) + }, + func() (Condition, error) { + return NewAsPathCondition(c.Conditions.BgpConditions.MatchAsPathSet) + }, + func() (Condition, error) { + return NewCommunityCondition(c.Conditions.BgpConditions.MatchCommunitySet) + }, + func() (Condition, error) { + return NewExtCommunityCondition(c.Conditions.BgpConditions.MatchExtCommunitySet) + }, + func() (Condition, error) { + return NewLargeCommunityCondition(c.Conditions.BgpConditions.MatchLargeCommunitySet) + }, + func() (Condition, error) { + return NewNextHopCondition(c.Conditions.BgpConditions.NextHopInList) + }, + func() (Condition, error) { + return NewAfiSafiInCondition(c.Conditions.BgpConditions.AfiSafiInList) + }, + } + cs = make([]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) + } + } + ra, err = NewRoutingAction(c.Actions.RouteDisposition) + if err != nil { + return nil, err + } + afs := []func() (Action, error){ + func() (Action, error) { + return NewCommunityAction(c.Actions.BgpActions.SetCommunity) + }, + func() (Action, error) { + return NewExtCommunityAction(c.Actions.BgpActions.SetExtCommunity) + }, + func() (Action, error) { + return NewLargeCommunityAction(c.Actions.BgpActions.SetLargeCommunity) + }, + func() (Action, error) { + return NewMedAction(c.Actions.BgpActions.SetMed) + }, + func() (Action, error) { + return NewLocalPrefAction(c.Actions.BgpActions.SetLocalPref) + }, + func() (Action, error) { + return NewAsPathPrependAction(c.Actions.BgpActions.SetAsPathPrepend) + }, + func() (Action, error) { + return NewNexthopAction(c.Actions.BgpActions.SetNextHop) + }, + } + as = make([]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 &Statement{ + Name: c.Name, + Conditions: cs, + RouteAction: ra, + ModActions: as, + }, nil +} + +type Policy struct { + Name string + Statements []*Statement +} + +// Compare path with a policy's condition in stored order in the policy. +// If a condition match, then this function stops evaluation and +// subsequent conditions are skipped. +func (p *Policy) Apply(path *Path, options *PolicyOptions) (RouteType, *Path) { + for _, stmt := range p.Statements { + var result RouteType + result, path = stmt.Apply(path, options) + if result != ROUTE_TYPE_NONE { + return result, path + } + } + return ROUTE_TYPE_NONE, path +} + +func (p *Policy) ToConfig() *config.PolicyDefinition { + ss := make([]config.Statement, 0, len(p.Statements)) + for _, s := range p.Statements { + ss = append(ss, *s.ToConfig()) + } + return &config.PolicyDefinition{ + Name: p.Name, + Statements: ss, + } +} + +func (p *Policy) FillUp(m map[string]*Statement) error { + stmts := make([]*Statement, 0, len(p.Statements)) + for _, x := range p.Statements { + y, ok := m[x.Name] + if !ok { + return fmt.Errorf("not found statement %s", x.Name) + } + stmts = append(stmts, y) + } + p.Statements = stmts + return nil +} + +func (lhs *Policy) Add(rhs *Policy) error { + lhs.Statements = append(lhs.Statements, rhs.Statements...) + return nil +} + +func (lhs *Policy) Remove(rhs *Policy) error { + stmts := make([]*Statement, 0, len(lhs.Statements)) + for _, x := range lhs.Statements { + found := false + for _, y := range rhs.Statements { + if x.Name == y.Name { + found = true + break + } + } + if !found { + stmts = append(stmts, x) + } + } + lhs.Statements = stmts + return nil +} + +func (lhs *Policy) Replace(rhs *Policy) error { + lhs.Statements = rhs.Statements + return nil +} + +func (p *Policy) MarshalJSON() ([]byte, error) { + return json.Marshal(p.ToConfig()) +} + +func NewPolicy(c config.PolicyDefinition) (*Policy, error) { + if c.Name == "" { + return nil, fmt.Errorf("empty policy name") + } + var st []*Statement + stmts := c.Statements + if len(stmts) != 0 { + st = make([]*Statement, 0, len(stmts)) + for idx, stmt := range stmts { + if stmt.Name == "" { + stmt.Name = fmt.Sprintf("%s_stmt%d", c.Name, idx) + } + s, err := NewStatement(stmt) + if err != nil { + return nil, err + } + st = append(st, s) + } + } + return &Policy{ + Name: c.Name, + Statements: st, + }, nil +} + +type Policies []*Policy + +func (p Policies) Len() int { + return len(p) +} + +func (p Policies) Swap(i, j int) { + p[i], p[j] = p[j], p[i] +} + +func (p Policies) Less(i, j int) bool { + return p[i].Name < p[j].Name +} + +type Assignment struct { + inPolicies []*Policy + defaultInPolicy RouteType + importPolicies []*Policy + defaultImportPolicy RouteType + exportPolicies []*Policy + defaultExportPolicy RouteType +} + +type RoutingPolicy struct { + definedSetMap DefinedSetMap + policyMap map[string]*Policy + statementMap map[string]*Statement + assignmentMap map[string]*Assignment + mu sync.RWMutex +} + +func (r *RoutingPolicy) ApplyPolicy(id string, dir PolicyDirection, before *Path, options *PolicyOptions) *Path { + r.mu.RLock() + defer r.mu.RUnlock() + + if before == nil { + return nil + } + + if before.IsWithdraw { + return before + } + result := ROUTE_TYPE_NONE + after := before + for _, p := range r.getPolicy(id, dir) { + result, after = p.Apply(after, options) + if result != ROUTE_TYPE_NONE { + break + } + } + if result == ROUTE_TYPE_NONE { + result = r.getDefaultPolicy(id, dir) + } + switch result { + case ROUTE_TYPE_ACCEPT: + return after + default: + return nil + } +} + +func (r *RoutingPolicy) getPolicy(id string, dir PolicyDirection) []*Policy { + a, ok := r.assignmentMap[id] + if !ok { + return nil + } + switch dir { + case POLICY_DIRECTION_IN: + return a.inPolicies + case POLICY_DIRECTION_IMPORT: + return a.importPolicies + case POLICY_DIRECTION_EXPORT: + return a.exportPolicies + default: + return nil + } +} + +func (r *RoutingPolicy) getDefaultPolicy(id string, dir PolicyDirection) RouteType { + a, ok := r.assignmentMap[id] + if !ok { + return ROUTE_TYPE_NONE + } + switch dir { + case POLICY_DIRECTION_IN: + return a.defaultInPolicy + case POLICY_DIRECTION_IMPORT: + return a.defaultImportPolicy + case POLICY_DIRECTION_EXPORT: + return a.defaultExportPolicy + default: + return ROUTE_TYPE_NONE + } + +} + +func (r *RoutingPolicy) setPolicy(id string, dir PolicyDirection, policies []*Policy) error { + a, ok := r.assignmentMap[id] + if !ok { + a = &Assignment{} + } + switch dir { + case POLICY_DIRECTION_IN: + a.inPolicies = policies + case POLICY_DIRECTION_IMPORT: + a.importPolicies = policies + case POLICY_DIRECTION_EXPORT: + a.exportPolicies = policies + } + r.assignmentMap[id] = a + return nil +} + +func (r *RoutingPolicy) setDefaultPolicy(id string, dir PolicyDirection, typ RouteType) error { + a, ok := r.assignmentMap[id] + if !ok { + a = &Assignment{} + } + switch dir { + case POLICY_DIRECTION_IN: + a.defaultInPolicy = typ + case POLICY_DIRECTION_IMPORT: + a.defaultImportPolicy = typ + case POLICY_DIRECTION_EXPORT: + a.defaultExportPolicy = typ + } + r.assignmentMap[id] = a + return nil +} + +func (r *RoutingPolicy) getAssignmentFromConfig(dir PolicyDirection, a config.ApplyPolicy) ([]*Policy, RouteType, error) { + var names []string + var cdef config.DefaultPolicyType + def := ROUTE_TYPE_ACCEPT + c := a.Config + switch dir { + case POLICY_DIRECTION_IN: + names = c.InPolicyList + cdef = c.DefaultInPolicy + case POLICY_DIRECTION_IMPORT: + names = c.ImportPolicyList + cdef = c.DefaultImportPolicy + case POLICY_DIRECTION_EXPORT: + names = c.ExportPolicyList + cdef = c.DefaultExportPolicy + default: + return nil, def, fmt.Errorf("invalid policy direction") + } + if cdef == config.DEFAULT_POLICY_TYPE_REJECT_ROUTE { + def = ROUTE_TYPE_REJECT + } + ps := make([]*Policy, 0, len(names)) + seen := make(map[string]bool) + for _, name := range names { + p, ok := r.policyMap[name] + if !ok { + return nil, def, fmt.Errorf("not found policy %s", name) + } + if seen[name] { + return nil, def, fmt.Errorf("duplicated policy %s", name) + } + seen[name] = true + ps = append(ps, p) + } + return ps, def, nil +} + +func (r *RoutingPolicy) validateCondition(v Condition) (err error) { + switch v.Type() { + case CONDITION_PREFIX: + m := r.definedSetMap[DEFINED_TYPE_PREFIX] + if i, ok := m[v.Name()]; !ok { + return fmt.Errorf("not found prefix set %s", v.Name()) + } else { + c := v.(*PrefixCondition) + c.set = i.(*PrefixSet) + } + case CONDITION_NEIGHBOR: + m := r.definedSetMap[DEFINED_TYPE_NEIGHBOR] + if i, ok := m[v.Name()]; !ok { + return fmt.Errorf("not found neighbor set %s", v.Name()) + } else { + c := v.(*NeighborCondition) + c.set = i.(*NeighborSet) + } + case CONDITION_AS_PATH: + m := r.definedSetMap[DEFINED_TYPE_AS_PATH] + if i, ok := m[v.Name()]; !ok { + return fmt.Errorf("not found as path set %s", v.Name()) + } else { + c := v.(*AsPathCondition) + c.set = i.(*AsPathSet) + } + case CONDITION_COMMUNITY: + m := r.definedSetMap[DEFINED_TYPE_COMMUNITY] + if i, ok := m[v.Name()]; !ok { + return fmt.Errorf("not found community set %s", v.Name()) + } else { + c := v.(*CommunityCondition) + c.set = i.(*CommunitySet) + } + case CONDITION_EXT_COMMUNITY: + m := r.definedSetMap[DEFINED_TYPE_EXT_COMMUNITY] + if i, ok := m[v.Name()]; !ok { + return fmt.Errorf("not found ext-community set %s", v.Name()) + } else { + c := v.(*ExtCommunityCondition) + c.set = i.(*ExtCommunitySet) + } + case CONDITION_LARGE_COMMUNITY: + m := r.definedSetMap[DEFINED_TYPE_LARGE_COMMUNITY] + if i, ok := m[v.Name()]; !ok { + return fmt.Errorf("not found large-community set %s", v.Name()) + } else { + c := v.(*LargeCommunityCondition) + c.set = i.(*LargeCommunitySet) + } + case CONDITION_NEXT_HOP: + case CONDITION_AFI_SAFI_IN: + case CONDITION_AS_PATH_LENGTH: + case CONDITION_RPKI: + } + return nil +} + +func (r *RoutingPolicy) inUse(d DefinedSet) bool { + name := d.Name() + for _, p := range r.policyMap { + for _, s := range p.Statements { + for _, c := range s.Conditions { + if c.Set() != nil && c.Set().Name() == name { + return true + } + } + } + } + return false +} + +func (r *RoutingPolicy) statementInUse(x *Statement) bool { + for _, p := range r.policyMap { + for _, y := range p.Statements { + if x.Name == y.Name { + return true + } + } + } + return false +} + +func (r *RoutingPolicy) reload(c config.RoutingPolicy) error { + dmap := make(map[DefinedType]map[string]DefinedSet) + dmap[DEFINED_TYPE_PREFIX] = make(map[string]DefinedSet) + d := c.DefinedSets + for _, x := range d.PrefixSets { + y, err := NewPrefixSet(x) + if err != nil { + return err + } + if y == nil { + return fmt.Errorf("empty prefix set") + } + dmap[DEFINED_TYPE_PREFIX][y.Name()] = y + } + dmap[DEFINED_TYPE_NEIGHBOR] = make(map[string]DefinedSet) + for _, x := range d.NeighborSets { + y, err := NewNeighborSet(x) + if err != nil { + return err + } + if y == nil { + return fmt.Errorf("empty neighbor set") + } + dmap[DEFINED_TYPE_NEIGHBOR][y.Name()] = y + } + // dmap[DEFINED_TYPE_TAG] = make(map[string]DefinedSet) + // for _, x := range c.DefinedSets.TagSets{ + // y, err := NewTagSet(x) + // if err != nil { + // return nil, err + // } + // dmap[DEFINED_TYPE_TAG][y.Name()] = y + // } + bd := c.DefinedSets.BgpDefinedSets + dmap[DEFINED_TYPE_AS_PATH] = make(map[string]DefinedSet) + for _, x := range bd.AsPathSets { + y, err := NewAsPathSet(x) + if err != nil { + return err + } + if y == nil { + return fmt.Errorf("empty as path set") + } + dmap[DEFINED_TYPE_AS_PATH][y.Name()] = y + } + dmap[DEFINED_TYPE_COMMUNITY] = make(map[string]DefinedSet) + for _, x := range bd.CommunitySets { + y, err := NewCommunitySet(x) + if err != nil { + return err + } + if y == nil { + return fmt.Errorf("empty community set") + } + dmap[DEFINED_TYPE_COMMUNITY][y.Name()] = y + } + dmap[DEFINED_TYPE_EXT_COMMUNITY] = make(map[string]DefinedSet) + for _, x := range bd.ExtCommunitySets { + y, err := NewExtCommunitySet(x) + if err != nil { + return err + } + if y == nil { + return fmt.Errorf("empty ext-community set") + } + dmap[DEFINED_TYPE_EXT_COMMUNITY][y.Name()] = y + } + dmap[DEFINED_TYPE_LARGE_COMMUNITY] = make(map[string]DefinedSet) + for _, x := range bd.LargeCommunitySets { + y, err := NewLargeCommunitySet(x) + if err != nil { + return err + } + if y == nil { + return fmt.Errorf("empty large-community set") + } + dmap[DEFINED_TYPE_LARGE_COMMUNITY][y.Name()] = y + } + + pmap := make(map[string]*Policy) + smap := make(map[string]*Statement) + for _, x := range c.PolicyDefinitions { + y, err := NewPolicy(x) + if err != nil { + return err + } + if _, ok := pmap[y.Name]; ok { + return fmt.Errorf("duplicated policy name. policy name must be unique.") + } + pmap[y.Name] = y + for _, s := range y.Statements { + _, ok := smap[s.Name] + if ok { + return fmt.Errorf("duplicated statement name. statement name must be unique.") + } + smap[s.Name] = s + } + } + + // hacky + oldMap := r.definedSetMap + r.definedSetMap = dmap + for _, y := range pmap { + for _, s := range y.Statements { + for _, c := range s.Conditions { + if err := r.validateCondition(c); err != nil { + r.definedSetMap = oldMap + return err + } + } + } + } + + r.definedSetMap = dmap + r.policyMap = pmap + r.statementMap = smap + r.assignmentMap = make(map[string]*Assignment) + // allow all routes coming in and going out by default + r.setDefaultPolicy(GLOBAL_RIB_NAME, POLICY_DIRECTION_IMPORT, ROUTE_TYPE_ACCEPT) + r.setDefaultPolicy(GLOBAL_RIB_NAME, POLICY_DIRECTION_EXPORT, ROUTE_TYPE_ACCEPT) + return nil +} + +func (r *RoutingPolicy) GetDefinedSet(typ DefinedType, name string) (*config.DefinedSets, error) { + r.mu.RLock() + + set, ok := r.definedSetMap[typ] + if !ok { + return nil, fmt.Errorf("invalid defined-set type: %d", typ) + } + + var dl DefinedSetList + for _, s := range set { + dl = append(dl, s) + } + r.mu.RUnlock() + + sort.Sort(dl) + + sets := &config.DefinedSets{ + PrefixSets: make([]config.PrefixSet, 0), + NeighborSets: make([]config.NeighborSet, 0), + BgpDefinedSets: config.BgpDefinedSets{ + CommunitySets: make([]config.CommunitySet, 0), + ExtCommunitySets: make([]config.ExtCommunitySet, 0), + LargeCommunitySets: make([]config.LargeCommunitySet, 0), + AsPathSets: make([]config.AsPathSet, 0), + }, + } + for _, s := range dl { + if name != "" && s.Name() != name { + continue + } + switch s.(type) { + case *PrefixSet: + sets.PrefixSets = append(sets.PrefixSets, *s.(*PrefixSet).ToConfig()) + case *NeighborSet: + sets.NeighborSets = append(sets.NeighborSets, *s.(*NeighborSet).ToConfig()) + case *CommunitySet: + sets.BgpDefinedSets.CommunitySets = append(sets.BgpDefinedSets.CommunitySets, *s.(*CommunitySet).ToConfig()) + case *ExtCommunitySet: + sets.BgpDefinedSets.ExtCommunitySets = append(sets.BgpDefinedSets.ExtCommunitySets, *s.(*ExtCommunitySet).ToConfig()) + case *LargeCommunitySet: + sets.BgpDefinedSets.LargeCommunitySets = append(sets.BgpDefinedSets.LargeCommunitySets, *s.(*LargeCommunitySet).ToConfig()) + case *AsPathSet: + sets.BgpDefinedSets.AsPathSets = append(sets.BgpDefinedSets.AsPathSets, *s.(*AsPathSet).ToConfig()) + } + } + return sets, nil +} + +func (r *RoutingPolicy) AddDefinedSet(s DefinedSet) error { + r.mu.Lock() + defer r.mu.Unlock() + + if m, ok := r.definedSetMap[s.Type()]; !ok { + return fmt.Errorf("invalid defined-set type: %d", s.Type()) + } else { + if d, ok := m[s.Name()]; ok { + if err := d.Append(s); err != nil { + return err + } + } else { + m[s.Name()] = s + } + } + return nil +} + +func (r *RoutingPolicy) DeleteDefinedSet(a DefinedSet, all bool) (err error) { + r.mu.Lock() + defer r.mu.Unlock() + + if m, ok := r.definedSetMap[a.Type()]; !ok { + err = fmt.Errorf("invalid defined-set type: %d", a.Type()) + } else { + d, ok := m[a.Name()] + if !ok { + return fmt.Errorf("not found defined-set: %s", a.Name()) + } + if all { + if r.inUse(d) { + err = fmt.Errorf("can't delete. defined-set %s is in use", a.Name()) + } else { + delete(m, a.Name()) + } + } else { + err = d.Remove(a) + } + } + return err +} + +func (r *RoutingPolicy) ReplaceDefinedSet(a DefinedSet) (err error) { + r.mu.Lock() + defer r.mu.Unlock() + + if m, ok := r.definedSetMap[a.Type()]; !ok { + err = fmt.Errorf("invalid defined-set type: %d", a.Type()) + } else { + if d, ok := m[a.Name()]; !ok { + err = fmt.Errorf("not found defined-set: %s", a.Name()) + } else { + err = d.Replace(a) + } + } + return err +} + +func (r *RoutingPolicy) GetStatement() []*config.Statement { + r.mu.RLock() + defer r.mu.RUnlock() + + l := make([]*config.Statement, 0, len(r.statementMap)) + for _, st := range r.statementMap { + l = append(l, st.ToConfig()) + } + return l +} + +func (r *RoutingPolicy) AddStatement(st *Statement) (err error) { + r.mu.Lock() + defer r.mu.Unlock() + + for _, c := range st.Conditions { + if err = r.validateCondition(c); err != nil { + return + } + } + m := r.statementMap + name := st.Name + if d, ok := m[name]; ok { + err = d.Add(st) + } else { + m[name] = st + } + + return err +} + +func (r *RoutingPolicy) DeleteStatement(st *Statement, all bool) (err error) { + r.mu.Lock() + defer r.mu.Unlock() + + m := r.statementMap + name := st.Name + if d, ok := m[name]; ok { + if all { + if r.statementInUse(d) { + err = fmt.Errorf("can't delete. statement %s is in use", name) + } else { + delete(m, name) + } + } else { + err = d.Remove(st) + } + } else { + err = fmt.Errorf("not found statement: %s", name) + } + return err +} + +func (r *RoutingPolicy) ReplaceStatement(st *Statement) (err error) { + r.mu.Lock() + defer r.mu.Unlock() + + for _, c := range st.Conditions { + if err = r.validateCondition(c); err != nil { + return + } + } + m := r.statementMap + name := st.Name + if d, ok := m[name]; ok { + err = d.Replace(st) + } else { + err = fmt.Errorf("not found statement: %s", name) + } + return err +} + +func (r *RoutingPolicy) GetAllPolicy() []*config.PolicyDefinition { + r.mu.RLock() + + var ps Policies + for _, p := range r.policyMap { + ps = append(ps, p) + } + r.mu.RUnlock() + + sort.Sort(ps) + + l := make([]*config.PolicyDefinition, 0, len(ps)) + for _, p := range ps { + l = append(l, p.ToConfig()) + } + return l +} + +func (r *RoutingPolicy) AddPolicy(x *Policy, refer bool) (err error) { + r.mu.Lock() + defer r.mu.Unlock() + + for _, st := range x.Statements { + for _, c := range st.Conditions { + if err = r.validateCondition(c); err != nil { + return + } + } + } + + pMap := r.policyMap + sMap := r.statementMap + name := x.Name + y, ok := pMap[name] + if refer { + err = x.FillUp(sMap) + } else { + for _, st := range x.Statements { + if _, ok := sMap[st.Name]; ok { + err = fmt.Errorf("statement %s already defined", st.Name) + return + } + sMap[st.Name] = st + } + } + if ok { + err = y.Add(x) + } else { + pMap[name] = x + } + + return err +} + +func (r *RoutingPolicy) DeletePolicy(x *Policy, all, preserve bool, activeId []string) (err error) { + r.mu.Lock() + defer r.mu.Unlock() + + pMap := r.policyMap + sMap := r.statementMap + name := x.Name + y, ok := pMap[name] + if !ok { + err = fmt.Errorf("not found policy: %s", name) + return + } + inUse := func(ids []string) bool { + for _, id := range ids { + for _, dir := range []PolicyDirection{POLICY_DIRECTION_IN, POLICY_DIRECTION_EXPORT, POLICY_DIRECTION_EXPORT} { + for _, y := range r.getPolicy(id, dir) { + if x.Name == y.Name { + return true + } + } + } + } + return false + } + + if all { + if inUse(activeId) { + err = fmt.Errorf("can't delete. policy %s is in use", name) + return + } + log.WithFields(log.Fields{ + "Topic": "Policy", + "Key": name, + }).Debug("delete policy") + delete(pMap, name) + } else { + err = y.Remove(x) + } + if err == nil && !preserve { + for _, st := range y.Statements { + if !r.statementInUse(st) { + log.WithFields(log.Fields{ + "Topic": "Policy", + "Key": st.Name, + }).Debug("delete unused statement") + delete(sMap, st.Name) + } + } + } + return err +} + +func (r *RoutingPolicy) ReplacePolicy(x *Policy, refer, preserve bool) (err error) { + r.mu.Lock() + defer r.mu.Unlock() + + for _, st := range x.Statements { + for _, c := range st.Conditions { + if err = r.validateCondition(c); err != nil { + return + } + } + } + + pMap := r.policyMap + sMap := r.statementMap + name := x.Name + y, ok := pMap[name] + if !ok { + err = fmt.Errorf("not found policy: %s", name) + return + } + if refer { + if err = x.FillUp(sMap); err != nil { + return + } + } else { + for _, st := range x.Statements { + if _, ok := sMap[st.Name]; ok { + err = fmt.Errorf("statement %s already defined", st.Name) + return + } + sMap[st.Name] = st + } + } + + ys := y.Statements + err = y.Replace(x) + if err == nil && !preserve { + for _, st := range ys { + if !r.statementInUse(st) { + log.WithFields(log.Fields{ + "Topic": "Policy", + "Key": st.Name, + }).Debug("delete unused statement") + delete(sMap, st.Name) + } + } + } + return err +} + +func (r *RoutingPolicy) GetPolicyAssignment(id string, dir PolicyDirection) (RouteType, []*config.PolicyDefinition, error) { + r.mu.RLock() + defer r.mu.RUnlock() + + rt := r.getDefaultPolicy(id, dir) + + ps := r.getPolicy(id, dir) + l := make([]*config.PolicyDefinition, 0, len(ps)) + for _, p := range ps { + l = append(l, p.ToConfig()) + } + return rt, l, nil +} + +func (r *RoutingPolicy) AddPolicyAssignment(id string, dir PolicyDirection, policies []*config.PolicyDefinition, def RouteType) (err error) { + r.mu.Lock() + defer r.mu.Unlock() + + ps := make([]*Policy, 0, len(policies)) + seen := make(map[string]bool) + for _, x := range policies { + p, ok := r.policyMap[x.Name] + if !ok { + err = fmt.Errorf("not found policy %s", x.Name) + return + } + if seen[x.Name] { + err = fmt.Errorf("duplicated policy %s", x.Name) + return + } + seen[x.Name] = true + ps = append(ps, p) + } + cur := r.getPolicy(id, dir) + if cur == nil { + err = r.setPolicy(id, dir, ps) + } else { + seen = make(map[string]bool) + ps = append(cur, ps...) + for _, x := range ps { + if seen[x.Name] { + err = fmt.Errorf("duplicated policy %s", x.Name) + return + } + seen[x.Name] = true + } + err = r.setPolicy(id, dir, ps) + } + if err == nil && def != ROUTE_TYPE_NONE { + err = r.setDefaultPolicy(id, dir, def) + } + return err +} + +func (r *RoutingPolicy) DeletePolicyAssignment(id string, dir PolicyDirection, policies []*config.PolicyDefinition, all bool) (err error) { + r.mu.Lock() + defer r.mu.Unlock() + + ps := make([]*Policy, 0, len(policies)) + seen := make(map[string]bool) + for _, x := range policies { + p, ok := r.policyMap[x.Name] + if !ok { + err = fmt.Errorf("not found policy %s", x.Name) + return + } + if seen[x.Name] { + err = fmt.Errorf("duplicated policy %s", x.Name) + return + } + seen[x.Name] = true + ps = append(ps, p) + } + cur := r.getPolicy(id, dir) + + if all { + err = r.setPolicy(id, dir, nil) + if err != nil { + return + } + err = r.setDefaultPolicy(id, dir, ROUTE_TYPE_NONE) + } else { + l := len(cur) - len(ps) + if l < 0 { + // try to remove more than the assigned policies... + l = len(cur) + } + n := make([]*Policy, 0, l) + for _, y := range cur { + found := false + for _, x := range ps { + if x.Name == y.Name { + found = true + break + } + } + if !found { + n = append(n, y) + } + } + err = r.setPolicy(id, dir, n) + } + return err +} + +func (r *RoutingPolicy) ReplacePolicyAssignment(id string, dir PolicyDirection, policies []*config.PolicyDefinition, def RouteType) (err error) { + r.mu.Lock() + defer r.mu.Unlock() + + ps := make([]*Policy, 0, len(policies)) + seen := make(map[string]bool) + for _, x := range policies { + p, ok := r.policyMap[x.Name] + if !ok { + err = fmt.Errorf("not found policy %s", x.Name) + return + } + if seen[x.Name] { + err = fmt.Errorf("duplicated policy %s", x.Name) + return + } + seen[x.Name] = true + ps = append(ps, p) + } + r.getPolicy(id, dir) + err = r.setPolicy(id, dir, ps) + if err == nil && def != ROUTE_TYPE_NONE { + err = r.setDefaultPolicy(id, dir, def) + } + return err +} + +func (r *RoutingPolicy) Reset(rp *config.RoutingPolicy, ap map[string]config.ApplyPolicy) error { + r.mu.Lock() + defer r.mu.Unlock() + + if rp != nil { + if err := r.reload(*rp); err != nil { + log.WithFields(log.Fields{ + "Topic": "Policy", + }).Errorf("failed to create routing policy: %s", err) + return err + } + } + + for id, c := range ap { + for _, dir := range []PolicyDirection{POLICY_DIRECTION_IN, POLICY_DIRECTION_IMPORT, POLICY_DIRECTION_EXPORT} { + ps, def, err := r.getAssignmentFromConfig(dir, c) + if err != nil { + log.WithFields(log.Fields{ + "Topic": "Policy", + "Dir": dir, + }).Errorf("failed to get policy info: %s", err) + continue + } + r.setDefaultPolicy(id, dir, def) + r.setPolicy(id, dir, ps) + } + } + return nil +} + +func NewRoutingPolicy() *RoutingPolicy { + return &RoutingPolicy{ + definedSetMap: make(map[DefinedType]map[string]DefinedSet), + policyMap: make(map[string]*Policy), + statementMap: make(map[string]*Statement), + assignmentMap: make(map[string]*Assignment), + } +} + +func CanImportToVrf(v *Vrf, path *Path) bool { + f := func(arg []bgp.ExtendedCommunityInterface) []string { + ret := make([]string, 0, len(arg)) + for _, a := range arg { + ret = append(ret, fmt.Sprintf("RT:%s", a.String())) + } + return ret + } + set, _ := NewExtCommunitySet(config.ExtCommunitySet{ + ExtCommunitySetName: v.Name, + ExtCommunityList: f(v.ImportRt), + }) + matchSet := config.MatchExtCommunitySet{ + ExtCommunitySet: v.Name, + MatchSetOptions: config.MATCH_SET_OPTIONS_TYPE_ANY, + } + c, _ := NewExtCommunityCondition(matchSet) + c.set = set + return c.Evaluate(path, nil) +} + +type PolicyAssignment struct { + Name string + Type PolicyDirection + Policies []*Policy + Default RouteType +} diff --git a/internal/pkg/table/policy_test.go b/internal/pkg/table/policy_test.go new file mode 100644 index 00000000..7f1a1dd9 --- /dev/null +++ b/internal/pkg/table/policy_test.go @@ -0,0 +1,3140 @@ +// Copyright (C) 2014,2015 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 table + +import ( + "fmt" + "math" + "net" + "strconv" + "strings" + "testing" + "time" + + "github.com/osrg/gobgp/internal/pkg/config" + "github.com/osrg/gobgp/pkg/packet/bgp" + + log "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPrefixCalcurateNoRange(t *testing.T) { + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65001})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.0")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + path := ProcessMessage(updateMsg, peer, time.Now())[0] + // test + pl1, _ := NewPrefix(config.Prefix{IpPrefix: "10.10.0.0/24", MasklengthRange: ""}) + match1 := pl1.Match(path) + assert.Equal(t, true, match1) + pl2, _ := NewPrefix(config.Prefix{IpPrefix: "10.10.0.0/23", MasklengthRange: ""}) + match2 := pl2.Match(path) + assert.Equal(t, false, match2) + pl3, _ := NewPrefix(config.Prefix{IpPrefix: "10.10.0.0/16", MasklengthRange: "21..24"}) + match3 := pl3.Match(path) + assert.Equal(t, true, match3) +} + +func TestPrefixCalcurateAddress(t *testing.T) { + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65001})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + path := ProcessMessage(updateMsg, peer, time.Now())[0] + // test + pl1, _ := NewPrefix(config.Prefix{IpPrefix: "10.11.0.0/16", MasklengthRange: "21..24"}) + match1 := pl1.Match(path) + assert.Equal(t, false, match1) + pl2, _ := NewPrefix(config.Prefix{IpPrefix: "10.10.0.0/16", MasklengthRange: "21..24"}) + match2 := pl2.Match(path) + assert.Equal(t, true, match2) +} + +func TestPrefixCalcurateLength(t *testing.T) { + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65001})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + path := ProcessMessage(updateMsg, peer, time.Now())[0] + // test + pl1, _ := NewPrefix(config.Prefix{IpPrefix: "10.10.64.0/24", MasklengthRange: "21..24"}) + match1 := pl1.Match(path) + assert.Equal(t, false, match1) + pl2, _ := NewPrefix(config.Prefix{IpPrefix: "10.10.64.0/16", MasklengthRange: "21..24"}) + match2 := pl2.Match(path) + assert.Equal(t, true, match2) +} + +func TestPrefixCalcurateLengthRange(t *testing.T) { + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65001})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + path := ProcessMessage(updateMsg, peer, time.Now())[0] + // test + pl1, _ := NewPrefix(config.Prefix{IpPrefix: "10.10.0.0/16", MasklengthRange: "21..23"}) + match1 := pl1.Match(path) + assert.Equal(t, false, match1) + pl2, _ := NewPrefix(config.Prefix{IpPrefix: "10.10.0.0/16", MasklengthRange: "25..26"}) + match2 := pl2.Match(path) + assert.Equal(t, false, match2) + pl3, _ := NewPrefix(config.Prefix{IpPrefix: "10.10.0.0/16", MasklengthRange: "21..24"}) + match3 := pl3.Match(path) + assert.Equal(t, true, match3) +} + +func TestPrefixCalcurateNoRangeIPv6(t *testing.T) { + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("2001::192:168:50:1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65001})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + mpnlri := []bgp.AddrPrefixInterface{bgp.NewIPv6AddrPrefix(64, "2001:123:123:1::")} + mpreach := bgp.NewPathAttributeMpReachNLRI("2001::192:168:50:1", mpnlri) + med := bgp.NewPathAttributeMultiExitDisc(0) + pathAttributes := []bgp.PathAttributeInterface{mpreach, origin, aspath, med} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nil) + path := ProcessMessage(updateMsg, peer, time.Now())[0] + // test + pl1, _ := NewPrefix(config.Prefix{IpPrefix: "2001:123:123::/48", MasklengthRange: ""}) + match1 := pl1.Match(path) + assert.Equal(t, false, match1) + pl2, _ := NewPrefix(config.Prefix{IpPrefix: "2001:123:123:1::/64", MasklengthRange: ""}) + match2 := pl2.Match(path) + assert.Equal(t, true, match2) + pl3, _ := NewPrefix(config.Prefix{IpPrefix: "2001:123:123::/48", MasklengthRange: "64..80"}) + match3 := pl3.Match(path) + assert.Equal(t, true, match3) +} + +func TestPrefixCalcurateAddressIPv6(t *testing.T) { + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("2001::192:168:50:1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65001})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + mpnlri := []bgp.AddrPrefixInterface{bgp.NewIPv6AddrPrefix(64, "2001:123:123:1::")} + mpreach := bgp.NewPathAttributeMpReachNLRI("2001::192:168:50:1", mpnlri) + med := bgp.NewPathAttributeMultiExitDisc(0) + pathAttributes := []bgp.PathAttributeInterface{mpreach, origin, aspath, med} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nil) + path := ProcessMessage(updateMsg, peer, time.Now())[0] + // test + pl1, _ := NewPrefix(config.Prefix{IpPrefix: "2001:123:128::/48", MasklengthRange: "64..80"}) + match1 := pl1.Match(path) + assert.Equal(t, false, match1) + pl2, _ := NewPrefix(config.Prefix{IpPrefix: "2001:123:123::/48", MasklengthRange: "64..80"}) + match2 := pl2.Match(path) + assert.Equal(t, true, match2) +} + +func TestPrefixCalcurateLengthIPv6(t *testing.T) { + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("2001::192:168:50:1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65001})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + mpnlri := []bgp.AddrPrefixInterface{bgp.NewIPv6AddrPrefix(64, "2001:123:123:1::")} + mpreach := bgp.NewPathAttributeMpReachNLRI("2001::192:168:50:1", mpnlri) + med := bgp.NewPathAttributeMultiExitDisc(0) + pathAttributes := []bgp.PathAttributeInterface{mpreach, origin, aspath, med} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nil) + path := ProcessMessage(updateMsg, peer, time.Now())[0] + // test + pl1, _ := NewPrefix(config.Prefix{IpPrefix: "2001:123:123:64::/64", MasklengthRange: "64..80"}) + match1 := pl1.Match(path) + assert.Equal(t, false, match1) + pl2, _ := NewPrefix(config.Prefix{IpPrefix: "2001:123:123:64::/48", MasklengthRange: "64..80"}) + match2 := pl2.Match(path) + assert.Equal(t, true, match2) +} + +func TestPrefixCalcurateLengthRangeIPv6(t *testing.T) { + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("2001::192:168:50:1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65001})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + mpnlri := []bgp.AddrPrefixInterface{bgp.NewIPv6AddrPrefix(64, "2001:123:123:1::")} + mpreach := bgp.NewPathAttributeMpReachNLRI("2001::192:168:50:1", mpnlri) + med := bgp.NewPathAttributeMultiExitDisc(0) + pathAttributes := []bgp.PathAttributeInterface{mpreach, origin, aspath, med} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nil) + path := ProcessMessage(updateMsg, peer, time.Now())[0] + // test + pl1, _ := NewPrefix(config.Prefix{IpPrefix: "2001:123:123::/48", MasklengthRange: "62..63"}) + match1 := pl1.Match(path) + assert.Equal(t, false, match1) + pl2, _ := NewPrefix(config.Prefix{IpPrefix: "2001:123:123::/48", MasklengthRange: "65..66"}) + match2 := pl2.Match(path) + assert.Equal(t, false, match2) + pl3, _ := NewPrefix(config.Prefix{IpPrefix: "2001:123:123::/48", MasklengthRange: "63..65"}) + match3 := pl3.Match(path) + assert.Equal(t, true, match3) +} + +func TestPolicyNotMatch(t *testing.T) { + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65001})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + path := ProcessMessage(updateMsg, peer, time.Now())[0] + + // create policy + ps := createPrefixSet("ps1", "10.3.0.0/16", "21..24") + ns := createNeighborSet("ns1", "10.0.0.1") + ds := config.DefinedSets{} + ds.PrefixSets = []config.PrefixSet{ps} + ds.NeighborSets = []config.NeighborSet{ns} + s := createStatement("statement1", "ps1", "ns1", false) + pd := createPolicyDefinition("pd1", s) + pl := createRoutingPolicy(ds, pd) + + r := NewRoutingPolicy() + err := r.reload(pl) + assert.Nil(t, err) + pType, newPath := r.policyMap["pd1"].Apply(path, nil) + assert.Equal(t, ROUTE_TYPE_NONE, pType) + assert.Equal(t, newPath, path) +} + +func TestPolicyMatchAndReject(t *testing.T) { + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65001})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + path := ProcessMessage(updateMsg, peer, time.Now())[0] + // create policy + ps := createPrefixSet("ps1", "10.10.0.0/16", "21..24") + ns := createNeighborSet("ns1", "10.0.0.1") + ds := config.DefinedSets{} + ds.PrefixSets = []config.PrefixSet{ps} + ds.NeighborSets = []config.NeighborSet{ns} + + s := createStatement("statement1", "ps1", "ns1", false) + pd := createPolicyDefinition("pd1", s) + pl := createRoutingPolicy(ds, pd) + + r := NewRoutingPolicy() + err := r.reload(pl) + assert.Nil(t, err) + pType, newPath := r.policyMap["pd1"].Apply(path, nil) + assert.Equal(t, ROUTE_TYPE_REJECT, pType) + assert.Equal(t, newPath, path) +} + +func TestPolicyMatchAndAccept(t *testing.T) { + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65001})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + path := ProcessMessage(updateMsg, peer, time.Now())[0] + // create policy + ps := createPrefixSet("ps1", "10.10.0.0/16", "21..24") + ns := createNeighborSet("ns1", "10.0.0.1") + ds := config.DefinedSets{} + ds.PrefixSets = []config.PrefixSet{ps} + ds.NeighborSets = []config.NeighborSet{ns} + + s := createStatement("statement1", "ps1", "ns1", true) + pd := createPolicyDefinition("pd1", s) + pl := createRoutingPolicy(ds, pd) + + //test + r := NewRoutingPolicy() + err := r.reload(pl) + assert.Nil(t, err) + pType, newPath := r.policyMap["pd1"].Apply(path, nil) + assert.Equal(t, ROUTE_TYPE_ACCEPT, pType) + assert.Equal(t, path, newPath) +} + +func TestPolicyRejectOnlyPrefixSet(t *testing.T) { + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.1.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65001})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.1.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.1.101")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + path1 := ProcessMessage(updateMsg, peer, time.Now())[0] + + peer = &PeerInfo{AS: 65002, Address: net.ParseIP("10.0.2.2")} + origin = bgp.NewPathAttributeOrigin(0) + aspathParam = []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65002})} + aspath = bgp.NewPathAttributeAsPath(aspathParam) + nexthop = bgp.NewPathAttributeNextHop("10.0.2.2") + med = bgp.NewPathAttributeMultiExitDisc(0) + pathAttributes = []bgp.PathAttributeInterface{origin, aspath, nexthop, med} + nlri = []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.9.2.102")} + updateMsg = bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + path2 := ProcessMessage(updateMsg, peer, time.Now())[0] + + // create policy + ps := createPrefixSet("ps1", "10.10.1.0/16", "21..24") + ds := config.DefinedSets{} + ds.PrefixSets = []config.PrefixSet{ps} + + s := createStatement("statement1", "ps1", "", false) + pd := createPolicyDefinition("pd1", s) + pl := createRoutingPolicy(ds, pd) + + //test + r := NewRoutingPolicy() + err := r.reload(pl) + assert.Nil(t, err) + p := r.policyMap["pd1"] + pType, newPath := p.Apply(path1, nil) + assert.Equal(t, ROUTE_TYPE_REJECT, pType) + assert.Equal(t, newPath, path1) + + pType2, newPath2 := p.Apply(path2, nil) + assert.Equal(t, ROUTE_TYPE_NONE, pType2) + assert.Equal(t, newPath2, path2) +} + +func TestPolicyRejectOnlyNeighborSet(t *testing.T) { + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.1.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65001})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.1.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.1.101")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + path1 := ProcessMessage(updateMsg, peer, time.Now())[0] + + peer = &PeerInfo{AS: 65002, Address: net.ParseIP("10.0.2.2")} + origin = bgp.NewPathAttributeOrigin(0) + aspathParam = []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65002})} + aspath = bgp.NewPathAttributeAsPath(aspathParam) + nexthop = bgp.NewPathAttributeNextHop("10.0.2.2") + med = bgp.NewPathAttributeMultiExitDisc(0) + pathAttributes = []bgp.PathAttributeInterface{origin, aspath, nexthop, med} + nlri = []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.2.102")} + updateMsg = bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + path2 := ProcessMessage(updateMsg, peer, time.Now())[0] + + // create policy + ns := createNeighborSet("ns1", "10.0.1.1") + ds := config.DefinedSets{} + ds.NeighborSets = []config.NeighborSet{ns} + + s := createStatement("statement1", "", "ns1", false) + pd := createPolicyDefinition("pd1", s) + pl := createRoutingPolicy(ds, pd) + + //test + r := NewRoutingPolicy() + err := r.reload(pl) + assert.Nil(t, err) + pType, newPath := r.policyMap["pd1"].Apply(path1, nil) + assert.Equal(t, ROUTE_TYPE_REJECT, pType) + assert.Equal(t, newPath, path1) + + pType2, newPath2 := r.policyMap["pd1"].Apply(path2, nil) + assert.Equal(t, ROUTE_TYPE_NONE, pType2) + assert.Equal(t, newPath2, path2) +} + +func TestPolicyDifferentRoutefamilyOfPathAndPolicy(t *testing.T) { + // create path ipv4 + peerIPv4 := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + originIPv4 := bgp.NewPathAttributeOrigin(0) + aspathParamIPv4 := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65001})} + aspathIPv4 := bgp.NewPathAttributeAsPath(aspathParamIPv4) + nexthopIPv4 := bgp.NewPathAttributeNextHop("10.0.0.1") + medIPv4 := bgp.NewPathAttributeMultiExitDisc(0) + pathAttributesIPv4 := []bgp.PathAttributeInterface{originIPv4, aspathIPv4, nexthopIPv4, medIPv4} + nlriIPv4 := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsgIPv4 := bgp.NewBGPUpdateMessage(nil, pathAttributesIPv4, nlriIPv4) + pathIPv4 := ProcessMessage(updateMsgIPv4, peerIPv4, time.Now())[0] + // create path ipv6 + peerIPv6 := &PeerInfo{AS: 65001, Address: net.ParseIP("2001::192:168:50:1")} + originIPv6 := bgp.NewPathAttributeOrigin(0) + aspathParamIPv6 := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65001})} + aspathIPv6 := bgp.NewPathAttributeAsPath(aspathParamIPv6) + mpnlriIPv6 := []bgp.AddrPrefixInterface{bgp.NewIPv6AddrPrefix(64, "2001:123:123:1::")} + mpreachIPv6 := bgp.NewPathAttributeMpReachNLRI("2001::192:168:50:1", mpnlriIPv6) + medIPv6 := bgp.NewPathAttributeMultiExitDisc(0) + pathAttributesIPv6 := []bgp.PathAttributeInterface{mpreachIPv6, originIPv6, aspathIPv6, medIPv6} + updateMsgIPv6 := bgp.NewBGPUpdateMessage(nil, pathAttributesIPv6, nil) + pathIPv6 := ProcessMessage(updateMsgIPv6, peerIPv6, time.Now())[0] + // create policy + psIPv4 := createPrefixSet("psIPv4", "10.10.0.0/16", "21..24") + nsIPv4 := createNeighborSet("nsIPv4", "10.0.0.1") + + psIPv6 := createPrefixSet("psIPv6", "2001:123:123::/48", "64..80") + nsIPv6 := createNeighborSet("nsIPv6", "2001::192:168:50:1") + + ds := config.DefinedSets{} + ds.PrefixSets = []config.PrefixSet{psIPv4, psIPv6} + ds.NeighborSets = []config.NeighborSet{nsIPv4, nsIPv6} + + stIPv4 := createStatement("statement1", "psIPv4", "nsIPv4", false) + stIPv6 := createStatement("statement2", "psIPv6", "nsIPv6", false) + + pd := createPolicyDefinition("pd1", stIPv4, stIPv6) + pl := createRoutingPolicy(ds, pd) + + //test + r := NewRoutingPolicy() + err := r.reload(pl) + assert.Nil(t, err) + p := r.policyMap["pd1"] + pType1, newPath1 := p.Apply(pathIPv4, nil) + assert.Equal(t, ROUTE_TYPE_REJECT, pType1) + assert.Equal(t, newPath1, pathIPv4) + + pType2, newPath2 := p.Apply(pathIPv6, nil) + assert.Equal(t, ROUTE_TYPE_REJECT, pType2) + assert.Equal(t, newPath2, pathIPv6) +} + +func TestAsPathLengthConditionEvaluate(t *testing.T) { + // setup + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{ + bgp.NewAsPathParam(2, []uint16{65001, 65000, 65004, 65005}), + bgp.NewAsPathParam(1, []uint16{65001, 65000, 65004, 65005}), + } + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + UpdatePathAttrs4ByteAs(updateMsg.Body.(*bgp.BGPUpdate)) + path := ProcessMessage(updateMsg, peer, time.Now())[0] + + // create match condition + asPathLength := config.AsPathLength{ + Operator: "eq", + Value: 5, + } + c, _ := NewAsPathLengthCondition(asPathLength) + + // test + assert.Equal(t, true, c.Evaluate(path, nil)) + + // create match condition + asPathLength = config.AsPathLength{ + Operator: "ge", + Value: 3, + } + c, _ = NewAsPathLengthCondition(asPathLength) + + // test + assert.Equal(t, true, c.Evaluate(path, nil)) + + // create match condition + asPathLength = config.AsPathLength{ + Operator: "le", + Value: 3, + } + c, _ = NewAsPathLengthCondition(asPathLength) + + // test + assert.Equal(t, false, c.Evaluate(path, nil)) +} + +func TestPolicyMatchAndAcceptNextHop(t *testing.T) { + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65001})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + path := ProcessMessage(updateMsg, peer, time.Now())[0] + + // create policy + ps := createPrefixSet("ps1", "10.10.0.0/16", "21..24") + ns := createNeighborSet("ns1", "10.0.0.1") + ds := config.DefinedSets{} + ds.PrefixSets = []config.PrefixSet{ps} + ds.NeighborSets = []config.NeighborSet{ns} + s := createStatement("statement1", "ps1", "ns1", true) + s.Conditions.BgpConditions.NextHopInList = []string{"10.0.0.1/32"} + pd := createPolicyDefinition("pd1", s) + pl := createRoutingPolicy(ds, pd) + + r := NewRoutingPolicy() + err := r.reload(pl) + assert.Nil(t, err) + pType, newPath := r.policyMap["pd1"].Apply(path, nil) + assert.Equal(t, ROUTE_TYPE_ACCEPT, pType) + assert.Equal(t, newPath, path) +} + +func TestPolicyMatchAndRejectNextHop(t *testing.T) { + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65001})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + path := ProcessMessage(updateMsg, peer, time.Now())[0] + + // create policy + ps := createPrefixSet("ps1", "10.10.0.0/16", "21..24") + ns := createNeighborSet("ns1", "10.0.0.1") + ds := config.DefinedSets{} + ds.PrefixSets = []config.PrefixSet{ps} + ds.NeighborSets = []config.NeighborSet{ns} + s := createStatement("statement1", "ps1", "ns1", true) + s.Conditions.BgpConditions.NextHopInList = []string{"10.0.0.12"} + pd := createPolicyDefinition("pd1", s) + pl := createRoutingPolicy(ds, pd) + + r := NewRoutingPolicy() + err := r.reload(pl) + assert.Nil(t, err) + pType, newPath := r.policyMap["pd1"].Apply(path, nil) + assert.Equal(t, ROUTE_TYPE_NONE, pType) + assert.Equal(t, newPath, path) +} + +func TestAsPathLengthConditionWithOtherCondition(t *testing.T) { + // setup + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{ + bgp.NewAsPathParam(2, []uint16{65001, 65000, 65004, 65004, 65005}), + bgp.NewAsPathParam(1, []uint16{65001, 65000, 65004, 65005}), + } + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + UpdatePathAttrs4ByteAs(updateMsg.Body.(*bgp.BGPUpdate)) + path := ProcessMessage(updateMsg, peer, time.Now())[0] + + // create policy + ps := createPrefixSet("ps1", "10.10.1.0/16", "21..24") + ns := createNeighborSet("ns1", "10.0.0.1") + + ds := config.DefinedSets{} + ds.PrefixSets = []config.PrefixSet{ps} + ds.NeighborSets = []config.NeighborSet{ns} + + // create match condition + asPathLength := config.AsPathLength{ + Operator: "le", + Value: 10, + } + + s := createStatement("statement1", "ps1", "ns1", false) + s.Conditions.BgpConditions.AsPathLength = asPathLength + pd := createPolicyDefinition("pd1", s) + pl := createRoutingPolicy(ds, pd) + + //test + r := NewRoutingPolicy() + err := r.reload(pl) + assert.Nil(t, err) + p := r.policyMap["pd1"] + pType, newPath := p.Apply(path, nil) + assert.Equal(t, ROUTE_TYPE_REJECT, pType) + assert.Equal(t, newPath, path) + +} + +func TestAs4PathLengthConditionEvaluate(t *testing.T) { + // setup + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{ + bgp.NewAs4PathParam(2, []uint32{ + createAs4Value("65001.1"), + createAs4Value("65000.1"), + createAs4Value("65004.1"), + createAs4Value("65005.1"), + }), + bgp.NewAs4PathParam(1, []uint32{ + createAs4Value("65001.1"), + createAs4Value("65000.1"), + createAs4Value("65004.1"), + createAs4Value("65005.1"), + }), + } + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + UpdatePathAttrs4ByteAs(updateMsg.Body.(*bgp.BGPUpdate)) + path := ProcessMessage(updateMsg, peer, time.Now())[0] + + // create match condition + asPathLength := config.AsPathLength{ + Operator: "eq", + Value: 5, + } + c, _ := NewAsPathLengthCondition(asPathLength) + + // test + assert.Equal(t, true, c.Evaluate(path, nil)) + + // create match condition + asPathLength = config.AsPathLength{ + Operator: "ge", + Value: 3, + } + c, _ = NewAsPathLengthCondition(asPathLength) + + // test + assert.Equal(t, true, c.Evaluate(path, nil)) + + // create match condition + asPathLength = config.AsPathLength{ + Operator: "le", + Value: 3, + } + c, _ = NewAsPathLengthCondition(asPathLength) + + // test + assert.Equal(t, false, c.Evaluate(path, nil)) +} + +func addPolicy(r *RoutingPolicy, x *Policy) { + for _, s := range x.Statements { + for _, c := range s.Conditions { + r.validateCondition(c) + } + } +} + +func TestAs4PathLengthConditionWithOtherCondition(t *testing.T) { + // setup + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{ + bgp.NewAs4PathParam(2, []uint32{ + createAs4Value("65001.1"), + createAs4Value("65000.1"), + createAs4Value("65004.1"), + createAs4Value("65004.1"), + createAs4Value("65005.1"), + }), + bgp.NewAs4PathParam(1, []uint32{ + createAs4Value("65001.1"), + createAs4Value("65000.1"), + createAs4Value("65004.1"), + createAs4Value("65005.1"), + }), + } + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + UpdatePathAttrs4ByteAs(updateMsg.Body.(*bgp.BGPUpdate)) + path := ProcessMessage(updateMsg, peer, time.Now())[0] + + // create policy + ps := createPrefixSet("ps1", "10.10.1.0/16", "21..24") + ns := createNeighborSet("ns1", "10.0.0.1") + + ds := config.DefinedSets{} + ds.PrefixSets = []config.PrefixSet{ps} + ds.NeighborSets = []config.NeighborSet{ns} + + // create match condition + asPathLength := config.AsPathLength{ + Operator: "le", + Value: 10, + } + + s := createStatement("statement1", "ps1", "ns1", false) + s.Conditions.BgpConditions.AsPathLength = asPathLength + pd := createPolicyDefinition("pd1", s) + pl := createRoutingPolicy(ds, pd) + + //test + r := NewRoutingPolicy() + r.reload(pl) + p, _ := NewPolicy(pl.PolicyDefinitions[0]) + addPolicy(r, p) + pType, newPath := p.Apply(path, nil) + assert.Equal(t, ROUTE_TYPE_REJECT, pType) + assert.Equal(t, newPath, path) + +} + +func TestAsPathConditionEvaluate(t *testing.T) { + + // setup + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam1 := []bgp.AsPathParamInterface{ + bgp.NewAsPathParam(2, []uint16{65001, 65000, 65010, 65004, 65005}), + } + aspath := bgp.NewPathAttributeAsPath(aspathParam1) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg1 := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + UpdatePathAttrs4ByteAs(updateMsg1.Body.(*bgp.BGPUpdate)) + path1 := ProcessMessage(updateMsg1, peer, time.Now())[0] + + aspathParam2 := []bgp.AsPathParamInterface{ + bgp.NewAsPathParam(2, []uint16{65010}), + } + aspath2 := bgp.NewPathAttributeAsPath(aspathParam2) + pathAttributes = []bgp.PathAttributeInterface{origin, aspath2, nexthop, med} + updateMsg2 := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + UpdatePathAttrs4ByteAs(updateMsg2.Body.(*bgp.BGPUpdate)) + path2 := ProcessMessage(updateMsg2, peer, time.Now())[0] + + // create match condition + asPathSet1 := config.AsPathSet{ + AsPathSetName: "asset1", + AsPathList: []string{"^65001"}, + } + + asPathSet2 := config.AsPathSet{ + AsPathSetName: "asset2", + AsPathList: []string{"65005$"}, + } + + asPathSet3 := config.AsPathSet{ + AsPathSetName: "asset3", + AsPathList: []string{"65004", "65005$"}, + } + + asPathSet4 := config.AsPathSet{ + AsPathSetName: "asset4", + AsPathList: []string{"65000$"}, + } + + asPathSet5 := config.AsPathSet{ + AsPathSetName: "asset5", + AsPathList: []string{"65010"}, + } + + asPathSet6 := config.AsPathSet{ + AsPathSetName: "asset6", + AsPathList: []string{"^65010$"}, + } + + m := make(map[string]DefinedSet) + for _, s := range []config.AsPathSet{asPathSet1, asPathSet2, asPathSet3, + asPathSet4, asPathSet5, asPathSet6} { + a, _ := NewAsPathSet(s) + m[s.AsPathSetName] = a + } + + createAspathC := func(name string, option config.MatchSetOptionsType) *AsPathCondition { + matchSet := config.MatchAsPathSet{} + matchSet.AsPathSet = name + matchSet.MatchSetOptions = option + p, _ := NewAsPathCondition(matchSet) + if v, ok := m[name]; ok { + p.set = v.(*AsPathSet) + } + return p + } + + p1 := createAspathC("asset1", config.MATCH_SET_OPTIONS_TYPE_ANY) + p2 := createAspathC("asset2", config.MATCH_SET_OPTIONS_TYPE_ANY) + p3 := createAspathC("asset3", config.MATCH_SET_OPTIONS_TYPE_ANY) + p4 := createAspathC("asset4", config.MATCH_SET_OPTIONS_TYPE_ANY) + p5 := createAspathC("asset5", config.MATCH_SET_OPTIONS_TYPE_ANY) + p6 := createAspathC("asset6", config.MATCH_SET_OPTIONS_TYPE_ANY) + p7 := createAspathC("asset3", config.MATCH_SET_OPTIONS_TYPE_ALL) + p8 := createAspathC("asset3", config.MATCH_SET_OPTIONS_TYPE_INVERT) + + // test + assert.Equal(t, true, p1.Evaluate(path1, nil)) + assert.Equal(t, true, p2.Evaluate(path1, nil)) + assert.Equal(t, true, p3.Evaluate(path1, nil)) + assert.Equal(t, false, p4.Evaluate(path1, nil)) + assert.Equal(t, true, p5.Evaluate(path1, nil)) + assert.Equal(t, false, p6.Evaluate(path1, nil)) + assert.Equal(t, true, p6.Evaluate(path2, nil)) + assert.Equal(t, true, p7.Evaluate(path1, nil)) + assert.Equal(t, true, p8.Evaluate(path2, nil)) +} + +func TestMultipleAsPathConditionEvaluate(t *testing.T) { + + // setup + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam1 := []bgp.AsPathParamInterface{ + bgp.NewAsPathParam(2, []uint16{65001, 65000, 54000, 65004, 65005}), + } + aspath := bgp.NewPathAttributeAsPath(aspathParam1) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg1 := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + UpdatePathAttrs4ByteAs(updateMsg1.Body.(*bgp.BGPUpdate)) + path1 := ProcessMessage(updateMsg1, peer, time.Now())[0] + + // create match condition + asPathSet1 := config.AsPathSet{ + AsPathSetName: "asset1", + AsPathList: []string{"^65001_65000"}, + } + + asPathSet2 := config.AsPathSet{ + AsPathSetName: "asset2", + AsPathList: []string{"65004_65005$"}, + } + + asPathSet3 := config.AsPathSet{ + AsPathSetName: "asset3", + AsPathList: []string{"65001_65000_54000"}, + } + + asPathSet4 := config.AsPathSet{ + AsPathSetName: "asset4", + AsPathList: []string{"54000_65004_65005"}, + } + + asPathSet5 := config.AsPathSet{ + AsPathSetName: "asset5", + AsPathList: []string{"^65001 65000 54000 65004 65005$"}, + } + + asPathSet6 := config.AsPathSet{ + AsPathSetName: "asset6", + AsPathList: []string{".*_[0-9]+_65005"}, + } + + asPathSet7 := config.AsPathSet{ + AsPathSetName: "asset7", + AsPathList: []string{".*_5[0-9]+_[0-9]+"}, + } + + asPathSet8 := config.AsPathSet{ + AsPathSetName: "asset8", + AsPathList: []string{"6[0-9]+_6[0-9]+_5[0-9]+"}, + } + + asPathSet9 := config.AsPathSet{ + AsPathSetName: "asset9", + AsPathList: []string{"6[0-9]+__6[0-9]+"}, + } + + m := make(map[string]DefinedSet) + for _, s := range []config.AsPathSet{asPathSet1, asPathSet2, asPathSet3, + asPathSet4, asPathSet5, asPathSet6, asPathSet7, asPathSet8, asPathSet9} { + a, _ := NewAsPathSet(s) + m[s.AsPathSetName] = a + } + + createAspathC := func(name string, option config.MatchSetOptionsType) *AsPathCondition { + matchSet := config.MatchAsPathSet{} + matchSet.AsPathSet = name + matchSet.MatchSetOptions = option + p, _ := NewAsPathCondition(matchSet) + if v, ok := m[name]; ok { + p.set = v.(*AsPathSet) + } + return p + } + + p1 := createAspathC("asset1", config.MATCH_SET_OPTIONS_TYPE_ANY) + p2 := createAspathC("asset2", config.MATCH_SET_OPTIONS_TYPE_ANY) + p3 := createAspathC("asset3", config.MATCH_SET_OPTIONS_TYPE_ANY) + p4 := createAspathC("asset4", config.MATCH_SET_OPTIONS_TYPE_ANY) + p5 := createAspathC("asset5", config.MATCH_SET_OPTIONS_TYPE_ANY) + p6 := createAspathC("asset6", config.MATCH_SET_OPTIONS_TYPE_ANY) + p7 := createAspathC("asset7", config.MATCH_SET_OPTIONS_TYPE_ANY) + p8 := createAspathC("asset8", config.MATCH_SET_OPTIONS_TYPE_ANY) + p9 := createAspathC("asset9", config.MATCH_SET_OPTIONS_TYPE_ANY) + + // test + assert.Equal(t, true, p1.Evaluate(path1, nil)) + assert.Equal(t, true, p2.Evaluate(path1, nil)) + assert.Equal(t, true, p3.Evaluate(path1, nil)) + assert.Equal(t, true, p4.Evaluate(path1, nil)) + assert.Equal(t, true, p5.Evaluate(path1, nil)) + assert.Equal(t, true, p6.Evaluate(path1, nil)) + assert.Equal(t, true, p7.Evaluate(path1, nil)) + assert.Equal(t, true, p8.Evaluate(path1, nil)) + assert.Equal(t, false, p9.Evaluate(path1, nil)) +} + +func TestAsPathCondition(t *testing.T) { + type astest struct { + path *Path + result bool + } + + makeTest := func(asPathAttrType uint8, ases []uint32, result bool) astest { + aspathParam := []bgp.AsPathParamInterface{ + bgp.NewAs4PathParam(asPathAttrType, ases), + } + pathAttributes := []bgp.PathAttributeInterface{bgp.NewPathAttributeAsPath(aspathParam)} + p := NewPath(nil, nil, false, pathAttributes, time.Time{}, false) + return astest{ + path: p, + result: result, + } + } + + tests := make(map[string][]astest) + + tests["^(100_)+(200_)+$"] = []astest{ + makeTest(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, []uint32{100, 200}, true), + makeTest(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, []uint32{100, 100, 200}, true), + makeTest(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, []uint32{100, 100, 200, 200}, true), + makeTest(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, []uint32{100, 100, 200, 200, 300}, false), + } + + aslen255 := func() []uint32 { + r := make([]uint32, 255) + for i := 0; i < 255; i++ { + r[i] = 1 + } + return r + }() + tests["^([0-9]+_){0,255}$"] = []astest{ + makeTest(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, aslen255, true), + makeTest(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, append(aslen255, 1), false), + } + + tests["(_7521)$"] = []astest{ + makeTest(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, []uint32{7521}, true), + makeTest(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, []uint32{1000, 7521}, true), + makeTest(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, []uint32{7521, 1000}, false), + makeTest(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, []uint32{1000, 7521, 100}, false), + } + + tests["^65001( |_.*_)65535$"] = []astest{ + makeTest(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, []uint32{65001, 65535}, true), + makeTest(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, []uint32{65001, 65001, 65535}, true), + makeTest(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, []uint32{65001, 65002, 65003, 65535}, true), + makeTest(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, []uint32{65001, 65534}, false), + makeTest(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, []uint32{65002, 65535}, false), + makeTest(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, []uint32{65002, 65001, 65535}, false), + makeTest(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, []uint32{65001, 65535, 65002}, false), + makeTest(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, []uint32{650019, 65535}, false), + makeTest(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, []uint32{65001, 165535}, false), + } + + for k, v := range tests { + s, _ := NewAsPathSet(config.AsPathSet{ + AsPathSetName: k, + AsPathList: []string{k}, + }) + c, _ := NewAsPathCondition(config.MatchAsPathSet{ + AsPathSet: k, + MatchSetOptions: config.MATCH_SET_OPTIONS_TYPE_ANY, + }) + c.set = s + for _, a := range v { + result := c.Evaluate(a.path, nil) + if a.result != result { + log.WithFields(log.Fields{ + "EXP": k, + "ASSTR": a.path.GetAsString(), + "Expected": a.result, + "Result": result, + }).Fatal("failed") + } + } + } +} + +func TestAsPathConditionWithOtherCondition(t *testing.T) { + + // setup + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{ + bgp.NewAsPathParam(1, []uint16{65001, 65000, 65004, 65005}), + bgp.NewAsPathParam(2, []uint16{65001, 65000, 65004, 65004, 65005}), + } + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + UpdatePathAttrs4ByteAs(updateMsg.Body.(*bgp.BGPUpdate)) + path := ProcessMessage(updateMsg, peer, time.Now())[0] + + // create policy + asPathSet := config.AsPathSet{ + AsPathSetName: "asset1", + AsPathList: []string{"65005$"}, + } + + ps := createPrefixSet("ps1", "10.10.1.0/16", "21..24") + ns := createNeighborSet("ns1", "10.0.0.1") + + ds := config.DefinedSets{} + ds.PrefixSets = []config.PrefixSet{ps} + ds.NeighborSets = []config.NeighborSet{ns} + ds.BgpDefinedSets.AsPathSets = []config.AsPathSet{asPathSet} + + s := createStatement("statement1", "ps1", "ns1", false) + s.Conditions.BgpConditions.MatchAsPathSet.AsPathSet = "asset1" + + pd := createPolicyDefinition("pd1", s) + pl := createRoutingPolicy(ds, pd) + + //test + r := NewRoutingPolicy() + err := r.reload(pl) + assert.Nil(t, err) + p := r.policyMap["pd1"] + pType, newPath := p.Apply(path, nil) + assert.Equal(t, ROUTE_TYPE_REJECT, pType) + assert.Equal(t, newPath, path) + +} + +func TestAs4PathConditionEvaluate(t *testing.T) { + + // setup + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam1 := []bgp.AsPathParamInterface{ + bgp.NewAs4PathParam(2, []uint32{ + createAs4Value("65001.1"), + createAs4Value("65000.1"), + createAs4Value("65010.1"), + createAs4Value("65004.1"), + createAs4Value("65005.1"), + })} + + aspath := bgp.NewPathAttributeAsPath(aspathParam1) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg1 := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + UpdatePathAttrs4ByteAs(updateMsg1.Body.(*bgp.BGPUpdate)) + path1 := ProcessMessage(updateMsg1, peer, time.Now())[0] + + aspathParam2 := []bgp.AsPathParamInterface{ + bgp.NewAs4PathParam(2, []uint32{ + createAs4Value("65010.1"), + }), + } + aspath2 := bgp.NewPathAttributeAsPath(aspathParam2) + pathAttributes = []bgp.PathAttributeInterface{origin, aspath2, nexthop, med} + updateMsg2 := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + UpdatePathAttrs4ByteAs(updateMsg2.Body.(*bgp.BGPUpdate)) + path2 := ProcessMessage(updateMsg2, peer, time.Now())[0] + + // create match condition + asPathSet1 := config.AsPathSet{ + AsPathSetName: "asset1", + AsPathList: []string{fmt.Sprintf("^%d", createAs4Value("65001.1"))}, + } + + asPathSet2 := config.AsPathSet{ + AsPathSetName: "asset2", + AsPathList: []string{fmt.Sprintf("%d$", createAs4Value("65005.1"))}, + } + + asPathSet3 := config.AsPathSet{ + AsPathSetName: "asset3", + AsPathList: []string{ + fmt.Sprintf("%d", createAs4Value("65004.1")), + fmt.Sprintf("%d$", createAs4Value("65005.1")), + }, + } + + asPathSet4 := config.AsPathSet{ + AsPathSetName: "asset4", + AsPathList: []string{ + fmt.Sprintf("%d$", createAs4Value("65000.1")), + }, + } + + asPathSet5 := config.AsPathSet{ + AsPathSetName: "asset5", + AsPathList: []string{ + fmt.Sprintf("%d", createAs4Value("65010.1")), + }, + } + + asPathSet6 := config.AsPathSet{ + AsPathSetName: "asset6", + AsPathList: []string{ + fmt.Sprintf("%d$", createAs4Value("65010.1")), + }, + } + + m := make(map[string]DefinedSet) + for _, s := range []config.AsPathSet{asPathSet1, asPathSet2, asPathSet3, + asPathSet4, asPathSet5, asPathSet6} { + a, _ := NewAsPathSet(s) + m[s.AsPathSetName] = a + } + + createAspathC := func(name string, option config.MatchSetOptionsType) *AsPathCondition { + matchSet := config.MatchAsPathSet{} + matchSet.AsPathSet = name + matchSet.MatchSetOptions = option + p, _ := NewAsPathCondition(matchSet) + if v, ok := m[name]; ok { + p.set = v.(*AsPathSet) + } + return p + } + + p1 := createAspathC("asset1", config.MATCH_SET_OPTIONS_TYPE_ANY) + p2 := createAspathC("asset2", config.MATCH_SET_OPTIONS_TYPE_ANY) + p3 := createAspathC("asset3", config.MATCH_SET_OPTIONS_TYPE_ANY) + p4 := createAspathC("asset4", config.MATCH_SET_OPTIONS_TYPE_ANY) + p5 := createAspathC("asset5", config.MATCH_SET_OPTIONS_TYPE_ANY) + p6 := createAspathC("asset6", config.MATCH_SET_OPTIONS_TYPE_ANY) + + p7 := createAspathC("asset3", config.MATCH_SET_OPTIONS_TYPE_ALL) + p8 := createAspathC("asset3", config.MATCH_SET_OPTIONS_TYPE_INVERT) + + // test + assert.Equal(t, true, p1.Evaluate(path1, nil)) + assert.Equal(t, true, p2.Evaluate(path1, nil)) + assert.Equal(t, true, p3.Evaluate(path1, nil)) + assert.Equal(t, false, p4.Evaluate(path1, nil)) + assert.Equal(t, true, p5.Evaluate(path1, nil)) + assert.Equal(t, false, p6.Evaluate(path1, nil)) + assert.Equal(t, true, p6.Evaluate(path2, nil)) + + assert.Equal(t, true, p7.Evaluate(path1, nil)) + assert.Equal(t, true, p8.Evaluate(path2, nil)) +} + +func TestMultipleAs4PathConditionEvaluate(t *testing.T) { + + // setup + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam1 := []bgp.AsPathParamInterface{ + bgp.NewAs4PathParam(2, []uint32{ + createAs4Value("65001.1"), + createAs4Value("65000.1"), + createAs4Value("54000.1"), + createAs4Value("65004.1"), + createAs4Value("65005.1"), + }), + } + + aspath := bgp.NewPathAttributeAsPath(aspathParam1) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg1 := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + UpdatePathAttrs4ByteAs(updateMsg1.Body.(*bgp.BGPUpdate)) + path1 := ProcessMessage(updateMsg1, peer, time.Now())[0] + + // create match condition + asPathSet1 := config.AsPathSet{ + AsPathSetName: "asset1", + AsPathList: []string{ + fmt.Sprintf("^%d_%d", createAs4Value("65001.1"), createAs4Value("65000.1")), + }, + } + + asPathSet2 := config.AsPathSet{ + AsPathSetName: "asset2", + AsPathList: []string{ + fmt.Sprintf("%d_%d$", createAs4Value("65004.1"), createAs4Value("65005.1")), + }, + } + + asPathSet3 := config.AsPathSet{ + AsPathSetName: "asset3", + AsPathList: []string{ + fmt.Sprintf("%d_%d_%d", createAs4Value("65001.1"), createAs4Value("65000.1"), createAs4Value("54000.1")), + }, + } + + asPathSet4 := config.AsPathSet{ + AsPathSetName: "asset4", + AsPathList: []string{ + fmt.Sprintf("%d_%d_%d", createAs4Value("54000.1"), createAs4Value("65004.1"), createAs4Value("65005.1")), + }, + } + + asPathSet5 := config.AsPathSet{ + AsPathSetName: "asset5", + AsPathList: []string{ + fmt.Sprintf("^%d %d %d %d %d$", createAs4Value("65001.1"), createAs4Value("65000.1"), createAs4Value("54000.1"), createAs4Value("65004.1"), createAs4Value("65005.1")), + }, + } + + asPathSet6 := config.AsPathSet{ + AsPathSetName: "asset6", + AsPathList: []string{ + fmt.Sprintf(".*_[0-9]+_%d", createAs4Value("65005.1")), + }, + } + + asPathSet7 := config.AsPathSet{ + AsPathSetName: "asset7", + AsPathList: []string{".*_3[0-9]+_[0-9]+"}, + } + + asPathSet8 := config.AsPathSet{ + AsPathSetName: "asset8", + AsPathList: []string{"4[0-9]+_4[0-9]+_3[0-9]+"}, + } + + asPathSet9 := config.AsPathSet{ + AsPathSetName: "asset9", + AsPathList: []string{"4[0-9]+__4[0-9]+"}, + } + + m := make(map[string]DefinedSet) + for _, s := range []config.AsPathSet{asPathSet1, asPathSet2, asPathSet3, + asPathSet4, asPathSet5, asPathSet6, asPathSet7, asPathSet8, asPathSet9} { + a, _ := NewAsPathSet(s) + m[s.AsPathSetName] = a + } + + createAspathC := func(name string, option config.MatchSetOptionsType) *AsPathCondition { + matchSet := config.MatchAsPathSet{} + matchSet.AsPathSet = name + matchSet.MatchSetOptions = option + p, _ := NewAsPathCondition(matchSet) + if v, ok := m[name]; ok { + p.set = v.(*AsPathSet) + } + return p + } + + p1 := createAspathC("asset1", config.MATCH_SET_OPTIONS_TYPE_ANY) + p2 := createAspathC("asset2", config.MATCH_SET_OPTIONS_TYPE_ANY) + p3 := createAspathC("asset3", config.MATCH_SET_OPTIONS_TYPE_ANY) + p4 := createAspathC("asset4", config.MATCH_SET_OPTIONS_TYPE_ANY) + p5 := createAspathC("asset5", config.MATCH_SET_OPTIONS_TYPE_ANY) + p6 := createAspathC("asset6", config.MATCH_SET_OPTIONS_TYPE_ANY) + p7 := createAspathC("asset7", config.MATCH_SET_OPTIONS_TYPE_ANY) + p8 := createAspathC("asset8", config.MATCH_SET_OPTIONS_TYPE_ANY) + p9 := createAspathC("asset9", config.MATCH_SET_OPTIONS_TYPE_ANY) + + // test + assert.Equal(t, true, p1.Evaluate(path1, nil)) + assert.Equal(t, true, p2.Evaluate(path1, nil)) + assert.Equal(t, true, p3.Evaluate(path1, nil)) + assert.Equal(t, true, p4.Evaluate(path1, nil)) + assert.Equal(t, true, p5.Evaluate(path1, nil)) + assert.Equal(t, true, p6.Evaluate(path1, nil)) + assert.Equal(t, true, p7.Evaluate(path1, nil)) + assert.Equal(t, true, p8.Evaluate(path1, nil)) + assert.Equal(t, false, p9.Evaluate(path1, nil)) +} + +func TestAs4PathConditionWithOtherCondition(t *testing.T) { + + // setup + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{ + bgp.NewAs4PathParam(1, []uint32{ + createAs4Value("65001.1"), + createAs4Value("65000.1"), + createAs4Value("65004.1"), + createAs4Value("65005.1"), + }), + bgp.NewAs4PathParam(2, []uint32{ + createAs4Value("65001.1"), + createAs4Value("65000.1"), + createAs4Value("65004.1"), + createAs4Value("65004.1"), + createAs4Value("65005.1"), + }), + } + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + UpdatePathAttrs4ByteAs(updateMsg.Body.(*bgp.BGPUpdate)) + path := ProcessMessage(updateMsg, peer, time.Now())[0] + + // create policy + asPathSet := config.AsPathSet{ + AsPathSetName: "asset1", + AsPathList: []string{fmt.Sprintf("%d$", createAs4Value("65005.1"))}, + } + + ps := createPrefixSet("ps1", "10.10.1.0/16", "21..24") + ns := createNeighborSet("ns1", "10.0.0.1") + + ds := config.DefinedSets{} + ds.PrefixSets = []config.PrefixSet{ps} + ds.NeighborSets = []config.NeighborSet{ns} + ds.BgpDefinedSets.AsPathSets = []config.AsPathSet{asPathSet} + + s := createStatement("statement1", "ps1", "ns1", false) + s.Conditions.BgpConditions.MatchAsPathSet.AsPathSet = "asset1" + + pd := createPolicyDefinition("pd1", s) + pl := createRoutingPolicy(ds, pd) + + //test + r := NewRoutingPolicy() + r.reload(pl) + p, _ := NewPolicy(pl.PolicyDefinitions[0]) + addPolicy(r, p) + pType, newPath := p.Apply(path, nil) + assert.Equal(t, ROUTE_TYPE_REJECT, pType) + assert.Equal(t, newPath, path) + +} + +func TestAs4PathConditionEvaluateMixedWith2byteAS(t *testing.T) { + + // setup + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam1 := []bgp.AsPathParamInterface{ + bgp.NewAs4PathParam(2, []uint32{ + createAs4Value("65001.1"), + createAs4Value("65000.1"), + createAs4Value("54000.1"), + 100, + 5000, + createAs4Value("65004.1"), + createAs4Value("65005.1"), + 4000, + }), + } + + aspath := bgp.NewPathAttributeAsPath(aspathParam1) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg1 := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + UpdatePathAttrs4ByteAs(updateMsg1.Body.(*bgp.BGPUpdate)) + path1 := ProcessMessage(updateMsg1, peer, time.Now())[0] + + // create match condition + asPathSet1 := config.AsPathSet{ + AsPathSetName: "asset1", + AsPathList: []string{fmt.Sprintf("^%d", createAs4Value("65001.1"))}, + } + + asPathSet2 := config.AsPathSet{ + AsPathSetName: "asset2", + AsPathList: []string{"4000$"}, + } + + asPathSet3 := config.AsPathSet{ + AsPathSetName: "asset3", + AsPathList: []string{fmt.Sprintf("%d", createAs4Value("65004.1")), "4000$"}, + } + + asPathSet4 := config.AsPathSet{ + AsPathSetName: "asset4", + AsPathList: []string{fmt.Sprintf("%d_%d_%d", createAs4Value("54000.1"), 100, 5000)}, + } + + asPathSet5 := config.AsPathSet{ + AsPathSetName: "asset5", + AsPathList: []string{".*_[0-9]+_100"}, + } + + asPathSet6 := config.AsPathSet{ + AsPathSetName: "asset6", + AsPathList: []string{".*_3[0-9]+_[0]+"}, + } + + asPathSet7 := config.AsPathSet{ + AsPathSetName: "asset7", + AsPathList: []string{".*_3[0-9]+_[1]+"}, + } + + m := make(map[string]DefinedSet) + for _, s := range []config.AsPathSet{asPathSet1, asPathSet2, asPathSet3, + asPathSet4, asPathSet5, asPathSet6, asPathSet7} { + a, _ := NewAsPathSet(s) + m[s.AsPathSetName] = a + } + + createAspathC := func(name string, option config.MatchSetOptionsType) *AsPathCondition { + matchSet := config.MatchAsPathSet{} + matchSet.AsPathSet = name + matchSet.MatchSetOptions = option + p, _ := NewAsPathCondition(matchSet) + if v, ok := m[name]; ok { + p.set = v.(*AsPathSet) + } + return p + } + + p1 := createAspathC("asset1", config.MATCH_SET_OPTIONS_TYPE_ANY) + p2 := createAspathC("asset2", config.MATCH_SET_OPTIONS_TYPE_ANY) + p3 := createAspathC("asset3", config.MATCH_SET_OPTIONS_TYPE_ALL) + p4 := createAspathC("asset4", config.MATCH_SET_OPTIONS_TYPE_ANY) + p5 := createAspathC("asset5", config.MATCH_SET_OPTIONS_TYPE_ANY) + p6 := createAspathC("asset6", config.MATCH_SET_OPTIONS_TYPE_ANY) + p7 := createAspathC("asset7", config.MATCH_SET_OPTIONS_TYPE_ANY) + + // test + assert.Equal(t, true, p1.Evaluate(path1, nil)) + assert.Equal(t, true, p2.Evaluate(path1, nil)) + assert.Equal(t, true, p3.Evaluate(path1, nil)) + assert.Equal(t, true, p4.Evaluate(path1, nil)) + assert.Equal(t, true, p5.Evaluate(path1, nil)) + assert.Equal(t, false, p6.Evaluate(path1, nil)) + assert.Equal(t, true, p7.Evaluate(path1, nil)) + +} + +func TestCommunityConditionEvaluate(t *testing.T) { + + // setup + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam1 := []bgp.AsPathParamInterface{ + bgp.NewAsPathParam(2, []uint16{65001, 65000, 65004, 65005}), + bgp.NewAsPathParam(1, []uint16{65001, 65010, 65004, 65005}), + } + aspath := bgp.NewPathAttributeAsPath(aspathParam1) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + communities := bgp.NewPathAttributeCommunities([]uint32{ + stringToCommunityValue("65001:100"), + stringToCommunityValue("65001:200"), + stringToCommunityValue("65001:300"), + stringToCommunityValue("65001:400"), + 0x00000000, + 0xFFFFFF01, + 0xFFFFFF02, + 0xFFFFFF03}) + + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med, communities} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg1 := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + UpdatePathAttrs4ByteAs(updateMsg1.Body.(*bgp.BGPUpdate)) + path1 := ProcessMessage(updateMsg1, peer, time.Now())[0] + + communities2 := bgp.NewPathAttributeCommunities([]uint32{ + stringToCommunityValue("65001:100"), + stringToCommunityValue("65001:200"), + stringToCommunityValue("65001:300"), + stringToCommunityValue("65001:400")}) + + pathAttributes2 := []bgp.PathAttributeInterface{origin, aspath, nexthop, med, communities2} + updateMsg2 := bgp.NewBGPUpdateMessage(nil, pathAttributes2, nlri) + UpdatePathAttrs4ByteAs(updateMsg2.Body.(*bgp.BGPUpdate)) + path2 := ProcessMessage(updateMsg2, peer, time.Now())[0] + + // create match condition + comSet1 := config.CommunitySet{ + CommunitySetName: "comset1", + CommunityList: []string{"65001:10", "65001:50", "65001:100"}, + } + + comSet2 := config.CommunitySet{ + CommunitySetName: "comset2", + CommunityList: []string{"65001:200"}, + } + + comSet3 := config.CommunitySet{ + CommunitySetName: "comset3", + CommunityList: []string{"4259905936"}, + } + + comSet4 := config.CommunitySet{ + CommunitySetName: "comset4", + CommunityList: []string{"^[0-9]*:300$"}, + } + + comSet5 := config.CommunitySet{ + CommunitySetName: "comset5", + CommunityList: []string{"INTERNET"}, + } + + comSet6 := config.CommunitySet{ + CommunitySetName: "comset6", + CommunityList: []string{"NO_EXPORT"}, + } + + comSet7 := config.CommunitySet{ + CommunitySetName: "comset7", + CommunityList: []string{"NO_ADVERTISE"}, + } + + comSet8 := config.CommunitySet{ + CommunitySetName: "comset8", + CommunityList: []string{"NO_EXPORT_SUBCONFED"}, + } + + comSet9 := config.CommunitySet{ + CommunitySetName: "comset9", + CommunityList: []string{ + "65001:\\d+", + "\\d+:\\d00", + }, + } + + comSet10 := config.CommunitySet{ + CommunitySetName: "comset10", + CommunityList: []string{ + "65001:1", + "65001:2", + "65001:3", + }, + } + + m := make(map[string]DefinedSet) + + for _, c := range []config.CommunitySet{comSet1, comSet2, comSet3, + comSet4, comSet5, comSet6, comSet7, comSet8, comSet9, comSet10} { + s, _ := NewCommunitySet(c) + m[c.CommunitySetName] = s + } + + createCommunityC := func(name string, option config.MatchSetOptionsType) *CommunityCondition { + matchSet := config.MatchCommunitySet{} + matchSet.CommunitySet = name + matchSet.MatchSetOptions = option + c, _ := NewCommunityCondition(matchSet) + if v, ok := m[name]; ok { + c.set = v.(*CommunitySet) + } + return c + } + + // ANY case + p1 := createCommunityC("comset1", config.MATCH_SET_OPTIONS_TYPE_ANY) + p2 := createCommunityC("comset2", config.MATCH_SET_OPTIONS_TYPE_ANY) + p3 := createCommunityC("comset3", config.MATCH_SET_OPTIONS_TYPE_ANY) + p4 := createCommunityC("comset4", config.MATCH_SET_OPTIONS_TYPE_ANY) + p5 := createCommunityC("comset5", config.MATCH_SET_OPTIONS_TYPE_ANY) + p6 := createCommunityC("comset6", config.MATCH_SET_OPTIONS_TYPE_ANY) + p7 := createCommunityC("comset7", config.MATCH_SET_OPTIONS_TYPE_ANY) + p8 := createCommunityC("comset8", config.MATCH_SET_OPTIONS_TYPE_ANY) + + // ALL case + p9 := createCommunityC("comset9", config.MATCH_SET_OPTIONS_TYPE_ALL) + + // INVERT case + p10 := createCommunityC("comset10", config.MATCH_SET_OPTIONS_TYPE_INVERT) + + // test + assert.Equal(t, true, p1.Evaluate(path1, nil)) + assert.Equal(t, true, p2.Evaluate(path1, nil)) + assert.Equal(t, true, p3.Evaluate(path1, nil)) + assert.Equal(t, true, p4.Evaluate(path1, nil)) + assert.Equal(t, true, p5.Evaluate(path1, nil)) + assert.Equal(t, true, p6.Evaluate(path1, nil)) + assert.Equal(t, true, p7.Evaluate(path1, nil)) + assert.Equal(t, true, p8.Evaluate(path1, nil)) + assert.Equal(t, true, p9.Evaluate(path2, nil)) + assert.Equal(t, true, p10.Evaluate(path1, nil)) + +} + +func TestCommunityConditionEvaluateWithOtherCondition(t *testing.T) { + + // setup + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{ + bgp.NewAsPathParam(1, []uint16{65001, 65000, 65004, 65005}), + bgp.NewAsPathParam(2, []uint16{65001, 65000, 65004, 65004, 65005}), + } + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + communities := bgp.NewPathAttributeCommunities([]uint32{ + stringToCommunityValue("65001:100"), + stringToCommunityValue("65001:200"), + stringToCommunityValue("65001:300"), + stringToCommunityValue("65001:400"), + 0x00000000, + 0xFFFFFF01, + 0xFFFFFF02, + 0xFFFFFF03}) + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med, communities} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + UpdatePathAttrs4ByteAs(updateMsg.Body.(*bgp.BGPUpdate)) + path := ProcessMessage(updateMsg, peer, time.Now())[0] + + // create policy + asPathSet := config.AsPathSet{ + AsPathSetName: "asset1", + AsPathList: []string{"65005$"}, + } + + comSet1 := config.CommunitySet{ + CommunitySetName: "comset1", + CommunityList: []string{"65001:100", "65001:200", "65001:300"}, + } + + comSet2 := config.CommunitySet{ + CommunitySetName: "comset2", + CommunityList: []string{"65050:\\d+"}, + } + + ps := createPrefixSet("ps1", "10.10.0.0/16", "21..24") + ns := createNeighborSet("ns1", "10.0.0.1") + + ds := config.DefinedSets{} + ds.PrefixSets = []config.PrefixSet{ps} + ds.NeighborSets = []config.NeighborSet{ns} + ds.BgpDefinedSets.AsPathSets = []config.AsPathSet{asPathSet} + ds.BgpDefinedSets.CommunitySets = []config.CommunitySet{comSet1, comSet2} + + s1 := createStatement("statement1", "ps1", "ns1", false) + s1.Conditions.BgpConditions.MatchAsPathSet.AsPathSet = "asset1" + s1.Conditions.BgpConditions.MatchCommunitySet.CommunitySet = "comset1" + + s2 := createStatement("statement2", "ps1", "ns1", false) + s2.Conditions.BgpConditions.MatchAsPathSet.AsPathSet = "asset1" + s2.Conditions.BgpConditions.MatchCommunitySet.CommunitySet = "comset2" + + pd1 := createPolicyDefinition("pd1", s1) + pd2 := createPolicyDefinition("pd2", s2) + pl := createRoutingPolicy(ds, pd1, pd2) + + //test + r := NewRoutingPolicy() + err := r.reload(pl) + assert.Nil(t, err) + p := r.policyMap["pd1"] + pType, newPath := p.Apply(path, nil) + assert.Equal(t, ROUTE_TYPE_REJECT, pType) + assert.Equal(t, newPath, path) + + p = r.policyMap["pd2"] + pType, newPath = p.Apply(path, nil) + assert.Equal(t, ROUTE_TYPE_NONE, pType) + assert.Equal(t, newPath, path) + +} + +func TestPolicyMatchAndAddCommunities(t *testing.T) { + + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65001})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + path := ProcessMessage(updateMsg, peer, time.Now())[0] + // create policy + ps := createPrefixSet("ps1", "10.10.0.0/16", "21..24") + ns := createNeighborSet("ns1", "10.0.0.1") + + ds := config.DefinedSets{} + ds.PrefixSets = []config.PrefixSet{ps} + ds.NeighborSets = []config.NeighborSet{ns} + + community := "65000:100" + + s := createStatement("statement1", "ps1", "ns1", true) + s.Actions.BgpActions.SetCommunity = createSetCommunity("ADD", community) + + pd := createPolicyDefinition("pd1", s) + pl := createRoutingPolicy(ds, pd) + + //test + r := NewRoutingPolicy() + err := r.reload(pl) + assert.Nil(t, err) + p := r.policyMap["pd1"] + + pType, newPath := p.Apply(path, nil) + assert.Equal(t, ROUTE_TYPE_ACCEPT, pType) + assert.NotEqual(t, nil, newPath) + assert.Equal(t, []uint32{stringToCommunityValue(community)}, newPath.GetCommunities()) +} + +func TestPolicyMatchAndReplaceCommunities(t *testing.T) { + + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65001})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + communities := bgp.NewPathAttributeCommunities([]uint32{ + stringToCommunityValue("65001:200"), + }) + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med, communities} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + path := ProcessMessage(updateMsg, peer, time.Now())[0] + // create policy + ps := createPrefixSet("ps1", "10.10.0.0/16", "21..24") + ns := createNeighborSet("ns1", "10.0.0.1") + + ds := config.DefinedSets{} + ds.PrefixSets = []config.PrefixSet{ps} + ds.NeighborSets = []config.NeighborSet{ns} + + community := "65000:100" + + s := createStatement("statement1", "ps1", "ns1", true) + s.Actions.BgpActions.SetCommunity = createSetCommunity("REPLACE", community) + + pd := createPolicyDefinition("pd1", s) + pl := createRoutingPolicy(ds, pd) + + //test + r := NewRoutingPolicy() + err := r.reload(pl) + assert.Nil(t, err) + p := r.policyMap["pd1"] + + pType, newPath := p.Apply(path, nil) + assert.Equal(t, ROUTE_TYPE_ACCEPT, pType) + assert.NotEqual(t, nil, newPath) + assert.Equal(t, []uint32{stringToCommunityValue(community)}, newPath.GetCommunities()) +} + +func TestPolicyMatchAndRemoveCommunities(t *testing.T) { + + // create path + community1 := "65000:100" + community2 := "65000:200" + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65001})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + communities := bgp.NewPathAttributeCommunities([]uint32{ + stringToCommunityValue(community1), + stringToCommunityValue(community2), + }) + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med, communities} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + path := ProcessMessage(updateMsg, peer, time.Now())[0] + // create policy + ps := createPrefixSet("ps1", "10.10.0.0/16", "21..24") + ns := createNeighborSet("ns1", "10.0.0.1") + + ds := config.DefinedSets{} + ds.PrefixSets = []config.PrefixSet{ps} + ds.NeighborSets = []config.NeighborSet{ns} + + s := createStatement("statement1", "ps1", "ns1", true) + s.Actions.BgpActions.SetCommunity = createSetCommunity("REMOVE", community1) + + pd := createPolicyDefinition("pd1", s) + pl := createRoutingPolicy(ds, pd) + + //test + r := NewRoutingPolicy() + err := r.reload(pl) + assert.Nil(t, err) + p := r.policyMap["pd1"] + pType, newPath := p.Apply(path, nil) + assert.Equal(t, ROUTE_TYPE_ACCEPT, pType) + assert.NotEqual(t, nil, newPath) + assert.Equal(t, []uint32{stringToCommunityValue(community2)}, newPath.GetCommunities()) +} + +func TestPolicyMatchAndRemoveCommunitiesRegexp(t *testing.T) { + + // create path + community1 := "65000:100" + community2 := "65000:200" + community3 := "65100:100" + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65001})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + communities := bgp.NewPathAttributeCommunities([]uint32{ + stringToCommunityValue(community1), + stringToCommunityValue(community2), + stringToCommunityValue(community3), + }) + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med, communities} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + path := ProcessMessage(updateMsg, peer, time.Now())[0] + // create policy + ps := createPrefixSet("ps1", "10.10.0.0/16", "21..24") + ns := createNeighborSet("ns1", "10.0.0.1") + + ds := config.DefinedSets{} + ds.PrefixSets = []config.PrefixSet{ps} + ds.NeighborSets = []config.NeighborSet{ns} + + s := createStatement("statement1", "ps1", "ns1", true) + s.Actions.BgpActions.SetCommunity = createSetCommunity("REMOVE", ".*:100") + + pd := createPolicyDefinition("pd1", s) + pl := createRoutingPolicy(ds, pd) + + //test + r := NewRoutingPolicy() + err := r.reload(pl) + assert.Nil(t, err) + p := r.policyMap["pd1"] + pType, newPath := p.Apply(path, nil) + assert.Equal(t, ROUTE_TYPE_ACCEPT, pType) + assert.NotEqual(t, nil, newPath) + assert.Equal(t, []uint32{stringToCommunityValue(community2)}, newPath.GetCommunities()) +} + +func TestPolicyMatchAndRemoveCommunitiesRegexp2(t *testing.T) { + + // create path + community1 := "0:1" + community2 := "10:1" + community3 := "45686:2" + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65001})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + communities := bgp.NewPathAttributeCommunities([]uint32{ + stringToCommunityValue(community1), + stringToCommunityValue(community2), + stringToCommunityValue(community3), + }) + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med, communities} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + path := ProcessMessage(updateMsg, peer, time.Now())[0] + // create policy + ps := createPrefixSet("ps1", "10.10.0.0/16", "21..24") + ns := createNeighborSet("ns1", "10.0.0.1") + + ds := config.DefinedSets{} + ds.PrefixSets = []config.PrefixSet{ps} + ds.NeighborSets = []config.NeighborSet{ns} + + s := createStatement("statement1", "ps1", "ns1", true) + s.Actions.BgpActions.SetCommunity = createSetCommunity("REMOVE", "^(0|45686):[0-9]+") + + pd := createPolicyDefinition("pd1", s) + pl := createRoutingPolicy(ds, pd) + + //test + r := NewRoutingPolicy() + err := r.reload(pl) + assert.Nil(t, err) + p := r.policyMap["pd1"] + pType, newPath := p.Apply(path, nil) + assert.Equal(t, ROUTE_TYPE_ACCEPT, pType) + assert.NotEqual(t, nil, newPath) + assert.Equal(t, []uint32{stringToCommunityValue(community2)}, newPath.GetCommunities()) +} + +func TestPolicyMatchAndClearCommunities(t *testing.T) { + + // create path + community1 := "65000:100" + community2 := "65000:200" + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65001})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + communities := bgp.NewPathAttributeCommunities([]uint32{ + stringToCommunityValue(community1), + stringToCommunityValue(community2), + }) + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med, communities} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + path := ProcessMessage(updateMsg, peer, time.Now())[0] + // create policy + ps := createPrefixSet("ps1", "10.10.0.0/16", "21..24") + ns := createNeighborSet("ns1", "10.0.0.1") + + ds := config.DefinedSets{} + ds.PrefixSets = []config.PrefixSet{ps} + ds.NeighborSets = []config.NeighborSet{ns} + + s := createStatement("statement1", "ps1", "ns1", true) + // action NULL is obsolate + s.Actions.BgpActions.SetCommunity.Options = "REPLACE" + s.Actions.BgpActions.SetCommunity.SetCommunityMethod.CommunitiesList = nil + + pd := createPolicyDefinition("pd1", s) + pl := createRoutingPolicy(ds, pd) + + //test + r := NewRoutingPolicy() + err := r.reload(pl) + assert.Nil(t, err) + p := r.policyMap["pd1"] + + pType, newPath := p.Apply(path, nil) + assert.Equal(t, ROUTE_TYPE_ACCEPT, pType) + assert.NotEqual(t, nil, newPath) + //assert.Equal(t, []uint32{}, newPath.GetCommunities()) +} + +func TestExtCommunityConditionEvaluate(t *testing.T) { + + // setup + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam1 := []bgp.AsPathParamInterface{ + bgp.NewAsPathParam(2, []uint16{65001, 65000, 65004, 65005}), + bgp.NewAsPathParam(1, []uint16{65001, 65010, 65004, 65005}), + } + aspath := bgp.NewPathAttributeAsPath(aspathParam1) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + eComAsSpecific1 := &bgp.TwoOctetAsSpecificExtended{ + SubType: bgp.ExtendedCommunityAttrSubType(bgp.EC_SUBTYPE_ROUTE_TARGET), + AS: 65001, + LocalAdmin: 200, + IsTransitive: true, + } + eComIpPrefix1 := &bgp.IPv4AddressSpecificExtended{ + SubType: bgp.ExtendedCommunityAttrSubType(bgp.EC_SUBTYPE_ROUTE_TARGET), + IPv4: net.ParseIP("10.0.0.1"), + LocalAdmin: 300, + IsTransitive: true, + } + eComAs4Specific1 := &bgp.FourOctetAsSpecificExtended{ + SubType: bgp.ExtendedCommunityAttrSubType(bgp.EC_SUBTYPE_ROUTE_TARGET), + AS: 65030000, + LocalAdmin: 200, + IsTransitive: true, + } + eComAsSpecific2 := &bgp.TwoOctetAsSpecificExtended{ + SubType: bgp.ExtendedCommunityAttrSubType(bgp.EC_SUBTYPE_ROUTE_TARGET), + AS: 65002, + LocalAdmin: 200, + IsTransitive: false, + } + eComIpPrefix2 := &bgp.IPv4AddressSpecificExtended{ + SubType: bgp.ExtendedCommunityAttrSubType(bgp.EC_SUBTYPE_ROUTE_TARGET), + IPv4: net.ParseIP("10.0.0.2"), + LocalAdmin: 300, + IsTransitive: false, + } + eComAs4Specific2 := &bgp.FourOctetAsSpecificExtended{ + SubType: bgp.ExtendedCommunityAttrSubType(bgp.EC_SUBTYPE_ROUTE_TARGET), + AS: 65030001, + LocalAdmin: 200, + IsTransitive: false, + } + eComAsSpecific3 := &bgp.TwoOctetAsSpecificExtended{ + SubType: bgp.ExtendedCommunityAttrSubType(bgp.EC_SUBTYPE_ROUTE_ORIGIN), + AS: 65010, + LocalAdmin: 300, + IsTransitive: true, + } + eComIpPrefix3 := &bgp.IPv4AddressSpecificExtended{ + SubType: bgp.ExtendedCommunityAttrSubType(bgp.EC_SUBTYPE_ROUTE_ORIGIN), + IPv4: net.ParseIP("10.0.10.10"), + LocalAdmin: 400, + IsTransitive: true, + } + eComAs4Specific3 := &bgp.FourOctetAsSpecificExtended{ + SubType: bgp.ExtendedCommunityAttrSubType(bgp.EC_SUBTYPE_ROUTE_TARGET), + AS: 65030002, + LocalAdmin: 500, + IsTransitive: true, + } + ec := []bgp.ExtendedCommunityInterface{eComAsSpecific1, eComIpPrefix1, eComAs4Specific1, eComAsSpecific2, + eComIpPrefix2, eComAs4Specific2, eComAsSpecific3, eComIpPrefix3, eComAs4Specific3} + extCommunities := bgp.NewPathAttributeExtendedCommunities(ec) + + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med, extCommunities} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg1 := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + UpdatePathAttrs4ByteAs(updateMsg1.Body.(*bgp.BGPUpdate)) + path1 := ProcessMessage(updateMsg1, peer, time.Now())[0] + + convUintStr := func(as uint32) string { + upper := strconv.FormatUint(uint64(as&0xFFFF0000>>16), 10) + lower := strconv.FormatUint(uint64(as&0x0000FFFF), 10) + str := fmt.Sprintf("%s.%s", upper, lower) + return str + } + + // create match condition + ecomSet1 := config.ExtCommunitySet{ + ExtCommunitySetName: "ecomSet1", + ExtCommunityList: []string{"RT:65001:200"}, + } + ecomSet2 := config.ExtCommunitySet{ + ExtCommunitySetName: "ecomSet2", + ExtCommunityList: []string{"RT:10.0.0.1:300"}, + } + ecomSet3 := config.ExtCommunitySet{ + ExtCommunitySetName: "ecomSet3", + ExtCommunityList: []string{fmt.Sprintf("RT:%s:200", convUintStr(65030000))}, + } + ecomSet4 := config.ExtCommunitySet{ + ExtCommunitySetName: "ecomSet4", + ExtCommunityList: []string{"RT:65002:200"}, + } + ecomSet5 := config.ExtCommunitySet{ + ExtCommunitySetName: "ecomSet5", + ExtCommunityList: []string{"RT:10.0.0.2:300"}, + } + ecomSet6 := config.ExtCommunitySet{ + ExtCommunitySetName: "ecomSet6", + ExtCommunityList: []string{fmt.Sprintf("RT:%s:200", convUintStr(65030001))}, + } + ecomSet7 := config.ExtCommunitySet{ + ExtCommunitySetName: "ecomSet7", + ExtCommunityList: []string{"SoO:65010:300"}, + } + ecomSet8 := config.ExtCommunitySet{ + ExtCommunitySetName: "ecomSet8", + ExtCommunityList: []string{"SoO:10.0.10.10:[0-9]+"}, + } + ecomSet9 := config.ExtCommunitySet{ + ExtCommunitySetName: "ecomSet9", + ExtCommunityList: []string{"RT:[0-9]+:[0-9]+"}, + } + ecomSet10 := config.ExtCommunitySet{ + ExtCommunitySetName: "ecomSet10", + ExtCommunityList: []string{"RT:.+:\\d00", "SoO:.+:\\d00"}, + } + + ecomSet11 := config.ExtCommunitySet{ + ExtCommunitySetName: "ecomSet11", + ExtCommunityList: []string{"RT:65001:2", "SoO:11.0.10.10:[0-9]+"}, + } + + m := make(map[string]DefinedSet) + for _, c := range []config.ExtCommunitySet{ecomSet1, ecomSet2, ecomSet3, ecomSet4, ecomSet5, ecomSet6, ecomSet7, + ecomSet8, ecomSet9, ecomSet10, ecomSet11} { + s, _ := NewExtCommunitySet(c) + m[s.Name()] = s + } + + createExtCommunityC := func(name string, option config.MatchSetOptionsType) *ExtCommunityCondition { + matchSet := config.MatchExtCommunitySet{} + matchSet.ExtCommunitySet = name + matchSet.MatchSetOptions = option + c, _ := NewExtCommunityCondition(matchSet) + if v, ok := m[name]; ok { + c.set = v.(*ExtCommunitySet) + } + + return c + } + + p1 := createExtCommunityC("ecomSet1", config.MATCH_SET_OPTIONS_TYPE_ANY) + p2 := createExtCommunityC("ecomSet2", config.MATCH_SET_OPTIONS_TYPE_ANY) + p3 := createExtCommunityC("ecomSet3", config.MATCH_SET_OPTIONS_TYPE_ANY) + p4 := createExtCommunityC("ecomSet4", config.MATCH_SET_OPTIONS_TYPE_ANY) + p5 := createExtCommunityC("ecomSet5", config.MATCH_SET_OPTIONS_TYPE_ANY) + p6 := createExtCommunityC("ecomSet6", config.MATCH_SET_OPTIONS_TYPE_ANY) + p7 := createExtCommunityC("ecomSet7", config.MATCH_SET_OPTIONS_TYPE_ANY) + p8 := createExtCommunityC("ecomSet8", config.MATCH_SET_OPTIONS_TYPE_ANY) + p9 := createExtCommunityC("ecomSet9", config.MATCH_SET_OPTIONS_TYPE_ANY) + + // ALL case + p10 := createExtCommunityC("ecomSet10", config.MATCH_SET_OPTIONS_TYPE_ALL) + + // INVERT case + p11 := createExtCommunityC("ecomSet11", config.MATCH_SET_OPTIONS_TYPE_INVERT) + + // test + assert.Equal(t, true, p1.Evaluate(path1, nil)) + assert.Equal(t, true, p2.Evaluate(path1, nil)) + assert.Equal(t, true, p3.Evaluate(path1, nil)) + assert.Equal(t, false, p4.Evaluate(path1, nil)) + assert.Equal(t, false, p5.Evaluate(path1, nil)) + assert.Equal(t, false, p6.Evaluate(path1, nil)) + assert.Equal(t, true, p7.Evaluate(path1, nil)) + assert.Equal(t, true, p8.Evaluate(path1, nil)) + assert.Equal(t, true, p9.Evaluate(path1, nil)) + assert.Equal(t, true, p10.Evaluate(path1, nil)) + assert.Equal(t, true, p11.Evaluate(path1, nil)) + +} + +func TestExtCommunityConditionEvaluateWithOtherCondition(t *testing.T) { + + // setup + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.2.1.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{ + bgp.NewAsPathParam(1, []uint16{65001, 65000, 65004, 65005}), + bgp.NewAsPathParam(2, []uint16{65001, 65000, 65004, 65004, 65005}), + } + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.2.1.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + eComAsSpecific1 := &bgp.TwoOctetAsSpecificExtended{ + SubType: bgp.ExtendedCommunityAttrSubType(bgp.EC_SUBTYPE_ROUTE_TARGET), + AS: 65001, + LocalAdmin: 200, + IsTransitive: true, + } + eComIpPrefix1 := &bgp.IPv4AddressSpecificExtended{ + SubType: bgp.ExtendedCommunityAttrSubType(bgp.EC_SUBTYPE_ROUTE_TARGET), + IPv4: net.ParseIP("10.0.0.1"), + LocalAdmin: 300, + IsTransitive: true, + } + eComAs4Specific1 := &bgp.FourOctetAsSpecificExtended{ + SubType: bgp.ExtendedCommunityAttrSubType(bgp.EC_SUBTYPE_ROUTE_TARGET), + AS: 65030000, + LocalAdmin: 200, + IsTransitive: true, + } + eComAsSpecific2 := &bgp.TwoOctetAsSpecificExtended{ + SubType: bgp.ExtendedCommunityAttrSubType(bgp.EC_SUBTYPE_ROUTE_TARGET), + AS: 65002, + LocalAdmin: 200, + IsTransitive: false, + } + eComIpPrefix2 := &bgp.IPv4AddressSpecificExtended{ + SubType: bgp.ExtendedCommunityAttrSubType(bgp.EC_SUBTYPE_ROUTE_TARGET), + IPv4: net.ParseIP("10.0.0.2"), + LocalAdmin: 300, + IsTransitive: false, + } + eComAs4Specific2 := &bgp.FourOctetAsSpecificExtended{ + SubType: bgp.ExtendedCommunityAttrSubType(bgp.EC_SUBTYPE_ROUTE_TARGET), + AS: 65030001, + LocalAdmin: 200, + IsTransitive: false, + } + eComAsSpecific3 := &bgp.TwoOctetAsSpecificExtended{ + SubType: bgp.ExtendedCommunityAttrSubType(bgp.EC_SUBTYPE_ROUTE_ORIGIN), + AS: 65010, + LocalAdmin: 300, + IsTransitive: true, + } + eComIpPrefix3 := &bgp.IPv4AddressSpecificExtended{ + SubType: bgp.ExtendedCommunityAttrSubType(bgp.EC_SUBTYPE_ROUTE_ORIGIN), + IPv4: net.ParseIP("10.0.10.10"), + LocalAdmin: 400, + IsTransitive: true, + } + eComAs4Specific3 := &bgp.FourOctetAsSpecificExtended{ + SubType: bgp.ExtendedCommunityAttrSubType(bgp.EC_SUBTYPE_ROUTE_TARGET), + AS: 65030002, + LocalAdmin: 500, + IsTransitive: true, + } + ec := []bgp.ExtendedCommunityInterface{eComAsSpecific1, eComIpPrefix1, eComAs4Specific1, eComAsSpecific2, + eComIpPrefix2, eComAs4Specific2, eComAsSpecific3, eComIpPrefix3, eComAs4Specific3} + extCommunities := bgp.NewPathAttributeExtendedCommunities(ec) + + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med, extCommunities} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + UpdatePathAttrs4ByteAs(updateMsg.Body.(*bgp.BGPUpdate)) + path := ProcessMessage(updateMsg, peer, time.Now())[0] + + // create policy + asPathSet := config.AsPathSet{ + AsPathSetName: "asset1", + AsPathList: []string{"65005$"}, + } + + ecomSet1 := config.ExtCommunitySet{ + ExtCommunitySetName: "ecomSet1", + ExtCommunityList: []string{"RT:65001:201"}, + } + ecomSet2 := config.ExtCommunitySet{ + ExtCommunitySetName: "ecomSet2", + ExtCommunityList: []string{"RT:[0-9]+:[0-9]+"}, + } + + ps := createPrefixSet("ps1", "10.10.1.0/16", "21..24") + ns := createNeighborSet("ns1", "10.2.1.1") + + ds := config.DefinedSets{} + ds.PrefixSets = []config.PrefixSet{ps} + ds.NeighborSets = []config.NeighborSet{ns} + ds.BgpDefinedSets.AsPathSets = []config.AsPathSet{asPathSet} + ds.BgpDefinedSets.ExtCommunitySets = []config.ExtCommunitySet{ecomSet1, ecomSet2} + + s1 := createStatement("statement1", "ps1", "ns1", false) + s1.Conditions.BgpConditions.MatchAsPathSet.AsPathSet = "asset1" + s1.Conditions.BgpConditions.MatchExtCommunitySet.ExtCommunitySet = "ecomSet1" + + s2 := createStatement("statement2", "ps1", "ns1", false) + s2.Conditions.BgpConditions.MatchAsPathSet.AsPathSet = "asset1" + s2.Conditions.BgpConditions.MatchExtCommunitySet.ExtCommunitySet = "ecomSet2" + + pd1 := createPolicyDefinition("pd1", s1) + pd2 := createPolicyDefinition("pd2", s2) + pl := createRoutingPolicy(ds, pd1, pd2) + //test + r := NewRoutingPolicy() + err := r.reload(pl) + assert.Nil(t, err) + p := r.policyMap["pd1"] + pType, newPath := p.Apply(path, nil) + assert.Equal(t, ROUTE_TYPE_NONE, pType) + assert.Equal(t, newPath, path) + + p = r.policyMap["pd2"] + pType, newPath = p.Apply(path, nil) + assert.Equal(t, ROUTE_TYPE_REJECT, pType) + assert.Equal(t, newPath, path) + +} + +func TestPolicyMatchAndReplaceMed(t *testing.T) { + + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65001})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(100) + + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + path := ProcessMessage(updateMsg, peer, time.Now())[0] + // create policy + ps := createPrefixSet("ps1", "10.10.0.0/16", "21..24") + ns := createNeighborSet("ns1", "10.0.0.1") + + ds := config.DefinedSets{} + ds.PrefixSets = []config.PrefixSet{ps} + ds.NeighborSets = []config.NeighborSet{ns} + + m := "200" + s := createStatement("statement1", "ps1", "ns1", true) + s.Actions.BgpActions.SetMed = config.BgpSetMedType(m) + + pd := createPolicyDefinition("pd1", s) + pl := createRoutingPolicy(ds, pd) + + //test + r := NewRoutingPolicy() + err := r.reload(pl) + assert.Nil(t, err) + p := r.policyMap["pd1"] + + pType, newPath := p.Apply(path, nil) + assert.Equal(t, ROUTE_TYPE_ACCEPT, pType) + assert.NotEqual(t, nil, newPath) + v, err := newPath.GetMed() + assert.Nil(t, err) + newMed := fmt.Sprintf("%d", v) + assert.Equal(t, m, newMed) +} + +func TestPolicyMatchAndAddingMed(t *testing.T) { + + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65001})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(100) + + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + path := ProcessMessage(updateMsg, peer, time.Now())[0] + // create policy + ps := createPrefixSet("ps1", "10.10.0.0/16", "21..24") + ns := createNeighborSet("ns1", "10.0.0.1") + + ds := config.DefinedSets{} + ds.PrefixSets = []config.PrefixSet{ps} + ds.NeighborSets = []config.NeighborSet{ns} + + m := "+200" + ma := "300" + s := createStatement("statement1", "ps1", "ns1", true) + s.Actions.BgpActions.SetMed = config.BgpSetMedType(m) + + pd := createPolicyDefinition("pd1", s) + pl := createRoutingPolicy(ds, pd) + //test + r := NewRoutingPolicy() + err := r.reload(pl) + assert.Nil(t, err) + p := r.policyMap["pd1"] + pType, newPath := p.Apply(path, nil) + assert.Equal(t, ROUTE_TYPE_ACCEPT, pType) + assert.NotEqual(t, nil, newPath) + + v, err := newPath.GetMed() + assert.Nil(t, err) + newMed := fmt.Sprintf("%d", v) + assert.Equal(t, ma, newMed) +} + +func TestPolicyMatchAndAddingMedOverFlow(t *testing.T) { + + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65001})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(1) + + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + path := ProcessMessage(updateMsg, peer, time.Now())[0] + // create policy + ps := createPrefixSet("ps1", "10.10.0.0/16", "21..24") + ns := createNeighborSet("ns1", "10.0.0.1") + + ds := config.DefinedSets{} + ds.PrefixSets = []config.PrefixSet{ps} + ds.NeighborSets = []config.NeighborSet{ns} + + m := fmt.Sprintf("+%d", uint32(math.MaxUint32)) + ma := "1" + + s := createStatement("statement1", "ps1", "ns1", true) + s.Actions.BgpActions.SetMed = config.BgpSetMedType(m) + + pd := createPolicyDefinition("pd1", s) + pl := createRoutingPolicy(ds, pd) + //test + r := NewRoutingPolicy() + err := r.reload(pl) + assert.Nil(t, err) + p := r.policyMap["pd1"] + + pType, newPath := p.Apply(path, nil) + assert.Equal(t, ROUTE_TYPE_ACCEPT, pType) + assert.NotEqual(t, nil, newPath) + + v, err := newPath.GetMed() + assert.Nil(t, err) + newMed := fmt.Sprintf("%d", v) + assert.Equal(t, ma, newMed) +} + +func TestPolicyMatchAndSubtractMed(t *testing.T) { + + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65001})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(100) + + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + path := ProcessMessage(updateMsg, peer, time.Now())[0] + // create policy + ps := createPrefixSet("ps1", "10.10.0.0/16", "21..24") + ns := createNeighborSet("ns1", "10.0.0.1") + + ds := config.DefinedSets{} + ds.PrefixSets = []config.PrefixSet{ps} + ds.NeighborSets = []config.NeighborSet{ns} + + m := "-50" + ma := "50" + + s := createStatement("statement1", "ps1", "ns1", true) + s.Actions.BgpActions.SetMed = config.BgpSetMedType(m) + + pd := createPolicyDefinition("pd1", s) + pl := createRoutingPolicy(ds, pd) + //test + r := NewRoutingPolicy() + err := r.reload(pl) + assert.Nil(t, err) + p := r.policyMap["pd1"] + + pType, newPath := p.Apply(path, nil) + assert.Equal(t, ROUTE_TYPE_ACCEPT, pType) + assert.NotEqual(t, nil, newPath) + + v, err := newPath.GetMed() + assert.Nil(t, err) + newMed := fmt.Sprintf("%d", v) + assert.Equal(t, ma, newMed) +} + +func TestPolicyMatchAndSubtractMedUnderFlow(t *testing.T) { + + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65001})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(100) + + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + path := ProcessMessage(updateMsg, peer, time.Now())[0] + // create policy + ps := createPrefixSet("ps1", "10.10.0.0/16", "21..24") + ns := createNeighborSet("ns1", "10.0.0.1") + + ds := config.DefinedSets{} + ds.PrefixSets = []config.PrefixSet{ps} + ds.NeighborSets = []config.NeighborSet{ns} + + m := "-101" + ma := "100" + + s := createStatement("statement1", "ps1", "ns1", true) + s.Actions.BgpActions.SetMed = config.BgpSetMedType(m) + + pd := createPolicyDefinition("pd1", s) + pl := createRoutingPolicy(ds, pd) + //test + r := NewRoutingPolicy() + err := r.reload(pl) + assert.Nil(t, err) + p := r.policyMap["pd1"] + + pType, newPath := p.Apply(path, nil) + assert.Equal(t, ROUTE_TYPE_ACCEPT, pType) + assert.NotEqual(t, nil, newPath) + + v, err := newPath.GetMed() + assert.Nil(t, err) + newMed := fmt.Sprintf("%d", v) + assert.Equal(t, ma, newMed) +} + +func TestPolicyMatchWhenPathHaveNotMed(t *testing.T) { + + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65001})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + path := ProcessMessage(updateMsg, peer, time.Now())[0] + // create policy + ps := createPrefixSet("ps1", "10.10.0.0/16", "21..24") + ns := createNeighborSet("ns1", "10.0.0.1") + + ds := config.DefinedSets{} + ds.PrefixSets = []config.PrefixSet{ps} + ds.NeighborSets = []config.NeighborSet{ns} + + m := "-50" + s := createStatement("statement1", "ps1", "ns1", true) + s.Actions.BgpActions.SetMed = config.BgpSetMedType(m) + + pd := createPolicyDefinition("pd1", s) + pl := createRoutingPolicy(ds, pd) + //test + r := NewRoutingPolicy() + err := r.reload(pl) + assert.Nil(t, err) + p := r.policyMap["pd1"] + + pType, newPath := p.Apply(path, nil) + assert.Equal(t, ROUTE_TYPE_ACCEPT, pType) + assert.NotEqual(t, nil, newPath) + + _, err = newPath.GetMed() + assert.NotNil(t, err) +} + +func TestPolicyAsPathPrepend(t *testing.T) { + + assert := assert.New(t) + + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65001, 65000})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + + body := updateMsg.Body.(*bgp.BGPUpdate) + UpdatePathAttrs4ByteAs(body) + path := ProcessMessage(updateMsg, peer, time.Now())[0] + + // create policy + ps := createPrefixSet("ps1", "10.10.0.0/16", "21..24") + ns := createNeighborSet("ns1", "10.0.0.1") + + ds := config.DefinedSets{} + ds.PrefixSets = []config.PrefixSet{ps} + ds.NeighborSets = []config.NeighborSet{ns} + + s := createStatement("statement1", "ps1", "ns1", true) + s.Actions.BgpActions.SetAsPathPrepend.As = "65002" + s.Actions.BgpActions.SetAsPathPrepend.RepeatN = 10 + + pd := createPolicyDefinition("pd1", s) + pl := createRoutingPolicy(ds, pd) + //test + r := NewRoutingPolicy() + r.reload(pl) + p := r.policyMap["pd1"] + + pType, newPath := p.Apply(path, nil) + assert.Equal(ROUTE_TYPE_ACCEPT, pType) + assert.NotEqual(nil, newPath) + assert.Equal([]uint32{65002, 65002, 65002, 65002, 65002, 65002, 65002, 65002, 65002, 65002, 65001, 65000}, newPath.GetAsSeqList()) +} + +func TestPolicyAsPathPrependLastAs(t *testing.T) { + + assert := assert.New(t) + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65002, 65001, 65000})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + + body := updateMsg.Body.(*bgp.BGPUpdate) + UpdatePathAttrs4ByteAs(body) + path := ProcessMessage(updateMsg, peer, time.Now())[0] + + // create policy + ps := createPrefixSet("ps1", "10.10.0.0/16", "21..24") + ns := createNeighborSet("ns1", "10.0.0.1") + + ds := config.DefinedSets{} + ds.PrefixSets = []config.PrefixSet{ps} + ds.NeighborSets = []config.NeighborSet{ns} + + s := createStatement("statement1", "ps1", "ns1", true) + s.Actions.BgpActions.SetAsPathPrepend.As = "last-as" + s.Actions.BgpActions.SetAsPathPrepend.RepeatN = 5 + + pd := createPolicyDefinition("pd1", s) + pl := createRoutingPolicy(ds, pd) + //test + r := NewRoutingPolicy() + r.reload(pl) + p := r.policyMap["pd1"] + + pType, newPath := p.Apply(path, nil) + assert.Equal(ROUTE_TYPE_ACCEPT, pType) + assert.NotEqual(nil, newPath) + assert.Equal([]uint32{65002, 65002, 65002, 65002, 65002, 65002, 65001, 65000}, newPath.GetAsSeqList()) +} + +func TestPolicyAs4PathPrepend(t *testing.T) { + + assert := assert.New(t) + + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{ + bgp.NewAs4PathParam(2, []uint32{ + createAs4Value("65001.1"), + createAs4Value("65000.1"), + }), + } + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + + body := updateMsg.Body.(*bgp.BGPUpdate) + UpdatePathAttrs4ByteAs(body) + path := ProcessMessage(updateMsg, peer, time.Now())[0] + + // create policy + ps := createPrefixSet("ps1", "10.10.0.0/16", "21..24") + ns := createNeighborSet("ns1", "10.0.0.1") + + ds := config.DefinedSets{} + ds.PrefixSets = []config.PrefixSet{ps} + ds.NeighborSets = []config.NeighborSet{ns} + + s := createStatement("statement1", "ps1", "ns1", true) + s.Actions.BgpActions.SetAsPathPrepend.As = fmt.Sprintf("%d", createAs4Value("65002.1")) + s.Actions.BgpActions.SetAsPathPrepend.RepeatN = 10 + + pd := createPolicyDefinition("pd1", s) + pl := createRoutingPolicy(ds, pd) + //test + r := NewRoutingPolicy() + r.reload(pl) + p, err := NewPolicy(pl.PolicyDefinitions[0]) + assert.Nil(err) + addPolicy(r, p) + + pType, newPath := p.Apply(path, nil) + assert.Equal(ROUTE_TYPE_ACCEPT, pType) + assert.NotEqual(nil, newPath) + asn := createAs4Value("65002.1") + assert.Equal([]uint32{ + asn, asn, asn, asn, asn, asn, asn, asn, asn, asn, + createAs4Value("65001.1"), + createAs4Value("65000.1"), + }, newPath.GetAsSeqList()) +} + +func TestPolicyAs4PathPrependLastAs(t *testing.T) { + + assert := assert.New(t) + // create path + peer := &PeerInfo{AS: 65001, Address: net.ParseIP("10.0.0.1")} + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{ + bgp.NewAs4PathParam(2, []uint32{ + createAs4Value("65002.1"), + createAs4Value("65001.1"), + createAs4Value("65000.1"), + }), + } + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + + pathAttributes := []bgp.PathAttributeInterface{origin, aspath, nexthop, med} + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.0.101")} + updateMsg := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + + body := updateMsg.Body.(*bgp.BGPUpdate) + UpdatePathAttrs4ByteAs(body) + path := ProcessMessage(updateMsg, peer, time.Now())[0] + + // create policy + ps := createPrefixSet("ps1", "10.10.0.0/16", "21..24") + ns := createNeighborSet("ns1", "10.0.0.1") + + ds := config.DefinedSets{} + ds.PrefixSets = []config.PrefixSet{ps} + ds.NeighborSets = []config.NeighborSet{ns} + + s := createStatement("statement1", "ps1", "ns1", true) + s.Actions.BgpActions.SetAsPathPrepend.As = "last-as" + s.Actions.BgpActions.SetAsPathPrepend.RepeatN = 5 + + pd := createPolicyDefinition("pd1", s) + pl := createRoutingPolicy(ds, pd) + //test + r := NewRoutingPolicy() + r.reload(pl) + p, _ := NewPolicy(pl.PolicyDefinitions[0]) + addPolicy(r, p) + + pType, newPath := p.Apply(path, nil) + assert.Equal(ROUTE_TYPE_ACCEPT, pType) + assert.NotEqual(nil, newPath) + asn := createAs4Value("65002.1") + assert.Equal([]uint32{ + asn, asn, asn, asn, asn, + createAs4Value("65002.1"), + createAs4Value("65001.1"), + createAs4Value("65000.1"), + }, newPath.GetAsSeqList()) +} + +func TestParseCommunityRegexp(t *testing.T) { + exp, err := ParseCommunityRegexp("65000:1") + assert.Equal(t, nil, err) + assert.Equal(t, true, exp.MatchString("65000:1")) + assert.Equal(t, false, exp.MatchString("65000:100")) +} + +func TestLocalPrefAction(t *testing.T) { + action, err := NewLocalPrefAction(10) + assert.Nil(t, err) + + nlri := bgp.NewIPAddrPrefix(24, "10.0.0.0") + + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{ + bgp.NewAs4PathParam(2, []uint32{ + createAs4Value("65002.1"), + createAs4Value("65001.1"), + createAs4Value("65000.1"), + }), + } + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + + attrs := []bgp.PathAttributeInterface{origin, aspath, nexthop, med} + + path := NewPath(nil, nlri, false, attrs, time.Now(), false) + p := action.Apply(path, nil) + assert.NotNil(t, p) + + attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_LOCAL_PREF) + assert.NotNil(t, attr) + lp := attr.(*bgp.PathAttributeLocalPref) + assert.Equal(t, int(lp.Value), int(10)) +} + +func createStatement(name, psname, nsname string, accept bool) config.Statement { + c := config.Conditions{ + MatchPrefixSet: config.MatchPrefixSet{ + PrefixSet: psname, + }, + MatchNeighborSet: config.MatchNeighborSet{ + NeighborSet: nsname, + }, + } + rd := config.ROUTE_DISPOSITION_REJECT_ROUTE + if accept { + rd = config.ROUTE_DISPOSITION_ACCEPT_ROUTE + } + a := config.Actions{ + RouteDisposition: rd, + } + s := config.Statement{ + Name: name, + Conditions: c, + Actions: a, + } + return s +} + +func createSetCommunity(operation string, community ...string) config.SetCommunity { + + s := config.SetCommunity{ + SetCommunityMethod: config.SetCommunityMethod{ + CommunitiesList: community, + }, + Options: operation, + } + return s +} + +func stringToCommunityValue(comStr string) uint32 { + elem := strings.Split(comStr, ":") + asn, _ := strconv.ParseUint(elem[0], 10, 16) + val, _ := strconv.ParseUint(elem[1], 10, 16) + return uint32(asn<<16 | val) +} + +func createPolicyDefinition(defName string, stmt ...config.Statement) config.PolicyDefinition { + pd := config.PolicyDefinition{ + Name: defName, + Statements: []config.Statement(stmt), + } + return pd +} + +func createRoutingPolicy(ds config.DefinedSets, pd ...config.PolicyDefinition) config.RoutingPolicy { + pl := config.RoutingPolicy{ + DefinedSets: ds, + PolicyDefinitions: []config.PolicyDefinition(pd), + } + return pl +} + +func createPrefixSet(name string, prefix string, maskLength string) config.PrefixSet { + ps := config.PrefixSet{ + PrefixSetName: name, + PrefixList: []config.Prefix{ + config.Prefix{ + IpPrefix: prefix, + MasklengthRange: maskLength, + }}, + } + return ps +} + +func createNeighborSet(name string, addr string) config.NeighborSet { + ns := config.NeighborSet{ + NeighborSetName: name, + NeighborInfoList: []string{addr}, + } + return ns +} + +func createAs4Value(s string) uint32 { + v := strings.Split(s, ".") + upper, _ := strconv.ParseUint(v[0], 10, 16) + lower, _ := strconv.ParseUint(v[1], 10, 16) + return uint32(upper<<16 | lower) +} + +func TestPrefixSetOperation(t *testing.T) { + // tryp to create prefixset with multiple families + p1 := config.Prefix{ + IpPrefix: "0.0.0.0/0", + MasklengthRange: "0..7", + } + p2 := config.Prefix{ + IpPrefix: "0::/25", + MasklengthRange: "25..128", + } + _, err := NewPrefixSet(config.PrefixSet{ + PrefixSetName: "ps1", + PrefixList: []config.Prefix{p1, p2}, + }) + assert.NotNil(t, err) + m1, _ := NewPrefixSet(config.PrefixSet{ + PrefixSetName: "ps1", + PrefixList: []config.Prefix{p1}, + }) + m2, err := NewPrefixSet(config.PrefixSet{PrefixSetName: "ps2"}) + assert.Nil(t, err) + err = m1.Append(m2) + assert.Nil(t, err) + err = m2.Append(m1) + assert.Nil(t, err) + assert.Equal(t, bgp.RF_IPv4_UC, m2.family) + p3, _ := NewPrefix(config.Prefix{IpPrefix: "10.10.0.0/24", MasklengthRange: ""}) + p4, _ := NewPrefix(config.Prefix{IpPrefix: "0::/25", MasklengthRange: ""}) + _, err = NewPrefixSetFromApiStruct("ps3", []*Prefix{p3, p4}) + assert.NotNil(t, err) +} + +func TestPrefixSetMatch(t *testing.T) { + p1 := config.Prefix{ + IpPrefix: "0.0.0.0/0", + MasklengthRange: "0..7", + } + p2 := config.Prefix{ + IpPrefix: "0.0.0.0/0", + MasklengthRange: "25..32", + } + ps, err := NewPrefixSet(config.PrefixSet{ + PrefixSetName: "ps1", + PrefixList: []config.Prefix{p1, p2}, + }) + assert.Nil(t, err) + m := &PrefixCondition{ + set: ps, + } + + path := NewPath(nil, bgp.NewIPAddrPrefix(6, "0.0.0.0"), false, []bgp.PathAttributeInterface{}, time.Now(), false) + assert.True(t, m.Evaluate(path, nil)) + + path = NewPath(nil, bgp.NewIPAddrPrefix(10, "0.0.0.0"), false, []bgp.PathAttributeInterface{}, time.Now(), false) + assert.False(t, m.Evaluate(path, nil)) + + path = NewPath(nil, bgp.NewIPAddrPrefix(25, "0.0.0.0"), false, []bgp.PathAttributeInterface{}, time.Now(), false) + assert.True(t, m.Evaluate(path, nil)) + + path = NewPath(nil, bgp.NewIPAddrPrefix(30, "0.0.0.0"), false, []bgp.PathAttributeInterface{}, time.Now(), false) + assert.True(t, m.Evaluate(path, nil)) + + p3 := config.Prefix{ + IpPrefix: "0.0.0.0/0", + MasklengthRange: "9..10", + } + ps2, err := NewPrefixSet(config.PrefixSet{ + PrefixSetName: "ps2", + PrefixList: []config.Prefix{p3}, + }) + assert.Nil(t, err) + err = ps.Append(ps2) + assert.Nil(t, err) + + path = NewPath(nil, bgp.NewIPAddrPrefix(10, "0.0.0.0"), false, []bgp.PathAttributeInterface{}, time.Now(), false) + assert.True(t, m.Evaluate(path, nil)) + + ps3, err := NewPrefixSet(config.PrefixSet{ + PrefixSetName: "ps3", + PrefixList: []config.Prefix{p1}, + }) + assert.Nil(t, err) + err = ps.Remove(ps3) + assert.Nil(t, err) + + path = NewPath(nil, bgp.NewIPAddrPrefix(6, "0.0.0.0"), false, []bgp.PathAttributeInterface{}, time.Now(), false) + assert.False(t, m.Evaluate(path, nil)) +} + +func TestPrefixSetMatchV4withV6Prefix(t *testing.T) { + p1 := config.Prefix{ + IpPrefix: "c000::/3", + MasklengthRange: "3..128", + } + ps, err := NewPrefixSet(config.PrefixSet{ + PrefixSetName: "ps1", + PrefixList: []config.Prefix{p1}, + }) + assert.Nil(t, err) + m := &PrefixCondition{ + set: ps, + } + + path := NewPath(nil, bgp.NewIPAddrPrefix(6, "192.0.0.0"), false, []bgp.PathAttributeInterface{}, time.Now(), false) + assert.False(t, m.Evaluate(path, nil)) +} + +func TestLargeCommunityMatchAction(t *testing.T) { + coms := []*bgp.LargeCommunity{ + &bgp.LargeCommunity{ASN: 100, LocalData1: 100, LocalData2: 100}, + &bgp.LargeCommunity{ASN: 100, LocalData1: 200, LocalData2: 200}, + } + p := NewPath(nil, nil, false, []bgp.PathAttributeInterface{bgp.NewPathAttributeLargeCommunities(coms)}, time.Time{}, false) + + c := config.LargeCommunitySet{ + LargeCommunitySetName: "l0", + LargeCommunityList: []string{ + "100:100:100", + "100:300:100", + }, + } + + set, err := NewLargeCommunitySet(c) + assert.Equal(t, err, nil) + + m, err := NewLargeCommunityCondition(config.MatchLargeCommunitySet{ + LargeCommunitySet: "l0", + }) + assert.Equal(t, err, nil) + m.set = set + + assert.Equal(t, m.Evaluate(p, nil), true) + + a, err := NewLargeCommunityAction(config.SetLargeCommunity{ + SetLargeCommunityMethod: config.SetLargeCommunityMethod{ + CommunitiesList: []string{"100:100:100"}, + }, + Options: config.BGP_SET_COMMUNITY_OPTION_TYPE_REMOVE, + }) + assert.Equal(t, err, nil) + p = a.Apply(p, nil) + + assert.Equal(t, m.Evaluate(p, nil), false) + + a, err = NewLargeCommunityAction(config.SetLargeCommunity{ + SetLargeCommunityMethod: config.SetLargeCommunityMethod{ + CommunitiesList: []string{ + "100:300:100", + "200:100:100", + }, + }, + Options: config.BGP_SET_COMMUNITY_OPTION_TYPE_ADD, + }) + assert.Equal(t, err, nil) + p = a.Apply(p, nil) + + assert.Equal(t, m.Evaluate(p, nil), true) + + a, err = NewLargeCommunityAction(config.SetLargeCommunity{ + SetLargeCommunityMethod: config.SetLargeCommunityMethod{ + CommunitiesList: []string{"^100:"}, + }, + Options: config.BGP_SET_COMMUNITY_OPTION_TYPE_REMOVE, + }) + assert.Equal(t, err, nil) + p = a.Apply(p, nil) + + assert.Equal(t, m.Evaluate(p, nil), false) + + c = config.LargeCommunitySet{ + LargeCommunitySetName: "l1", + LargeCommunityList: []string{ + "200:", + }, + } + + set, err = NewLargeCommunitySet(c) + assert.Equal(t, err, nil) + + m, err = NewLargeCommunityCondition(config.MatchLargeCommunitySet{ + LargeCommunitySet: "l1", + }) + assert.Equal(t, err, nil) + m.set = set + + assert.Equal(t, m.Evaluate(p, nil), true) +} + +func TestAfiSafiInMatchPath(t *testing.T) { + condition, err := NewAfiSafiInCondition([]config.AfiSafiType{config.AFI_SAFI_TYPE_L3VPN_IPV4_UNICAST, config.AFI_SAFI_TYPE_L3VPN_IPV6_UNICAST}) + require.NoError(t, err) + + rtExtCom, err := bgp.ParseExtendedCommunity(bgp.EC_SUBTYPE_ROUTE_TARGET, "100:100") + assert.NoError(t, err) + + prefixVPNv4 := bgp.NewLabeledVPNIPAddrPrefix(0, "1.1.1.0/24", *bgp.NewMPLSLabelStack(), bgp.NewRouteDistinguisherTwoOctetAS(100, 100)) + prefixVPNv6 := bgp.NewLabeledVPNIPv6AddrPrefix(0, "2001:0db8:85a3:0000:0000:8a2e:0370:7334", *bgp.NewMPLSLabelStack(), bgp.NewRouteDistinguisherTwoOctetAS(200, 200)) + prefixRTC := bgp.NewRouteTargetMembershipNLRI(100, nil) + prefixv4 := bgp.NewIPAddrPrefix(0, "1.1.1.0/24") + prefixv6 := bgp.NewIPv6AddrPrefix(0, "2001:0db8:85a3:0000:0000:8a2e:0370:7334") + + pathVPNv4 := NewPath(nil, prefixVPNv4, false, []bgp.PathAttributeInterface{bgp.NewPathAttributeExtendedCommunities([]bgp.ExtendedCommunityInterface{rtExtCom})}, time.Time{}, false) + pathVPNv6 := NewPath(nil, prefixVPNv6, false, []bgp.PathAttributeInterface{bgp.NewPathAttributeExtendedCommunities([]bgp.ExtendedCommunityInterface{rtExtCom})}, time.Time{}, false) + pathv4 := NewPath(nil, prefixv4, false, []bgp.PathAttributeInterface{}, time.Time{}, false) + pathv6 := NewPath(nil, prefixv6, false, []bgp.PathAttributeInterface{}, time.Time{}, false) + pathRTC := NewPath(nil, prefixRTC, false, []bgp.PathAttributeInterface{}, time.Time{}, false) + + type Entry struct { + path *Path + shouldMatch bool + } + + for _, entry := range []Entry{ + {pathVPNv4, true}, + {pathVPNv6, true}, + {pathv4, false}, + {pathv6, false}, + {pathRTC, false}, + } { + assert.Equal(t, condition.Evaluate(entry.path, nil), entry.shouldMatch) + } +} + +func TestMultipleStatementPolicy(t *testing.T) { + r := NewRoutingPolicy() + rp := config.RoutingPolicy{ + PolicyDefinitions: []config.PolicyDefinition{config.PolicyDefinition{ + Name: "p1", + Statements: []config.Statement{ + config.Statement{ + Actions: config.Actions{ + BgpActions: config.BgpActions{ + SetMed: "+100", + }, + }, + }, + config.Statement{ + Actions: config.Actions{ + BgpActions: config.BgpActions{ + SetLocalPref: 100, + }, + }, + }, + }, + }, + }, + } + err := r.reload(rp) + assert.Nil(t, err) + + nlri := bgp.NewIPAddrPrefix(24, "10.10.0.0") + + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65001})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("10.0.0.1") + pattrs := []bgp.PathAttributeInterface{origin, aspath, nexthop} + + path := NewPath(nil, nlri, false, pattrs, time.Now(), false) + + pType, newPath := r.policyMap["p1"].Apply(path, nil) + assert.Equal(t, ROUTE_TYPE_NONE, pType) + med, _ := newPath.GetMed() + assert.Equal(t, med, uint32(100)) + localPref, _ := newPath.GetLocalPref() + assert.Equal(t, localPref, uint32(100)) +} diff --git a/internal/pkg/table/roa.go b/internal/pkg/table/roa.go new file mode 100644 index 00000000..fe08fe54 --- /dev/null +++ b/internal/pkg/table/roa.go @@ -0,0 +1,60 @@ +// 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 table + +import ( + "fmt" + "net" +) + +type IPPrefix struct { + Prefix net.IP + Length uint8 +} + +func (p *IPPrefix) String() string { + return fmt.Sprintf("%s/%d", p.Prefix, p.Length) +} + +type ROA struct { + Family int + Prefix *IPPrefix + MaxLen uint8 + AS uint32 + Src string +} + +func NewROA(family int, prefixByte []byte, prefixLen uint8, maxLen uint8, as uint32, src string) *ROA { + p := make([]byte, len(prefixByte)) + copy(p, prefixByte) + return &ROA{ + Family: family, + Prefix: &IPPrefix{ + Prefix: p, + Length: prefixLen, + }, + MaxLen: maxLen, + AS: as, + Src: src, + } +} + +func (r *ROA) Equal(roa *ROA) bool { + if r.MaxLen == roa.MaxLen && r.Src == roa.Src && r.AS == roa.AS { + return true + } + return false +} diff --git a/internal/pkg/table/table.go b/internal/pkg/table/table.go new file mode 100644 index 00000000..bcde936b --- /dev/null +++ b/internal/pkg/table/table.go @@ -0,0 +1,451 @@ +// Copyright (C) 2014 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 table + +import ( + "fmt" + "net" + "strings" + "unsafe" + + "github.com/armon/go-radix" + "github.com/osrg/gobgp/pkg/packet/bgp" + log "github.com/sirupsen/logrus" +) + +type LookupOption uint8 + +const ( + LOOKUP_EXACT LookupOption = iota + LOOKUP_LONGER + LOOKUP_SHORTER +) + +type LookupPrefix struct { + Prefix string + LookupOption +} + +type TableSelectOption struct { + ID string + AS uint32 + LookupPrefixes []*LookupPrefix + VRF *Vrf + adj bool + Best bool + MultiPath bool +} + +type Table struct { + routeFamily bgp.RouteFamily + destinations map[string]*Destination +} + +func NewTable(rf bgp.RouteFamily, dsts ...*Destination) *Table { + t := &Table{ + routeFamily: rf, + destinations: make(map[string]*Destination), + } + for _, dst := range dsts { + t.setDestination(dst) + } + return t +} + +func (t *Table) GetRoutefamily() bgp.RouteFamily { + return t.routeFamily +} + +func (t *Table) deletePathsByVrf(vrf *Vrf) []*Path { + pathList := make([]*Path, 0) + for _, dest := range t.destinations { + for _, p := range dest.knownPathList { + var rd bgp.RouteDistinguisherInterface + nlri := p.GetNlri() + switch nlri.(type) { + case *bgp.LabeledVPNIPAddrPrefix: + rd = nlri.(*bgp.LabeledVPNIPAddrPrefix).RD + case *bgp.LabeledVPNIPv6AddrPrefix: + rd = nlri.(*bgp.LabeledVPNIPv6AddrPrefix).RD + case *bgp.EVPNNLRI: + rd = nlri.(*bgp.EVPNNLRI).RD() + default: + return pathList + } + if p.IsLocal() && vrf.Rd.String() == rd.String() { + pathList = append(pathList, p.Clone(true)) + break + } + } + } + return pathList +} + +func (t *Table) deleteRTCPathsByVrf(vrf *Vrf, vrfs map[string]*Vrf) []*Path { + pathList := make([]*Path, 0) + if t.routeFamily != bgp.RF_RTC_UC { + return pathList + } + for _, target := range vrf.ImportRt { + lhs := target.String() + for _, dest := range t.destinations { + nlri := dest.GetNlri().(*bgp.RouteTargetMembershipNLRI) + rhs := nlri.RouteTarget.String() + if lhs == rhs && isLastTargetUser(vrfs, target) { + for _, p := range dest.knownPathList { + if p.IsLocal() { + pathList = append(pathList, p.Clone(true)) + break + } + } + } + } + } + return pathList +} + +func (t *Table) deleteDestByNlri(nlri bgp.AddrPrefixInterface) *Destination { + if dst := t.GetDestination(nlri); dst != nil { + t.deleteDest(dst) + return dst + } + return nil +} + +func (t *Table) deleteDest(dest *Destination) { + destinations := t.GetDestinations() + delete(destinations, t.tableKey(dest.GetNlri())) + if len(destinations) == 0 { + t.destinations = make(map[string]*Destination) + } +} + +func (t *Table) validatePath(path *Path) { + if path == nil { + log.WithFields(log.Fields{ + "Topic": "Table", + "Key": t.routeFamily, + }).Error("path is nil") + } + if path.GetRouteFamily() != t.routeFamily { + log.WithFields(log.Fields{ + "Topic": "Table", + "Key": t.routeFamily, + "Prefix": path.GetNlri().String(), + "ReceivedRf": path.GetRouteFamily().String(), + }).Error("Invalid path. RouteFamily mismatch") + } + if attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_AS_PATH); attr != nil { + pathParam := attr.(*bgp.PathAttributeAsPath).Value + for _, as := range pathParam { + _, y := as.(*bgp.As4PathParam) + if !y { + log.WithFields(log.Fields{ + "Topic": "Table", + "Key": t.routeFamily, + "As": as, + }).Fatal("AsPathParam must be converted to As4PathParam") + } + } + } + if attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_AS4_PATH); attr != nil { + log.WithFields(log.Fields{ + "Topic": "Table", + "Key": t.routeFamily, + }).Fatal("AS4_PATH must be converted to AS_PATH") + } + if path.GetNlri() == nil { + log.WithFields(log.Fields{ + "Topic": "Table", + "Key": t.routeFamily, + }).Fatal("path's nlri is nil") + } +} + +func (t *Table) getOrCreateDest(nlri bgp.AddrPrefixInterface) *Destination { + dest := t.GetDestination(nlri) + // If destination for given prefix does not exist we create it. + if dest == nil { + log.WithFields(log.Fields{ + "Topic": "Table", + "Nlri": nlri, + }).Debugf("create Destination") + dest = NewDestination(nlri, 64) + t.setDestination(dest) + } + return dest +} + +func (t *Table) GetDestinations() map[string]*Destination { + return t.destinations +} +func (t *Table) setDestinations(destinations map[string]*Destination) { + t.destinations = destinations +} +func (t *Table) GetDestination(nlri bgp.AddrPrefixInterface) *Destination { + dest, ok := t.destinations[t.tableKey(nlri)] + if ok { + return dest + } else { + return nil + } +} + +func (t *Table) GetLongerPrefixDestinations(key string) ([]*Destination, error) { + results := make([]*Destination, 0, len(t.GetDestinations())) + switch t.routeFamily { + case bgp.RF_IPv4_UC, bgp.RF_IPv6_UC, bgp.RF_IPv4_MPLS, bgp.RF_IPv6_MPLS: + _, prefix, err := net.ParseCIDR(key) + if err != nil { + return nil, err + } + k := CidrToRadixkey(prefix.String()) + r := radix.New() + for _, dst := range t.GetDestinations() { + r.Insert(AddrToRadixkey(dst.nlri), dst) + } + r.WalkPrefix(k, func(s string, v interface{}) bool { + results = append(results, v.(*Destination)) + return false + }) + default: + for _, dst := range t.GetDestinations() { + results = append(results, dst) + } + } + return results, nil +} + +func (t *Table) GetEvpnDestinationsWithRouteType(typ string) ([]*Destination, error) { + var routeType uint8 + switch strings.ToLower(typ) { + case "a-d": + routeType = bgp.EVPN_ROUTE_TYPE_ETHERNET_AUTO_DISCOVERY + case "macadv": + routeType = bgp.EVPN_ROUTE_TYPE_MAC_IP_ADVERTISEMENT + case "multicast": + routeType = bgp.EVPN_INCLUSIVE_MULTICAST_ETHERNET_TAG + case "esi": + routeType = bgp.EVPN_ETHERNET_SEGMENT_ROUTE + case "prefix": + routeType = bgp.EVPN_IP_PREFIX + default: + return nil, fmt.Errorf("unsupported evpn route type: %s", typ) + } + destinations := t.GetDestinations() + results := make([]*Destination, 0, len(destinations)) + switch t.routeFamily { + case bgp.RF_EVPN: + for _, dst := range destinations { + if nlri, ok := dst.nlri.(*bgp.EVPNNLRI); !ok { + return nil, fmt.Errorf("invalid evpn nlri type detected: %T", dst.nlri) + } else if nlri.RouteType == routeType { + results = append(results, dst) + } + } + default: + for _, dst := range destinations { + results = append(results, dst) + } + } + return results, nil +} + +func (t *Table) setDestination(dst *Destination) { + t.destinations[t.tableKey(dst.nlri)] = dst +} + +func (t *Table) tableKey(nlri bgp.AddrPrefixInterface) string { + switch T := nlri.(type) { + case *bgp.IPAddrPrefix: + b := make([]byte, 5) + copy(b, T.Prefix.To4()) + b[4] = T.Length + return *(*string)(unsafe.Pointer(&b)) + case *bgp.IPv6AddrPrefix: + b := make([]byte, 17) + copy(b, T.Prefix.To16()) + b[16] = T.Length + return *(*string)(unsafe.Pointer(&b)) + } + return nlri.String() +} + +func (t *Table) Bests(id string, as uint32) []*Path { + paths := make([]*Path, 0, len(t.destinations)) + for _, dst := range t.destinations { + path := dst.GetBestPath(id, as) + if path != nil { + paths = append(paths, path) + } + } + return paths +} + +func (t *Table) MultiBests(id string) [][]*Path { + paths := make([][]*Path, 0, len(t.destinations)) + for _, dst := range t.destinations { + path := dst.GetMultiBestPath(id) + if path != nil { + paths = append(paths, path) + } + } + return paths +} + +func (t *Table) GetKnownPathList(id string, as uint32) []*Path { + paths := make([]*Path, 0, len(t.destinations)) + for _, dst := range t.destinations { + paths = append(paths, dst.GetKnownPathList(id, as)...) + } + return paths +} + +func (t *Table) Select(option ...TableSelectOption) (*Table, error) { + id := GLOBAL_RIB_NAME + var vrf *Vrf + adj := false + prefixes := make([]*LookupPrefix, 0, len(option)) + best := false + mp := false + as := uint32(0) + for _, o := range option { + if o.ID != "" { + id = o.ID + } + if o.VRF != nil { + vrf = o.VRF + } + adj = o.adj + prefixes = append(prefixes, o.LookupPrefixes...) + best = o.Best + mp = o.MultiPath + as = o.AS + } + dOption := DestinationSelectOption{ID: id, AS: as, VRF: vrf, adj: adj, Best: best, MultiPath: mp} + r := &Table{ + routeFamily: t.routeFamily, + destinations: make(map[string]*Destination), + } + + if len(prefixes) != 0 { + switch t.routeFamily { + case bgp.RF_IPv4_UC, bgp.RF_IPv6_UC: + f := func(prefixStr string) bool { + var nlri bgp.AddrPrefixInterface + if t.routeFamily == bgp.RF_IPv4_UC { + nlri, _ = bgp.NewPrefixFromRouteFamily(bgp.AFI_IP, bgp.SAFI_UNICAST, prefixStr) + } else { + nlri, _ = bgp.NewPrefixFromRouteFamily(bgp.AFI_IP6, bgp.SAFI_UNICAST, prefixStr) + } + if dst := t.GetDestination(nlri); dst != nil { + if d := dst.Select(dOption); d != nil { + r.setDestination(d) + return true + } + } + return false + } + + for _, p := range prefixes { + key := p.Prefix + switch p.LookupOption { + case LOOKUP_LONGER: + ds, err := t.GetLongerPrefixDestinations(key) + if err != nil { + return nil, err + } + for _, dst := range ds { + if d := dst.Select(dOption); d != nil { + r.setDestination(d) + } + } + case LOOKUP_SHORTER: + addr, prefix, err := net.ParseCIDR(key) + if err != nil { + return nil, err + } + ones, _ := prefix.Mask.Size() + for i := ones; i >= 0; i-- { + _, prefix, _ := net.ParseCIDR(fmt.Sprintf("%s/%d", addr.String(), i)) + f(prefix.String()) + } + default: + if host := net.ParseIP(key); host != nil { + masklen := 32 + if t.routeFamily == bgp.RF_IPv6_UC { + masklen = 128 + } + for i := masklen; i >= 0; i-- { + _, prefix, err := net.ParseCIDR(fmt.Sprintf("%s/%d", key, i)) + if err != nil { + return nil, err + } + if f(prefix.String()) { + break + } + } + } else { + f(key) + } + } + } + case bgp.RF_EVPN: + for _, p := range prefixes { + // Uses LookupPrefix.Prefix as EVPN Route Type string + ds, err := t.GetEvpnDestinationsWithRouteType(p.Prefix) + if err != nil { + return nil, err + } + for _, dst := range ds { + if d := dst.Select(dOption); d != nil { + r.setDestination(d) + } + } + } + default: + return nil, fmt.Errorf("route filtering is not supported for this family") + } + } else { + for _, dst := range t.GetDestinations() { + if d := dst.Select(dOption); d != nil { + r.setDestination(d) + } + } + } + return r, nil +} + +type TableInfo struct { + NumDestination int + NumPath int + NumAccepted int +} + +func (t *Table) Info(id string, as uint32) *TableInfo { + var numD, numP int + for _, d := range t.destinations { + ps := d.GetKnownPathList(id, as) + if len(ps) > 0 { + numD += 1 + numP += len(ps) + } + } + return &TableInfo{ + NumDestination: numD, + NumPath: numP, + } +} diff --git a/internal/pkg/table/table_manager.go b/internal/pkg/table/table_manager.go new file mode 100644 index 00000000..e10f4d6a --- /dev/null +++ b/internal/pkg/table/table_manager.go @@ -0,0 +1,356 @@ +// Copyright (C) 2014 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 table + +import ( + "bytes" + "fmt" + "net" + "time" + + "github.com/osrg/gobgp/pkg/packet/bgp" + + farm "github.com/dgryski/go-farm" + log "github.com/sirupsen/logrus" +) + +const ( + GLOBAL_RIB_NAME = "global" +) + +func ProcessMessage(m *bgp.BGPMessage, peerInfo *PeerInfo, timestamp time.Time) []*Path { + update := m.Body.(*bgp.BGPUpdate) + + if y, f := update.IsEndOfRib(); y { + // this message has no normal updates or withdrawals. + return []*Path{NewEOR(f)} + } + + adds := make([]bgp.AddrPrefixInterface, 0, len(update.NLRI)) + for _, nlri := range update.NLRI { + adds = append(adds, nlri) + } + + dels := make([]bgp.AddrPrefixInterface, 0, len(update.WithdrawnRoutes)) + for _, nlri := range update.WithdrawnRoutes { + dels = append(dels, nlri) + } + + attrs := make([]bgp.PathAttributeInterface, 0, len(update.PathAttributes)) + var reach *bgp.PathAttributeMpReachNLRI + for _, attr := range update.PathAttributes { + switch a := attr.(type) { + case *bgp.PathAttributeMpReachNLRI: + reach = a + case *bgp.PathAttributeMpUnreachNLRI: + l := make([]bgp.AddrPrefixInterface, 0, len(a.Value)) + l = append(l, a.Value...) + dels = append(dels, l...) + default: + attrs = append(attrs, attr) + } + } + + listLen := len(adds) + len(dels) + if reach != nil { + listLen += len(reach.Value) + } + + var hash uint32 + if len(adds) > 0 || reach != nil { + total := bytes.NewBuffer(make([]byte, 0)) + for _, a := range attrs { + b, _ := a.Serialize() + total.Write(b) + } + hash = farm.Hash32(total.Bytes()) + } + + pathList := make([]*Path, 0, listLen) + for _, nlri := range adds { + p := NewPath(peerInfo, nlri, false, attrs, timestamp, false) + p.SetHash(hash) + pathList = append(pathList, p) + } + if reach != nil { + reachAttrs := make([]bgp.PathAttributeInterface, len(attrs)+1) + copy(reachAttrs, attrs) + // we sort attributes when creating a bgp message from paths + reachAttrs[len(reachAttrs)-1] = reach + + for _, nlri := range reach.Value { + p := NewPath(peerInfo, nlri, false, reachAttrs, timestamp, false) + p.SetHash(hash) + pathList = append(pathList, p) + } + } + for _, nlri := range dels { + p := NewPath(peerInfo, nlri, true, []bgp.PathAttributeInterface{}, timestamp, false) + pathList = append(pathList, p) + } + return pathList +} + +type TableManager struct { + Tables map[bgp.RouteFamily]*Table + Vrfs map[string]*Vrf + rfList []bgp.RouteFamily +} + +func NewTableManager(rfList []bgp.RouteFamily) *TableManager { + t := &TableManager{ + Tables: make(map[bgp.RouteFamily]*Table), + Vrfs: make(map[string]*Vrf), + rfList: rfList, + } + for _, rf := range rfList { + t.Tables[rf] = NewTable(rf) + } + return t +} + +func (manager *TableManager) GetRFlist() []bgp.RouteFamily { + return manager.rfList +} + +func (manager *TableManager) AddVrf(name string, id uint32, rd bgp.RouteDistinguisherInterface, importRt, exportRt []bgp.ExtendedCommunityInterface, info *PeerInfo) ([]*Path, error) { + if _, ok := manager.Vrfs[name]; ok { + return nil, fmt.Errorf("vrf %s already exists", name) + } + log.WithFields(log.Fields{ + "Topic": "Vrf", + "Key": name, + "Rd": rd, + "ImportRt": importRt, + "ExportRt": exportRt, + }).Debugf("add vrf") + manager.Vrfs[name] = &Vrf{ + Name: name, + Id: id, + Rd: rd, + ImportRt: importRt, + ExportRt: exportRt, + } + msgs := make([]*Path, 0, len(importRt)) + nexthop := "0.0.0.0" + for _, target := range importRt { + nlri := bgp.NewRouteTargetMembershipNLRI(info.AS, target) + pattr := make([]bgp.PathAttributeInterface, 0, 2) + pattr = append(pattr, bgp.NewPathAttributeOrigin(bgp.BGP_ORIGIN_ATTR_TYPE_IGP)) + pattr = append(pattr, bgp.NewPathAttributeMpReachNLRI(nexthop, []bgp.AddrPrefixInterface{nlri})) + msgs = append(msgs, NewPath(info, nlri, false, pattr, time.Now(), false)) + } + return msgs, nil +} + +func (manager *TableManager) DeleteVrf(name string) ([]*Path, error) { + if _, ok := manager.Vrfs[name]; !ok { + return nil, fmt.Errorf("vrf %s not found", name) + } + msgs := make([]*Path, 0) + vrf := manager.Vrfs[name] + for _, t := range manager.Tables { + msgs = append(msgs, t.deletePathsByVrf(vrf)...) + } + log.WithFields(log.Fields{ + "Topic": "Vrf", + "Key": vrf.Name, + "Rd": vrf.Rd, + "ImportRt": vrf.ImportRt, + "ExportRt": vrf.ExportRt, + }).Debugf("delete vrf") + delete(manager.Vrfs, name) + rtcTable := manager.Tables[bgp.RF_RTC_UC] + msgs = append(msgs, rtcTable.deleteRTCPathsByVrf(vrf, manager.Vrfs)...) + return msgs, nil +} + +func (tm *TableManager) update(newPath *Path) *Update { + t := tm.Tables[newPath.GetRouteFamily()] + t.validatePath(newPath) + dst := t.getOrCreateDest(newPath.GetNlri()) + u := dst.Calculate(newPath) + if len(dst.knownPathList) == 0 { + t.deleteDest(dst) + } + return u +} + +func (manager *TableManager) GetPathListByPeer(info *PeerInfo, rf bgp.RouteFamily) []*Path { + if t, ok := manager.Tables[rf]; ok { + pathList := make([]*Path, 0, len(t.destinations)) + for _, dst := range t.destinations { + for _, p := range dst.knownPathList { + if p.GetSource().Equal(info) { + pathList = append(pathList, p) + } + } + } + return pathList + } + return nil +} + +func (manager *TableManager) Update(newPath *Path) []*Update { + if newPath == nil || newPath.IsEOR() { + return nil + } + + // Except for a special case with EVPN, we'll have one destination. + updates := make([]*Update, 0, 1) + family := newPath.GetRouteFamily() + if _, ok := manager.Tables[family]; ok { + updates = append(updates, manager.update(newPath)) + + if family == bgp.RF_EVPN { + for _, p := range manager.handleMacMobility(newPath) { + updates = append(updates, manager.update(p)) + } + } + } + return updates +} + +// EVPN MAC MOBILITY HANDLING +// +// RFC7432 15. MAC Mobility +// +// A PE receiving a MAC/IP Advertisement route for a MAC address with a +// different Ethernet segment identifier and a higher sequence number +// than that which it had previously advertised withdraws its MAC/IP +// Advertisement route. +func (manager *TableManager) handleMacMobility(path *Path) []*Path { + pathList := make([]*Path, 0) + nlri := path.GetNlri().(*bgp.EVPNNLRI) + if path.IsWithdraw || path.IsLocal() || nlri.RouteType != bgp.EVPN_ROUTE_TYPE_MAC_IP_ADVERTISEMENT { + return nil + } + for _, path2 := range manager.GetPathList(GLOBAL_RIB_NAME, 0, []bgp.RouteFamily{bgp.RF_EVPN}) { + if !path2.IsLocal() || path2.GetNlri().(*bgp.EVPNNLRI).RouteType != bgp.EVPN_ROUTE_TYPE_MAC_IP_ADVERTISEMENT { + continue + } + f := func(p *Path) (bgp.EthernetSegmentIdentifier, net.HardwareAddr, int) { + nlri := p.GetNlri().(*bgp.EVPNNLRI) + d := nlri.RouteTypeData.(*bgp.EVPNMacIPAdvertisementRoute) + ecs := p.GetExtCommunities() + seq := -1 + for _, ec := range ecs { + if t, st := ec.GetTypes(); t == bgp.EC_TYPE_EVPN && st == bgp.EC_SUBTYPE_MAC_MOBILITY { + seq = int(ec.(*bgp.MacMobilityExtended).Sequence) + break + } + } + return d.ESI, d.MacAddress, seq + } + e1, m1, s1 := f(path) + e2, m2, s2 := f(path2) + if bytes.Equal(m1, m2) && !bytes.Equal(e1.Value, e2.Value) && s1 > s2 { + pathList = append(pathList, path2.Clone(true)) + } + } + return pathList +} + +func (manager *TableManager) tables(list ...bgp.RouteFamily) []*Table { + l := make([]*Table, 0, len(manager.Tables)) + if len(list) == 0 { + for _, v := range manager.Tables { + l = append(l, v) + } + return l + } + for _, f := range list { + if t, ok := manager.Tables[f]; ok { + l = append(l, t) + } + } + return l +} + +func (manager *TableManager) getDestinationCount(rfList []bgp.RouteFamily) int { + count := 0 + for _, t := range manager.tables(rfList...) { + count += len(t.GetDestinations()) + } + return count +} + +func (manager *TableManager) GetBestPathList(id string, as uint32, rfList []bgp.RouteFamily) []*Path { + if SelectionOptions.DisableBestPathSelection { + // Note: If best path selection disabled, there is no best path. + return nil + } + paths := make([]*Path, 0, manager.getDestinationCount(rfList)) + for _, t := range manager.tables(rfList...) { + paths = append(paths, t.Bests(id, as)...) + } + return paths +} + +func (manager *TableManager) GetBestMultiPathList(id string, rfList []bgp.RouteFamily) [][]*Path { + if !UseMultiplePaths.Enabled || SelectionOptions.DisableBestPathSelection { + // Note: If multi path not enabled or best path selection disabled, + // there is no best multi path. + return nil + } + paths := make([][]*Path, 0, manager.getDestinationCount(rfList)) + for _, t := range manager.tables(rfList...) { + paths = append(paths, t.MultiBests(id)...) + } + return paths +} + +func (manager *TableManager) GetPathList(id string, as uint32, rfList []bgp.RouteFamily) []*Path { + paths := make([]*Path, 0, manager.getDestinationCount(rfList)) + for _, t := range manager.tables(rfList...) { + paths = append(paths, t.GetKnownPathList(id, as)...) + } + return paths +} + +func (manager *TableManager) GetPathListWithNexthop(id string, rfList []bgp.RouteFamily, nexthop net.IP) []*Path { + paths := make([]*Path, 0, manager.getDestinationCount(rfList)) + for _, rf := range rfList { + if t, ok := manager.Tables[rf]; ok { + for _, path := range t.GetKnownPathList(id, 0) { + if path.GetNexthop().Equal(nexthop) { + paths = append(paths, path) + } + } + } + } + return paths +} + +func (manager *TableManager) GetDestination(path *Path) *Destination { + if path == nil { + return nil + } + family := path.GetRouteFamily() + t, ok := manager.Tables[family] + if !ok { + return nil + } + return t.GetDestination(path.GetNlri()) +} + +func (manager *TableManager) TableInfo(id string, as uint32, family bgp.RouteFamily) (*TableInfo, error) { + t, ok := manager.Tables[family] + if !ok { + return nil, fmt.Errorf("address family %s is not configured", family) + } + return t.Info(id, as), nil +} diff --git a/internal/pkg/table/table_manager_test.go b/internal/pkg/table/table_manager_test.go new file mode 100644 index 00000000..67c26c11 --- /dev/null +++ b/internal/pkg/table/table_manager_test.go @@ -0,0 +1,2282 @@ +// Copyright (C) 2014 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 table + +import ( + _ "fmt" + "net" + "testing" + "time" + + "github.com/osrg/gobgp/pkg/packet/bgp" + + "github.com/stretchr/testify/assert" +) + +// process BGPUpdate message +// this function processes only BGPUpdate +func (manager *TableManager) ProcessUpdate(fromPeer *PeerInfo, message *bgp.BGPMessage) ([]*Path, error) { + pathList := make([]*Path, 0) + dsts := make([]*Update, 0) + for _, path := range ProcessMessage(message, fromPeer, time.Now()) { + dsts = append(dsts, manager.Update(path)...) + } + for _, d := range dsts { + b, _, _ := d.GetChanges(GLOBAL_RIB_NAME, 0, false) + pathList = append(pathList, b) + } + return pathList, nil +} + +func peerR1() *PeerInfo { + peer := &PeerInfo{ + AS: 65000, + LocalAS: 65000, + ID: net.ParseIP("10.0.0.3").To4(), + LocalID: net.ParseIP("10.0.0.1").To4(), + Address: net.ParseIP("10.0.0.1").To4(), + } + return peer +} + +func peerR2() *PeerInfo { + peer := &PeerInfo{ + AS: 65100, + LocalAS: 65000, + Address: net.ParseIP("10.0.0.2").To4(), + } + return peer +} + +func peerR3() *PeerInfo { + peer := &PeerInfo{ + AS: 65000, + LocalAS: 65000, + ID: net.ParseIP("10.0.0.2").To4(), + LocalID: net.ParseIP("10.0.0.1").To4(), + Address: net.ParseIP("10.0.0.3").To4(), + } + return peer +} + +// test best path calculation and check the result path is from R1 +func TestProcessBGPUpdate_0_select_onlypath_ipv4(t *testing.T) { + + tm := NewTableManager([]bgp.RouteFamily{bgp.RF_IPv4_UC}) + + bgpMessage := update_fromR1() + peer := peerR1() + pList, err := tm.ProcessUpdate(peer, bgpMessage) + assert.Equal(t, len(pList), 1) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + // check type + path := pList[0] + assert.Equal(t, path.GetRouteFamily(), bgp.RF_IPv4_UC) + + // check PathAttribute + pathAttributes := bgpMessage.Body.(*bgp.BGPUpdate).PathAttributes + expectedOrigin := pathAttributes[0] + attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_ORIGIN) + pathOrigin := attr.(*bgp.PathAttributeOrigin) + assert.Equal(t, expectedOrigin, pathOrigin) + + expectedAsPath := pathAttributes[1] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_AS_PATH) + pathAspath := attr.(*bgp.PathAttributeAsPath) + assert.Equal(t, expectedAsPath, pathAspath) + + expectedNexthopAttr := pathAttributes[2] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_NEXT_HOP) + pathNexthop := attr.(*bgp.PathAttributeNextHop) + assert.Equal(t, expectedNexthopAttr, pathNexthop) + + expectedMed := pathAttributes[3] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_MULTI_EXIT_DISC) + pathMed := attr.(*bgp.PathAttributeMultiExitDisc) + assert.Equal(t, expectedMed, pathMed) + + // check PathAttribute length + assert.Equal(t, 4, len(path.GetPathAttrs())) + + // check destination + expectedPrefix := "10.10.10.0/24" + assert.Equal(t, expectedPrefix, path.getPrefix()) + // check nexthop + expectedNexthop := "192.168.50.1" + assert.Equal(t, expectedNexthop, path.GetNexthop().String()) + +} + +// test best path calculation and check the result path is from R1 +func TestProcessBGPUpdate_0_select_onlypath_ipv6(t *testing.T) { + + tm := NewTableManager([]bgp.RouteFamily{bgp.RF_IPv6_UC}) + + bgpMessage := update_fromR1_ipv6() + peer := peerR1() + pList, err := tm.ProcessUpdate(peer, bgpMessage) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + // check type + path := pList[0] + assert.Equal(t, path.GetRouteFamily(), bgp.RF_IPv6_UC) + + // check PathAttribute + pathAttributes := bgpMessage.Body.(*bgp.BGPUpdate).PathAttributes + + expectedNexthopAttr := pathAttributes[0] + attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_MP_REACH_NLRI) + pathNexthop := attr.(*bgp.PathAttributeMpReachNLRI) + assert.Equal(t, expectedNexthopAttr, pathNexthop) + + expectedOrigin := pathAttributes[1] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_ORIGIN) + pathOrigin := attr.(*bgp.PathAttributeOrigin) + assert.Equal(t, expectedOrigin, pathOrigin) + + expectedAsPath := pathAttributes[2] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_AS_PATH) + pathAspath := attr.(*bgp.PathAttributeAsPath) + assert.Equal(t, expectedAsPath, pathAspath) + + expectedMed := pathAttributes[3] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_MULTI_EXIT_DISC) + pathMed := attr.(*bgp.PathAttributeMultiExitDisc) + assert.Equal(t, expectedMed, pathMed) + + // check PathAttribute length + assert.Equal(t, 4, len(path.GetPathAttrs())) + + // check destination + expectedPrefix := "2001:123:123:1::/64" + assert.Equal(t, expectedPrefix, path.getPrefix()) + // check nexthop + expectedNexthop := "2001::192:168:50:1" + assert.Equal(t, expectedNexthop, path.GetNexthop().String()) + +} + +// test: compare localpref +func TestProcessBGPUpdate_1_select_high_localpref_ipv4(t *testing.T) { + + tm := NewTableManager([]bgp.RouteFamily{bgp.RF_IPv4_UC}) + + // low localpref message + origin1 := bgp.NewPathAttributeOrigin(0) + aspath1 := createAsPathAttribute([]uint32{65000}) + nexthop1 := bgp.NewPathAttributeNextHop("192.168.50.1") + med1 := bgp.NewPathAttributeMultiExitDisc(0) + localpref1 := bgp.NewPathAttributeLocalPref(100) + + pathAttributes1 := []bgp.PathAttributeInterface{ + origin1, aspath1, nexthop1, med1, localpref1, + } + nlri1 := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.10.0")} + bgpMessage1 := bgp.NewBGPUpdateMessage(nil, pathAttributes1, nlri1) + + // high localpref message + origin2 := bgp.NewPathAttributeOrigin(0) + aspath2 := createAsPathAttribute([]uint32{65100, 65000}) + nexthop2 := bgp.NewPathAttributeNextHop("192.168.50.1") + med2 := bgp.NewPathAttributeMultiExitDisc(100) + localpref2 := bgp.NewPathAttributeLocalPref(200) + + pathAttributes2 := []bgp.PathAttributeInterface{ + origin2, aspath2, nexthop2, med2, localpref2, + } + nlri2 := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.10.0")} + bgpMessage2 := bgp.NewBGPUpdateMessage(nil, pathAttributes2, nlri2) + + peer1 := peerR1() + pList, err := tm.ProcessUpdate(peer1, bgpMessage1) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + peer2 := peerR2() + pList, err = tm.ProcessUpdate(peer2, bgpMessage2) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + // check type + path := pList[0] + assert.Equal(t, path.GetRouteFamily(), bgp.RF_IPv4_UC) + + // check PathAttribute + pathAttributes := bgpMessage2.Body.(*bgp.BGPUpdate).PathAttributes + expectedOrigin := pathAttributes[0] + attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_ORIGIN) + pathOrigin := attr.(*bgp.PathAttributeOrigin) + assert.Equal(t, expectedOrigin, pathOrigin) + + expectedAsPath := pathAttributes[1] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_AS_PATH) + pathAspath := attr.(*bgp.PathAttributeAsPath) + assert.Equal(t, expectedAsPath, pathAspath) + + expectedNexthopAttr := pathAttributes[2] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_NEXT_HOP) + pathNexthop := attr.(*bgp.PathAttributeNextHop) + assert.Equal(t, expectedNexthopAttr, pathNexthop) + + expectedMed := pathAttributes[3] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_MULTI_EXIT_DISC) + pathMed := attr.(*bgp.PathAttributeMultiExitDisc) + assert.Equal(t, expectedMed, pathMed) + + // check PathAttribute length + assert.Equal(t, len(pathAttributes2), len(path.GetPathAttrs())) + + // check destination + expectedPrefix := "10.10.10.0/24" + assert.Equal(t, expectedPrefix, path.getPrefix()) + // check nexthop + expectedNexthop := "192.168.50.1" + assert.Equal(t, expectedNexthop, path.GetNexthop().String()) + +} + +func TestProcessBGPUpdate_1_select_high_localpref_ipv6(t *testing.T) { + + tm := NewTableManager([]bgp.RouteFamily{bgp.RF_IPv6_UC}) + + origin1 := bgp.NewPathAttributeOrigin(0) + aspath1 := createAsPathAttribute([]uint32{65000}) + mp_reach1 := createMpReach("2001::192:168:50:1", + []bgp.AddrPrefixInterface{bgp.NewIPv6AddrPrefix(64, "2001:123:123:1::")}) + med1 := bgp.NewPathAttributeMultiExitDisc(100) + localpref1 := bgp.NewPathAttributeLocalPref(100) + + pathAttributes1 := []bgp.PathAttributeInterface{ + mp_reach1, origin1, aspath1, med1, localpref1, + } + + bgpMessage1 := bgp.NewBGPUpdateMessage(nil, pathAttributes1, nil) + + origin2 := bgp.NewPathAttributeOrigin(0) + aspath2 := createAsPathAttribute([]uint32{65100, 65000}) + mp_reach2 := createMpReach("2001::192:168:100:1", + []bgp.AddrPrefixInterface{bgp.NewIPv6AddrPrefix(64, "2001:123:123:1::")}) + med2 := bgp.NewPathAttributeMultiExitDisc(100) + localpref2 := bgp.NewPathAttributeLocalPref(200) + + pathAttributes2 := []bgp.PathAttributeInterface{ + mp_reach2, origin2, aspath2, med2, localpref2, + } + + bgpMessage2 := bgp.NewBGPUpdateMessage(nil, pathAttributes2, nil) + + peer1 := peerR1() + pList, err := tm.ProcessUpdate(peer1, bgpMessage1) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + peer2 := peerR2() + pList, err = tm.ProcessUpdate(peer2, bgpMessage2) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + // check type + path := pList[0] + assert.Equal(t, path.GetRouteFamily(), bgp.RF_IPv6_UC) + + // check PathAttribute + pathAttributes := bgpMessage2.Body.(*bgp.BGPUpdate).PathAttributes + + expectedNexthopAttr := pathAttributes[0] + attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_MP_REACH_NLRI) + pathNexthop := attr.(*bgp.PathAttributeMpReachNLRI) + assert.Equal(t, expectedNexthopAttr, pathNexthop) + + expectedOrigin := pathAttributes[1] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_ORIGIN) + pathOrigin := attr.(*bgp.PathAttributeOrigin) + assert.Equal(t, expectedOrigin, pathOrigin) + + expectedAsPath := pathAttributes[2] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_AS_PATH) + pathAspath := attr.(*bgp.PathAttributeAsPath) + assert.Equal(t, expectedAsPath, pathAspath) + + expectedMed := pathAttributes[3] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_MULTI_EXIT_DISC) + pathMed := attr.(*bgp.PathAttributeMultiExitDisc) + assert.Equal(t, expectedMed, pathMed) + + // check PathAttribute length + assert.Equal(t, 5, len(path.GetPathAttrs())) + + // check destination + expectedPrefix := "2001:123:123:1::/64" + assert.Equal(t, expectedPrefix, path.getPrefix()) + // check nexthop + expectedNexthop := "2001::192:168:100:1" + assert.Equal(t, expectedNexthop, path.GetNexthop().String()) + +} + +// test: compare localOrigin +func TestProcessBGPUpdate_2_select_local_origin_ipv4(t *testing.T) { + + tm := NewTableManager([]bgp.RouteFamily{bgp.RF_IPv4_UC}) + + // low localpref message + origin1 := bgp.NewPathAttributeOrigin(0) + aspath1 := createAsPathAttribute([]uint32{65000}) + nexthop1 := bgp.NewPathAttributeNextHop("192.168.50.1") + med1 := bgp.NewPathAttributeMultiExitDisc(0) + localpref1 := bgp.NewPathAttributeLocalPref(100) + + pathAttributes1 := []bgp.PathAttributeInterface{ + origin1, aspath1, nexthop1, med1, localpref1, + } + nlri1 := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.10.0")} + bgpMessage1 := bgp.NewBGPUpdateMessage(nil, pathAttributes1, nlri1) + + // high localpref message + origin2 := bgp.NewPathAttributeOrigin(0) + aspath2 := createAsPathAttribute([]uint32{}) + nexthop2 := bgp.NewPathAttributeNextHop("0.0.0.0") + med2 := bgp.NewPathAttributeMultiExitDisc(100) + localpref2 := bgp.NewPathAttributeLocalPref(100) + + pathAttributes2 := []bgp.PathAttributeInterface{ + origin2, aspath2, nexthop2, med2, localpref2, + } + nlri2 := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.10.0")} + bgpMessage2 := bgp.NewBGPUpdateMessage(nil, pathAttributes2, nlri2) + + peer1 := peerR1() + pList, err := tm.ProcessUpdate(peer1, bgpMessage1) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + var peer2 *PeerInfo = &PeerInfo{ + Address: net.ParseIP("0.0.0.0"), + } + pList, err = tm.ProcessUpdate(peer2, bgpMessage2) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + // check type + path := pList[0] + assert.Equal(t, path.GetRouteFamily(), bgp.RF_IPv4_UC) + + // check PathAttribute + pathAttributes := bgpMessage2.Body.(*bgp.BGPUpdate).PathAttributes + expectedOrigin := pathAttributes[0] + attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_ORIGIN) + pathOrigin := attr.(*bgp.PathAttributeOrigin) + assert.Equal(t, expectedOrigin, pathOrigin) + + expectedAsPath := pathAttributes[1] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_AS_PATH) + pathAspath := attr.(*bgp.PathAttributeAsPath) + assert.Equal(t, expectedAsPath, pathAspath) + + expectedNexthopAttr := pathAttributes[2] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_NEXT_HOP) + pathNexthop := attr.(*bgp.PathAttributeNextHop) + assert.Equal(t, expectedNexthopAttr, pathNexthop) + + expectedMed := pathAttributes[3] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_MULTI_EXIT_DISC) + pathMed := attr.(*bgp.PathAttributeMultiExitDisc) + assert.Equal(t, expectedMed, pathMed) + + // check PathAttribute length + assert.Equal(t, len(pathAttributes2), len(path.GetPathAttrs())) + + // check destination + expectedPrefix := "10.10.10.0/24" + assert.Equal(t, expectedPrefix, path.getPrefix()) + // check nexthop + expectedNexthop := "0.0.0.0" + assert.Equal(t, expectedNexthop, path.GetNexthop().String()) + +} + +func TestProcessBGPUpdate_2_select_local_origin_ipv6(t *testing.T) { + + tm := NewTableManager([]bgp.RouteFamily{bgp.RF_IPv6_UC}) + + origin1 := bgp.NewPathAttributeOrigin(0) + aspath1 := createAsPathAttribute([]uint32{65000}) + mp_reach1 := createMpReach("2001::192:168:50:1", + []bgp.AddrPrefixInterface{bgp.NewIPv6AddrPrefix(64, "2001:123:123:1::")}) + med1 := bgp.NewPathAttributeMultiExitDisc(100) + localpref1 := bgp.NewPathAttributeLocalPref(100) + + pathAttributes1 := []bgp.PathAttributeInterface{ + mp_reach1, origin1, aspath1, med1, localpref1, + } + + bgpMessage1 := bgp.NewBGPUpdateMessage(nil, pathAttributes1, nil) + + origin2 := bgp.NewPathAttributeOrigin(0) + aspath2 := createAsPathAttribute([]uint32{}) + mp_reach2 := createMpReach("::", + []bgp.AddrPrefixInterface{bgp.NewIPv6AddrPrefix(64, "2001:123:123:1::")}) + med2 := bgp.NewPathAttributeMultiExitDisc(100) + localpref2 := bgp.NewPathAttributeLocalPref(100) + + pathAttributes2 := []bgp.PathAttributeInterface{ + mp_reach2, origin2, aspath2, med2, localpref2, + } + + bgpMessage2 := bgp.NewBGPUpdateMessage(nil, pathAttributes2, nil) + + peer1 := peerR1() + pList, err := tm.ProcessUpdate(peer1, bgpMessage1) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + var peer2 *PeerInfo = &PeerInfo{ + Address: net.ParseIP("0.0.0.0"), + } + + pList, err = tm.ProcessUpdate(peer2, bgpMessage2) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + // check type + path := pList[0] + assert.Equal(t, path.GetRouteFamily(), bgp.RF_IPv6_UC) + + // check PathAttribute + pathAttributes := bgpMessage2.Body.(*bgp.BGPUpdate).PathAttributes + + expectedNexthopAttr := pathAttributes[0] + attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_MP_REACH_NLRI) + pathNexthop := attr.(*bgp.PathAttributeMpReachNLRI) + assert.Equal(t, expectedNexthopAttr, pathNexthop) + + expectedOrigin := pathAttributes[1] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_ORIGIN) + pathOrigin := attr.(*bgp.PathAttributeOrigin) + assert.Equal(t, expectedOrigin, pathOrigin) + + expectedAsPath := pathAttributes[2] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_AS_PATH) + pathAspath := attr.(*bgp.PathAttributeAsPath) + assert.Equal(t, expectedAsPath, pathAspath) + + expectedMed := pathAttributes[3] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_MULTI_EXIT_DISC) + pathMed := attr.(*bgp.PathAttributeMultiExitDisc) + assert.Equal(t, expectedMed, pathMed) + + // check PathAttribute length + assert.Equal(t, 5, len(path.GetPathAttrs())) + + // check destination + expectedPrefix := "2001:123:123:1::/64" + assert.Equal(t, expectedPrefix, path.getPrefix()) + // check nexthop + expectedNexthop := "::" + assert.Equal(t, expectedNexthop, path.GetNexthop().String()) + +} + +// test: compare AS_PATH +func TestProcessBGPUpdate_3_select_aspath_ipv4(t *testing.T) { + + tm := NewTableManager([]bgp.RouteFamily{bgp.RF_IPv4_UC}) + + bgpMessage1 := update_fromR2viaR1() + peer1 := peerR1() + pList, err := tm.ProcessUpdate(peer1, bgpMessage1) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + bgpMessage2 := update_fromR2() + peer2 := peerR2() + pList, err = tm.ProcessUpdate(peer2, bgpMessage2) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + // check type + path := pList[0] + assert.Equal(t, path.GetRouteFamily(), bgp.RF_IPv4_UC) + + // check PathAttribute + pathAttributes := bgpMessage2.Body.(*bgp.BGPUpdate).PathAttributes + expectedOrigin := pathAttributes[0] + attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_ORIGIN) + pathOrigin := attr.(*bgp.PathAttributeOrigin) + assert.Equal(t, expectedOrigin, pathOrigin) + + expectedAsPath := pathAttributes[1] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_AS_PATH) + pathAspath := attr.(*bgp.PathAttributeAsPath) + assert.Equal(t, expectedAsPath, pathAspath) + + expectedNexthopAttr := pathAttributes[2] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_NEXT_HOP) + pathNexthop := attr.(*bgp.PathAttributeNextHop) + assert.Equal(t, expectedNexthopAttr, pathNexthop) + + expectedMed := pathAttributes[3] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_MULTI_EXIT_DISC) + pathMed := attr.(*bgp.PathAttributeMultiExitDisc) + assert.Equal(t, expectedMed, pathMed) + + // check PathAttribute length + assert.Equal(t, 4, len(path.GetPathAttrs())) + + // check destination + expectedPrefix := "20.20.20.0/24" + assert.Equal(t, expectedPrefix, path.getPrefix()) + // check nexthop + expectedNexthop := "192.168.100.1" + assert.Equal(t, expectedNexthop, path.GetNexthop().String()) + +} + +func TestProcessBGPUpdate_3_select_aspath_ipv6(t *testing.T) { + + tm := NewTableManager([]bgp.RouteFamily{bgp.RF_IPv6_UC}) + + bgpMessage1 := update_fromR2viaR1_ipv6() + peer1 := peerR1() + pList, err := tm.ProcessUpdate(peer1, bgpMessage1) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + bgpMessage2 := update_fromR2_ipv6() + peer2 := peerR2() + pList, err = tm.ProcessUpdate(peer2, bgpMessage2) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + // check type + path := pList[0] + assert.Equal(t, path.GetRouteFamily(), bgp.RF_IPv6_UC) + + // check PathAttribute + pathAttributes := bgpMessage2.Body.(*bgp.BGPUpdate).PathAttributes + + expectedNexthopAttr := pathAttributes[0] + attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_MP_REACH_NLRI) + pathNexthop := attr.(*bgp.PathAttributeMpReachNLRI) + assert.Equal(t, expectedNexthopAttr, pathNexthop) + + expectedOrigin := pathAttributes[1] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_ORIGIN) + pathOrigin := attr.(*bgp.PathAttributeOrigin) + assert.Equal(t, expectedOrigin, pathOrigin) + + expectedAsPath := pathAttributes[2] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_AS_PATH) + pathAspath := attr.(*bgp.PathAttributeAsPath) + assert.Equal(t, expectedAsPath, pathAspath) + + expectedMed := pathAttributes[3] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_MULTI_EXIT_DISC) + pathMed := attr.(*bgp.PathAttributeMultiExitDisc) + assert.Equal(t, expectedMed, pathMed) + + // check PathAttribute length + assert.Equal(t, 4, len(path.GetPathAttrs())) + + // check destination + expectedPrefix := "2002:223:123:1::/64" + assert.Equal(t, expectedPrefix, path.getPrefix()) + // check nexthop + expectedNexthop := "2001::192:168:100:1" + assert.Equal(t, expectedNexthop, path.GetNexthop().String()) + +} + +// test: compare Origin +func TestProcessBGPUpdate_4_select_low_origin_ipv4(t *testing.T) { + + tm := NewTableManager([]bgp.RouteFamily{bgp.RF_IPv4_UC}) + + // low origin message + origin1 := bgp.NewPathAttributeOrigin(1) + aspath1 := createAsPathAttribute([]uint32{65200, 65000}) + nexthop1 := bgp.NewPathAttributeNextHop("192.168.50.1") + med1 := bgp.NewPathAttributeMultiExitDisc(100) + localpref1 := bgp.NewPathAttributeLocalPref(100) + + pathAttributes1 := []bgp.PathAttributeInterface{ + origin1, aspath1, nexthop1, med1, localpref1, + } + nlri1 := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.10.0")} + bgpMessage1 := bgp.NewBGPUpdateMessage(nil, pathAttributes1, nlri1) + + // high origin message + origin2 := bgp.NewPathAttributeOrigin(0) + aspath2 := createAsPathAttribute([]uint32{65100, 65000}) + nexthop2 := bgp.NewPathAttributeNextHop("192.168.100.1") + med2 := bgp.NewPathAttributeMultiExitDisc(100) + localpref2 := bgp.NewPathAttributeLocalPref(100) + + pathAttributes2 := []bgp.PathAttributeInterface{ + origin2, aspath2, nexthop2, med2, localpref2, + } + nlri2 := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.10.0")} + bgpMessage2 := bgp.NewBGPUpdateMessage(nil, pathAttributes2, nlri2) + + peer1 := peerR1() + pList, err := tm.ProcessUpdate(peer1, bgpMessage1) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + peer2 := peerR2() + pList, err = tm.ProcessUpdate(peer2, bgpMessage2) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + // check type + path := pList[0] + assert.Equal(t, path.GetRouteFamily(), bgp.RF_IPv4_UC) + + // check PathAttribute + pathAttributes := bgpMessage2.Body.(*bgp.BGPUpdate).PathAttributes + expectedOrigin := pathAttributes[0] + attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_ORIGIN) + pathOrigin := attr.(*bgp.PathAttributeOrigin) + assert.Equal(t, expectedOrigin, pathOrigin) + + expectedAsPath := pathAttributes[1] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_AS_PATH) + pathAspath := attr.(*bgp.PathAttributeAsPath) + assert.Equal(t, expectedAsPath, pathAspath) + + expectedNexthopAttr := pathAttributes[2] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_NEXT_HOP) + pathNexthop := attr.(*bgp.PathAttributeNextHop) + assert.Equal(t, expectedNexthopAttr, pathNexthop) + + expectedMed := pathAttributes[3] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_MULTI_EXIT_DISC) + pathMed := attr.(*bgp.PathAttributeMultiExitDisc) + assert.Equal(t, expectedMed, pathMed) + + // check PathAttribute length + assert.Equal(t, len(pathAttributes2), len(path.GetPathAttrs())) + + // check destination + expectedPrefix := "10.10.10.0/24" + assert.Equal(t, expectedPrefix, path.getPrefix()) + // check nexthop + expectedNexthop := "192.168.100.1" + assert.Equal(t, expectedNexthop, path.GetNexthop().String()) + +} + +func TestProcessBGPUpdate_4_select_low_origin_ipv6(t *testing.T) { + + tm := NewTableManager([]bgp.RouteFamily{bgp.RF_IPv6_UC}) + + origin1 := bgp.NewPathAttributeOrigin(1) + aspath1 := createAsPathAttribute([]uint32{65200, 65000}) + mp_reach1 := createMpReach("2001::192:168:50:1", + []bgp.AddrPrefixInterface{bgp.NewIPv6AddrPrefix(64, "2001:123:123:1::")}) + med1 := bgp.NewPathAttributeMultiExitDisc(100) + localpref1 := bgp.NewPathAttributeLocalPref(100) + + pathAttributes1 := []bgp.PathAttributeInterface{ + mp_reach1, origin1, aspath1, med1, localpref1, + } + + bgpMessage1 := bgp.NewBGPUpdateMessage(nil, pathAttributes1, nil) + + origin2 := bgp.NewPathAttributeOrigin(0) + aspath2 := createAsPathAttribute([]uint32{65100, 65000}) + mp_reach2 := createMpReach("2001::192:168:100:1", + []bgp.AddrPrefixInterface{bgp.NewIPv6AddrPrefix(64, "2001:123:123:1::")}) + med2 := bgp.NewPathAttributeMultiExitDisc(100) + localpref2 := bgp.NewPathAttributeLocalPref(200) + + pathAttributes2 := []bgp.PathAttributeInterface{ + mp_reach2, origin2, aspath2, med2, localpref2, + } + + bgpMessage2 := bgp.NewBGPUpdateMessage(nil, pathAttributes2, nil) + + peer1 := peerR1() + pList, err := tm.ProcessUpdate(peer1, bgpMessage1) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + peer2 := peerR2() + pList, err = tm.ProcessUpdate(peer2, bgpMessage2) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + // check type + path := pList[0] + assert.Equal(t, path.GetRouteFamily(), bgp.RF_IPv6_UC) + + // check PathAttribute + pathAttributes := bgpMessage2.Body.(*bgp.BGPUpdate).PathAttributes + + expectedNexthopAttr := pathAttributes[0] + attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_MP_REACH_NLRI) + pathNexthop := attr.(*bgp.PathAttributeMpReachNLRI) + assert.Equal(t, expectedNexthopAttr, pathNexthop) + + expectedOrigin := pathAttributes[1] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_ORIGIN) + pathOrigin := attr.(*bgp.PathAttributeOrigin) + assert.Equal(t, expectedOrigin, pathOrigin) + + expectedAsPath := pathAttributes[2] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_AS_PATH) + pathAspath := attr.(*bgp.PathAttributeAsPath) + assert.Equal(t, expectedAsPath, pathAspath) + + expectedMed := pathAttributes[3] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_MULTI_EXIT_DISC) + pathMed := attr.(*bgp.PathAttributeMultiExitDisc) + assert.Equal(t, expectedMed, pathMed) + + // check PathAttribute length + assert.Equal(t, 5, len(path.GetPathAttrs())) + + // check destination + expectedPrefix := "2001:123:123:1::/64" + assert.Equal(t, expectedPrefix, path.getPrefix()) + // check nexthop + expectedNexthop := "2001::192:168:100:1" + assert.Equal(t, expectedNexthop, path.GetNexthop().String()) + +} + +// test: compare MED +func TestProcessBGPUpdate_5_select_low_med_ipv4(t *testing.T) { + + tm := NewTableManager([]bgp.RouteFamily{bgp.RF_IPv4_UC}) + + // low origin message + origin1 := bgp.NewPathAttributeOrigin(0) + aspath1 := createAsPathAttribute([]uint32{65200, 65000}) + nexthop1 := bgp.NewPathAttributeNextHop("192.168.50.1") + med1 := bgp.NewPathAttributeMultiExitDisc(500) + localpref1 := bgp.NewPathAttributeLocalPref(100) + + pathAttributes1 := []bgp.PathAttributeInterface{ + origin1, aspath1, nexthop1, med1, localpref1, + } + nlri1 := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.10.0")} + bgpMessage1 := bgp.NewBGPUpdateMessage(nil, pathAttributes1, nlri1) + + // high origin message + origin2 := bgp.NewPathAttributeOrigin(0) + aspath2 := createAsPathAttribute([]uint32{65100, 65000}) + nexthop2 := bgp.NewPathAttributeNextHop("192.168.100.1") + med2 := bgp.NewPathAttributeMultiExitDisc(100) + localpref2 := bgp.NewPathAttributeLocalPref(100) + + pathAttributes2 := []bgp.PathAttributeInterface{ + origin2, aspath2, nexthop2, med2, localpref2, + } + nlri2 := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.10.0")} + bgpMessage2 := bgp.NewBGPUpdateMessage(nil, pathAttributes2, nlri2) + + peer1 := peerR1() + pList, err := tm.ProcessUpdate(peer1, bgpMessage1) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + peer2 := peerR2() + pList, err = tm.ProcessUpdate(peer2, bgpMessage2) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + // check type + path := pList[0] + assert.Equal(t, path.GetRouteFamily(), bgp.RF_IPv4_UC) + + // check PathAttribute + pathAttributes := bgpMessage2.Body.(*bgp.BGPUpdate).PathAttributes + expectedOrigin := pathAttributes[0] + attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_ORIGIN) + pathOrigin := attr.(*bgp.PathAttributeOrigin) + assert.Equal(t, expectedOrigin, pathOrigin) + + expectedAsPath := pathAttributes[1] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_AS_PATH) + pathAspath := attr.(*bgp.PathAttributeAsPath) + assert.Equal(t, expectedAsPath, pathAspath) + + expectedNexthopAttr := pathAttributes[2] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_NEXT_HOP) + pathNexthop := attr.(*bgp.PathAttributeNextHop) + assert.Equal(t, expectedNexthopAttr, pathNexthop) + + expectedMed := pathAttributes[3] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_MULTI_EXIT_DISC) + pathMed := attr.(*bgp.PathAttributeMultiExitDisc) + assert.Equal(t, expectedMed, pathMed) + + // check PathAttribute length + assert.Equal(t, len(pathAttributes2), len(path.GetPathAttrs())) + + // check destination + expectedPrefix := "10.10.10.0/24" + assert.Equal(t, expectedPrefix, path.getPrefix()) + // check nexthop + expectedNexthop := "192.168.100.1" + assert.Equal(t, expectedNexthop, path.GetNexthop().String()) + +} + +func TestProcessBGPUpdate_5_select_low_med_ipv6(t *testing.T) { + + tm := NewTableManager([]bgp.RouteFamily{bgp.RF_IPv6_UC}) + + origin1 := bgp.NewPathAttributeOrigin(0) + aspath1 := createAsPathAttribute([]uint32{65200, 65000}) + mp_reach1 := createMpReach("2001::192:168:50:1", + []bgp.AddrPrefixInterface{bgp.NewIPv6AddrPrefix(64, "2001:123:123:1::")}) + med1 := bgp.NewPathAttributeMultiExitDisc(500) + localpref1 := bgp.NewPathAttributeLocalPref(100) + + pathAttributes1 := []bgp.PathAttributeInterface{ + mp_reach1, origin1, aspath1, med1, localpref1, + } + + bgpMessage1 := bgp.NewBGPUpdateMessage(nil, pathAttributes1, nil) + + origin2 := bgp.NewPathAttributeOrigin(0) + aspath2 := createAsPathAttribute([]uint32{65100, 65000}) + mp_reach2 := createMpReach("2001::192:168:100:1", + []bgp.AddrPrefixInterface{bgp.NewIPv6AddrPrefix(64, "2001:123:123:1::")}) + med2 := bgp.NewPathAttributeMultiExitDisc(200) + localpref2 := bgp.NewPathAttributeLocalPref(100) + + pathAttributes2 := []bgp.PathAttributeInterface{ + mp_reach2, origin2, aspath2, med2, localpref2, + } + + bgpMessage2 := bgp.NewBGPUpdateMessage(nil, pathAttributes2, nil) + + peer1 := peerR1() + pList, err := tm.ProcessUpdate(peer1, bgpMessage1) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + peer2 := peerR2() + pList, err = tm.ProcessUpdate(peer2, bgpMessage2) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + // check type + path := pList[0] + assert.Equal(t, path.GetRouteFamily(), bgp.RF_IPv6_UC) + + // check PathAttribute + pathAttributes := bgpMessage2.Body.(*bgp.BGPUpdate).PathAttributes + + expectedNexthopAttr := pathAttributes[0] + attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_MP_REACH_NLRI) + pathNexthop := attr.(*bgp.PathAttributeMpReachNLRI) + assert.Equal(t, expectedNexthopAttr, pathNexthop) + + expectedOrigin := pathAttributes[1] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_ORIGIN) + pathOrigin := attr.(*bgp.PathAttributeOrigin) + assert.Equal(t, expectedOrigin, pathOrigin) + + expectedAsPath := pathAttributes[2] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_AS_PATH) + pathAspath := attr.(*bgp.PathAttributeAsPath) + assert.Equal(t, expectedAsPath, pathAspath) + + expectedMed := pathAttributes[3] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_MULTI_EXIT_DISC) + pathMed := attr.(*bgp.PathAttributeMultiExitDisc) + assert.Equal(t, expectedMed, pathMed) + + // check PathAttribute length + assert.Equal(t, 5, len(path.GetPathAttrs())) + + // check destination + expectedPrefix := "2001:123:123:1::/64" + assert.Equal(t, expectedPrefix, path.getPrefix()) + // check nexthop + expectedNexthop := "2001::192:168:100:1" + assert.Equal(t, expectedNexthop, path.GetNexthop().String()) + +} + +// test: compare AS_NUMBER(prefer eBGP path) +func TestProcessBGPUpdate_6_select_ebgp_path_ipv4(t *testing.T) { + + tm := NewTableManager([]bgp.RouteFamily{bgp.RF_IPv4_UC}) + + // low origin message + origin1 := bgp.NewPathAttributeOrigin(0) + aspath1 := createAsPathAttribute([]uint32{65000, 65200}) + nexthop1 := bgp.NewPathAttributeNextHop("192.168.50.1") + med1 := bgp.NewPathAttributeMultiExitDisc(200) + localpref1 := bgp.NewPathAttributeLocalPref(100) + + pathAttributes1 := []bgp.PathAttributeInterface{ + origin1, aspath1, nexthop1, med1, localpref1, + } + nlri1 := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.10.0")} + bgpMessage1 := bgp.NewBGPUpdateMessage(nil, pathAttributes1, nlri1) + + // high origin message + origin2 := bgp.NewPathAttributeOrigin(0) + aspath2 := createAsPathAttribute([]uint32{65100, 65000}) + nexthop2 := bgp.NewPathAttributeNextHop("192.168.100.1") + med2 := bgp.NewPathAttributeMultiExitDisc(200) + localpref2 := bgp.NewPathAttributeLocalPref(100) + + pathAttributes2 := []bgp.PathAttributeInterface{ + origin2, aspath2, nexthop2, med2, localpref2, + } + nlri2 := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.10.0")} + bgpMessage2 := bgp.NewBGPUpdateMessage(nil, pathAttributes2, nlri2) + + peer1 := peerR1() + pList, err := tm.ProcessUpdate(peer1, bgpMessage1) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + peer2 := peerR2() + pList, err = tm.ProcessUpdate(peer2, bgpMessage2) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + // check type + path := pList[0] + assert.Equal(t, path.GetRouteFamily(), bgp.RF_IPv4_UC) + + // check PathAttribute + pathAttributes := bgpMessage2.Body.(*bgp.BGPUpdate).PathAttributes + expectedOrigin := pathAttributes[0] + attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_ORIGIN) + pathOrigin := attr.(*bgp.PathAttributeOrigin) + assert.Equal(t, expectedOrigin, pathOrigin) + + expectedAsPath := pathAttributes[1] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_AS_PATH) + pathAspath := attr.(*bgp.PathAttributeAsPath) + assert.Equal(t, expectedAsPath, pathAspath) + + expectedNexthopAttr := pathAttributes[2] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_NEXT_HOP) + pathNexthop := attr.(*bgp.PathAttributeNextHop) + assert.Equal(t, expectedNexthopAttr, pathNexthop) + + expectedMed := pathAttributes[3] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_MULTI_EXIT_DISC) + pathMed := attr.(*bgp.PathAttributeMultiExitDisc) + assert.Equal(t, expectedMed, pathMed) + + // check PathAttribute length + assert.Equal(t, len(pathAttributes2), len(path.GetPathAttrs())) + + // check destination + expectedPrefix := "10.10.10.0/24" + assert.Equal(t, expectedPrefix, path.getPrefix()) + // check nexthop + expectedNexthop := "192.168.100.1" + assert.Equal(t, expectedNexthop, path.GetNexthop().String()) + +} + +func TestProcessBGPUpdate_6_select_ebgp_path_ipv6(t *testing.T) { + + tm := NewTableManager([]bgp.RouteFamily{bgp.RF_IPv6_UC}) + + origin1 := bgp.NewPathAttributeOrigin(0) + aspath1 := createAsPathAttribute([]uint32{65000, 65200}) + mp_reach1 := createMpReach("2001::192:168:50:1", + []bgp.AddrPrefixInterface{bgp.NewIPv6AddrPrefix(64, "2001:123:123:1::")}) + med1 := bgp.NewPathAttributeMultiExitDisc(200) + localpref1 := bgp.NewPathAttributeLocalPref(100) + + pathAttributes1 := []bgp.PathAttributeInterface{ + mp_reach1, origin1, aspath1, med1, localpref1, + } + + bgpMessage1 := bgp.NewBGPUpdateMessage(nil, pathAttributes1, nil) + + origin2 := bgp.NewPathAttributeOrigin(0) + aspath2 := createAsPathAttribute([]uint32{65100, 65200}) + mp_reach2 := createMpReach("2001::192:168:100:1", + []bgp.AddrPrefixInterface{bgp.NewIPv6AddrPrefix(64, "2001:123:123:1::")}) + med2 := bgp.NewPathAttributeMultiExitDisc(200) + localpref2 := bgp.NewPathAttributeLocalPref(100) + + pathAttributes2 := []bgp.PathAttributeInterface{ + mp_reach2, origin2, aspath2, med2, localpref2, + } + + bgpMessage2 := bgp.NewBGPUpdateMessage(nil, pathAttributes2, nil) + + peer1 := peerR1() + pList, err := tm.ProcessUpdate(peer1, bgpMessage1) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + peer2 := peerR2() + pList, err = tm.ProcessUpdate(peer2, bgpMessage2) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + // check type + path := pList[0] + assert.Equal(t, path.GetRouteFamily(), bgp.RF_IPv6_UC) + + // check PathAttribute + pathAttributes := bgpMessage2.Body.(*bgp.BGPUpdate).PathAttributes + + expectedNexthopAttr := pathAttributes[0] + attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_MP_REACH_NLRI) + pathNexthop := attr.(*bgp.PathAttributeMpReachNLRI) + assert.Equal(t, expectedNexthopAttr, pathNexthop) + + expectedOrigin := pathAttributes[1] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_ORIGIN) + pathOrigin := attr.(*bgp.PathAttributeOrigin) + assert.Equal(t, expectedOrigin, pathOrigin) + + expectedAsPath := pathAttributes[2] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_AS_PATH) + pathAspath := attr.(*bgp.PathAttributeAsPath) + assert.Equal(t, expectedAsPath, pathAspath) + + expectedMed := pathAttributes[3] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_MULTI_EXIT_DISC) + pathMed := attr.(*bgp.PathAttributeMultiExitDisc) + assert.Equal(t, expectedMed, pathMed) + + // check PathAttribute length + assert.Equal(t, 5, len(path.GetPathAttrs())) + + // check destination + expectedPrefix := "2001:123:123:1::/64" + assert.Equal(t, expectedPrefix, path.getPrefix()) + // check nexthop + expectedNexthop := "2001::192:168:100:1" + assert.Equal(t, expectedNexthop, path.GetNexthop().String()) + +} + +// test: compare IGP cost -> N/A + +// test: compare Router ID +func TestProcessBGPUpdate_7_select_low_routerid_path_ipv4(t *testing.T) { + + tm := NewTableManager([]bgp.RouteFamily{bgp.RF_IPv4_UC}) + SelectionOptions.ExternalCompareRouterId = true + + // low origin message + origin1 := bgp.NewPathAttributeOrigin(0) + aspath1 := createAsPathAttribute([]uint32{65000, 65200}) + nexthop1 := bgp.NewPathAttributeNextHop("192.168.50.1") + med1 := bgp.NewPathAttributeMultiExitDisc(200) + localpref1 := bgp.NewPathAttributeLocalPref(100) + + pathAttributes1 := []bgp.PathAttributeInterface{ + origin1, aspath1, nexthop1, med1, localpref1, + } + nlri1 := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.10.0")} + bgpMessage1 := bgp.NewBGPUpdateMessage(nil, pathAttributes1, nlri1) + + // high origin message + origin2 := bgp.NewPathAttributeOrigin(0) + aspath2 := createAsPathAttribute([]uint32{65000, 65100}) + nexthop2 := bgp.NewPathAttributeNextHop("192.168.100.1") + med2 := bgp.NewPathAttributeMultiExitDisc(200) + localpref2 := bgp.NewPathAttributeLocalPref(100) + + pathAttributes2 := []bgp.PathAttributeInterface{ + origin2, aspath2, nexthop2, med2, localpref2, + } + nlri2 := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.10.0")} + bgpMessage2 := bgp.NewBGPUpdateMessage(nil, pathAttributes2, nlri2) + + peer1 := peerR1() + pList, err := tm.ProcessUpdate(peer1, bgpMessage1) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + peer3 := peerR3() + pList, err = tm.ProcessUpdate(peer3, bgpMessage2) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + // check type + path := pList[0] + assert.Equal(t, path.GetRouteFamily(), bgp.RF_IPv4_UC) + + // check PathAttribute + pathAttributes := bgpMessage2.Body.(*bgp.BGPUpdate).PathAttributes + expectedOrigin := pathAttributes[0] + attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_ORIGIN) + pathOrigin := attr.(*bgp.PathAttributeOrigin) + assert.Equal(t, expectedOrigin, pathOrigin) + + expectedAsPath := pathAttributes[1] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_AS_PATH) + pathAspath := attr.(*bgp.PathAttributeAsPath) + assert.Equal(t, expectedAsPath, pathAspath) + + expectedNexthopAttr := pathAttributes[2] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_NEXT_HOP) + pathNexthop := attr.(*bgp.PathAttributeNextHop) + assert.Equal(t, expectedNexthopAttr, pathNexthop) + + expectedMed := pathAttributes[3] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_MULTI_EXIT_DISC) + pathMed := attr.(*bgp.PathAttributeMultiExitDisc) + assert.Equal(t, expectedMed, pathMed) + + // check PathAttribute length + assert.Equal(t, len(pathAttributes2), len(path.GetPathAttrs())) + + // check destination + expectedPrefix := "10.10.10.0/24" + assert.Equal(t, expectedPrefix, path.getPrefix()) + // check nexthop + expectedNexthop := "192.168.100.1" + assert.Equal(t, expectedNexthop, path.GetNexthop().String()) + +} + +func TestProcessBGPUpdate_7_select_low_routerid_path_ipv6(t *testing.T) { + + tm := NewTableManager([]bgp.RouteFamily{bgp.RF_IPv6_UC}) + + origin1 := bgp.NewPathAttributeOrigin(0) + aspath1 := createAsPathAttribute([]uint32{65000, 65200}) + mp_reach1 := createMpReach("2001::192:168:50:1", + []bgp.AddrPrefixInterface{bgp.NewIPv6AddrPrefix(64, "2001:123:123:1::")}) + med1 := bgp.NewPathAttributeMultiExitDisc(200) + localpref1 := bgp.NewPathAttributeLocalPref(100) + + pathAttributes1 := []bgp.PathAttributeInterface{ + mp_reach1, origin1, aspath1, med1, localpref1, + } + + bgpMessage1 := bgp.NewBGPUpdateMessage(nil, pathAttributes1, nil) + + origin2 := bgp.NewPathAttributeOrigin(0) + aspath2 := createAsPathAttribute([]uint32{65100, 65200}) + mp_reach2 := createMpReach("2001::192:168:100:1", + []bgp.AddrPrefixInterface{bgp.NewIPv6AddrPrefix(64, "2001:123:123:1::")}) + med2 := bgp.NewPathAttributeMultiExitDisc(200) + localpref2 := bgp.NewPathAttributeLocalPref(100) + + pathAttributes2 := []bgp.PathAttributeInterface{ + mp_reach2, origin2, aspath2, med2, localpref2, + } + + bgpMessage2 := bgp.NewBGPUpdateMessage(nil, pathAttributes2, nil) + + peer1 := peerR1() + pList, err := tm.ProcessUpdate(peer1, bgpMessage1) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + peer3 := peerR3() + pList, err = tm.ProcessUpdate(peer3, bgpMessage2) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + // check type + path := pList[0] + assert.Equal(t, path.GetRouteFamily(), bgp.RF_IPv6_UC) + + // check PathAttribute + pathAttributes := bgpMessage2.Body.(*bgp.BGPUpdate).PathAttributes + + expectedNexthopAttr := pathAttributes[0] + attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_MP_REACH_NLRI) + pathNexthop := attr.(*bgp.PathAttributeMpReachNLRI) + assert.Equal(t, expectedNexthopAttr, pathNexthop) + + expectedOrigin := pathAttributes[1] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_ORIGIN) + pathOrigin := attr.(*bgp.PathAttributeOrigin) + assert.Equal(t, expectedOrigin, pathOrigin) + + expectedAsPath := pathAttributes[2] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_AS_PATH) + pathAspath := attr.(*bgp.PathAttributeAsPath) + assert.Equal(t, expectedAsPath, pathAspath) + + expectedMed := pathAttributes[3] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_MULTI_EXIT_DISC) + pathMed := attr.(*bgp.PathAttributeMultiExitDisc) + assert.Equal(t, expectedMed, pathMed) + + // check PathAttribute length + assert.Equal(t, 5, len(path.GetPathAttrs())) + + // check destination + expectedPrefix := "2001:123:123:1::/64" + assert.Equal(t, expectedPrefix, path.getPrefix()) + // check nexthop + expectedNexthop := "2001::192:168:100:1" + assert.Equal(t, expectedNexthop, path.GetNexthop().String()) + +} + +// test: withdraw and mpunreach path +func TestProcessBGPUpdate_8_withdraw_path_ipv4(t *testing.T) { + + tm := NewTableManager([]bgp.RouteFamily{bgp.RF_IPv4_UC}) + + // path1 + origin1 := bgp.NewPathAttributeOrigin(0) + aspath1 := createAsPathAttribute([]uint32{65000}) + nexthop1 := bgp.NewPathAttributeNextHop("192.168.50.1") + med1 := bgp.NewPathAttributeMultiExitDisc(200) + localpref1 := bgp.NewPathAttributeLocalPref(100) + + pathAttributes1 := []bgp.PathAttributeInterface{ + origin1, aspath1, nexthop1, med1, localpref1, + } + nlri1 := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.10.0")} + bgpMessage1 := bgp.NewBGPUpdateMessage(nil, pathAttributes1, nlri1) + + // path 2 + origin2 := bgp.NewPathAttributeOrigin(0) + aspath2 := createAsPathAttribute([]uint32{65100, 65000}) + nexthop2 := bgp.NewPathAttributeNextHop("192.168.100.1") + med2 := bgp.NewPathAttributeMultiExitDisc(200) + localpref2 := bgp.NewPathAttributeLocalPref(200) + + pathAttributes2 := []bgp.PathAttributeInterface{ + origin2, aspath2, nexthop2, med2, localpref2, + } + nlri2 := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.10.0")} + bgpMessage2 := bgp.NewBGPUpdateMessage(nil, pathAttributes2, nlri2) + + peer1 := peerR1() + pList, err := tm.ProcessUpdate(peer1, bgpMessage1) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + peer2 := peerR2() + pList, err = tm.ProcessUpdate(peer2, bgpMessage2) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + // check type + path := pList[0] + assert.Equal(t, path.GetRouteFamily(), bgp.RF_IPv4_UC) + + // check PathAttribute + checkPattr := func(expected *bgp.BGPMessage, actual *Path) { + pathAttributes := expected.Body.(*bgp.BGPUpdate).PathAttributes + expectedOrigin := pathAttributes[0] + attr := actual.getPathAttr(bgp.BGP_ATTR_TYPE_ORIGIN) + pathOrigin := attr.(*bgp.PathAttributeOrigin) + assert.Equal(t, expectedOrigin, pathOrigin) + + expectedAsPath := pathAttributes[1] + attr = actual.getPathAttr(bgp.BGP_ATTR_TYPE_AS_PATH) + pathAspath := attr.(*bgp.PathAttributeAsPath) + assert.Equal(t, expectedAsPath, pathAspath) + + expectedNexthopAttr := pathAttributes[2] + attr = actual.getPathAttr(bgp.BGP_ATTR_TYPE_NEXT_HOP) + pathNexthop := attr.(*bgp.PathAttributeNextHop) + assert.Equal(t, expectedNexthopAttr, pathNexthop) + + expectedMed := pathAttributes[3] + attr = actual.getPathAttr(bgp.BGP_ATTR_TYPE_MULTI_EXIT_DISC) + pathMed := attr.(*bgp.PathAttributeMultiExitDisc) + assert.Equal(t, expectedMed, pathMed) + + // check PathAttribute length + assert.Equal(t, len(pathAttributes), len(path.GetPathAttrs())) + } + checkPattr(bgpMessage2, path) + // check destination + expectedPrefix := "10.10.10.0/24" + assert.Equal(t, expectedPrefix, path.getPrefix()) + // check nexthop + expectedNexthop := "192.168.100.1" + assert.Equal(t, expectedNexthop, path.GetNexthop().String()) + + //withdraw path + w1 := bgp.NewIPAddrPrefix(24, "10.10.10.0") + w := []*bgp.IPAddrPrefix{w1} + bgpMessage3 := bgp.NewBGPUpdateMessage(w, nil, nil) + + pList, err = tm.ProcessUpdate(peer2, bgpMessage3) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + path = pList[0] + assert.Equal(t, path.GetRouteFamily(), bgp.RF_IPv4_UC) + + checkPattr(bgpMessage1, path) + // check destination + expectedPrefix = "10.10.10.0/24" + assert.Equal(t, expectedPrefix, path.getPrefix()) + // check nexthop + expectedNexthop = "192.168.50.1" + assert.Equal(t, expectedNexthop, path.GetNexthop().String()) +} + +// TODO MP_UNREACH +func TestProcessBGPUpdate_8_mpunreach_path_ipv6(t *testing.T) { + + tm := NewTableManager([]bgp.RouteFamily{bgp.RF_IPv6_UC}) + + origin1 := bgp.NewPathAttributeOrigin(0) + aspath1 := createAsPathAttribute([]uint32{65000}) + mp_reach1 := createMpReach("2001::192:168:50:1", + []bgp.AddrPrefixInterface{bgp.NewIPv6AddrPrefix(64, "2001:123:123:1::")}) + med1 := bgp.NewPathAttributeMultiExitDisc(200) + localpref1 := bgp.NewPathAttributeLocalPref(100) + + pathAttributes1 := []bgp.PathAttributeInterface{ + mp_reach1, origin1, aspath1, med1, localpref1, + } + + bgpMessage1 := bgp.NewBGPUpdateMessage(nil, pathAttributes1, nil) + + origin2 := bgp.NewPathAttributeOrigin(0) + aspath2 := createAsPathAttribute([]uint32{65100, 65000}) + mp_reach2 := createMpReach("2001::192:168:100:1", + []bgp.AddrPrefixInterface{bgp.NewIPv6AddrPrefix(64, "2001:123:123:1::")}) + med2 := bgp.NewPathAttributeMultiExitDisc(200) + localpref2 := bgp.NewPathAttributeLocalPref(200) + + pathAttributes2 := []bgp.PathAttributeInterface{ + mp_reach2, origin2, aspath2, med2, localpref2, + } + + bgpMessage2 := bgp.NewBGPUpdateMessage(nil, pathAttributes2, nil) + + peer1 := peerR1() + pList, err := tm.ProcessUpdate(peer1, bgpMessage1) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + peer2 := peerR2() + pList, err = tm.ProcessUpdate(peer2, bgpMessage2) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + // check type + path := pList[0] + assert.Equal(t, path.GetRouteFamily(), bgp.RF_IPv6_UC) + + // check PathAttribute + pathAttributes := bgpMessage2.Body.(*bgp.BGPUpdate).PathAttributes + + expectedNexthopAttr := pathAttributes[0] + attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_MP_REACH_NLRI) + pathNexthop := attr.(*bgp.PathAttributeMpReachNLRI) + assert.Equal(t, expectedNexthopAttr, pathNexthop) + + expectedOrigin := pathAttributes[1] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_ORIGIN) + pathOrigin := attr.(*bgp.PathAttributeOrigin) + assert.Equal(t, expectedOrigin, pathOrigin) + + expectedAsPath := pathAttributes[2] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_AS_PATH) + pathAspath := attr.(*bgp.PathAttributeAsPath) + assert.Equal(t, expectedAsPath, pathAspath) + + expectedMed := pathAttributes[3] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_MULTI_EXIT_DISC) + pathMed := attr.(*bgp.PathAttributeMultiExitDisc) + assert.Equal(t, expectedMed, pathMed) + + // check PathAttribute + checkPattr := func(expected *bgp.BGPMessage, actual *Path) { + pathAttributes := expected.Body.(*bgp.BGPUpdate).PathAttributes + + expectedNexthopAttr := pathAttributes[0] + attr = actual.getPathAttr(bgp.BGP_ATTR_TYPE_MP_REACH_NLRI) + pathNexthop := attr.(*bgp.PathAttributeMpReachNLRI) + assert.Equal(t, expectedNexthopAttr, pathNexthop) + + expectedOrigin := pathAttributes[1] + attr = actual.getPathAttr(bgp.BGP_ATTR_TYPE_ORIGIN) + pathOrigin := attr.(*bgp.PathAttributeOrigin) + assert.Equal(t, expectedOrigin, pathOrigin) + + expectedAsPath := pathAttributes[2] + attr = actual.getPathAttr(bgp.BGP_ATTR_TYPE_AS_PATH) + pathAspath := attr.(*bgp.PathAttributeAsPath) + assert.Equal(t, expectedAsPath, pathAspath) + + expectedMed := pathAttributes[3] + attr = actual.getPathAttr(bgp.BGP_ATTR_TYPE_MULTI_EXIT_DISC) + pathMed := attr.(*bgp.PathAttributeMultiExitDisc) + assert.Equal(t, expectedMed, pathMed) + // check PathAttribute length + assert.Equal(t, len(pathAttributes), len(path.GetPathAttrs())) + } + + checkPattr(bgpMessage2, path) + + // check destination + expectedPrefix := "2001:123:123:1::/64" + assert.Equal(t, expectedPrefix, path.getPrefix()) + // check nexthop + expectedNexthop := "2001::192:168:100:1" + assert.Equal(t, expectedNexthop, path.GetNexthop().String()) + + //mpunreach path + mp_unreach := createMpUNReach("2001:123:123:1::", 64) + bgpMessage3 := bgp.NewBGPUpdateMessage(nil, []bgp.PathAttributeInterface{mp_unreach}, nil) + + pList, err = tm.ProcessUpdate(peer2, bgpMessage3) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + path = pList[0] + assert.Equal(t, path.GetRouteFamily(), bgp.RF_IPv6_UC) + + checkPattr(bgpMessage1, path) + // check destination + expectedPrefix = "2001:123:123:1::/64" + assert.Equal(t, expectedPrefix, path.getPrefix()) + // check nexthop + expectedNexthop = "2001::192:168:50:1" + assert.Equal(t, expectedNexthop, path.GetNexthop().String()) + +} + +// handle bestpath lost +func TestProcessBGPUpdate_bestpath_lost_ipv4(t *testing.T) { + + tm := NewTableManager([]bgp.RouteFamily{bgp.RF_IPv4_UC}) + + // path1 + origin1 := bgp.NewPathAttributeOrigin(0) + aspath1 := createAsPathAttribute([]uint32{65000}) + nexthop1 := bgp.NewPathAttributeNextHop("192.168.50.1") + med1 := bgp.NewPathAttributeMultiExitDisc(200) + localpref1 := bgp.NewPathAttributeLocalPref(100) + + pathAttributes1 := []bgp.PathAttributeInterface{ + origin1, aspath1, nexthop1, med1, localpref1, + } + nlri1 := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.10.0")} + bgpMessage1 := bgp.NewBGPUpdateMessage(nil, pathAttributes1, nlri1) + + // path 1 withdraw + w1 := bgp.NewIPAddrPrefix(24, "10.10.10.0") + w := []*bgp.IPAddrPrefix{w1} + bgpMessage1_w := bgp.NewBGPUpdateMessage(w, nil, nil) + + peer1 := peerR1() + pList, err := tm.ProcessUpdate(peer1, bgpMessage1) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + pList, err = tm.ProcessUpdate(peer1, bgpMessage1_w) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, true) + assert.NoError(t, err) + + // check old best path + path := pList[0] + assert.Equal(t, path.GetRouteFamily(), bgp.RF_IPv4_UC) + + // check PathAttribute + checkPattr := func(expected *bgp.BGPMessage, actual *Path) { + pathAttributes := expected.Body.(*bgp.BGPUpdate).PathAttributes + expectedOrigin := pathAttributes[0] + attr := actual.getPathAttr(bgp.BGP_ATTR_TYPE_ORIGIN) + pathOrigin := attr.(*bgp.PathAttributeOrigin) + assert.Equal(t, expectedOrigin, pathOrigin) + + expectedAsPath := pathAttributes[1] + attr = actual.getPathAttr(bgp.BGP_ATTR_TYPE_AS_PATH) + pathAspath := attr.(*bgp.PathAttributeAsPath) + assert.Equal(t, expectedAsPath, pathAspath) + + expectedNexthopAttr := pathAttributes[2] + attr = actual.getPathAttr(bgp.BGP_ATTR_TYPE_NEXT_HOP) + pathNexthop := attr.(*bgp.PathAttributeNextHop) + assert.Equal(t, expectedNexthopAttr, pathNexthop) + + expectedMed := pathAttributes[3] + attr = actual.getPathAttr(bgp.BGP_ATTR_TYPE_MULTI_EXIT_DISC) + pathMed := attr.(*bgp.PathAttributeMultiExitDisc) + assert.Equal(t, expectedMed, pathMed) + + // check PathAttribute length + assert.Equal(t, len(pathAttributes), len(path.GetPathAttrs())) + } + + checkPattr(bgpMessage1, path) + // check destination + expectedPrefix := "10.10.10.0/24" + assert.Equal(t, expectedPrefix, path.getPrefix()) +} + +func TestProcessBGPUpdate_bestpath_lost_ipv6(t *testing.T) { + + tm := NewTableManager([]bgp.RouteFamily{bgp.RF_IPv6_UC}) + + origin1 := bgp.NewPathAttributeOrigin(0) + aspath1 := createAsPathAttribute([]uint32{65000}) + mp_reach1 := createMpReach("2001::192:168:50:1", + []bgp.AddrPrefixInterface{bgp.NewIPv6AddrPrefix(64, "2001:123:123:1::")}) + med1 := bgp.NewPathAttributeMultiExitDisc(200) + localpref1 := bgp.NewPathAttributeLocalPref(100) + + pathAttributes1 := []bgp.PathAttributeInterface{ + mp_reach1, origin1, aspath1, med1, localpref1, + } + + bgpMessage1 := bgp.NewBGPUpdateMessage(nil, pathAttributes1, nil) + + peer1 := peerR1() + pList, err := tm.ProcessUpdate(peer1, bgpMessage1) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + // path1 mpunreach + mp_unreach := createMpUNReach("2001:123:123:1::", 64) + bgpMessage1_w := bgp.NewBGPUpdateMessage(nil, []bgp.PathAttributeInterface{mp_unreach}, nil) + + pList, err = tm.ProcessUpdate(peer1, bgpMessage1_w) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, true) + assert.NoError(t, err) + + // check old best path + path := pList[0] + assert.Equal(t, path.GetRouteFamily(), bgp.RF_IPv6_UC) + + // check PathAttribute + checkPattr := func(expected *bgp.BGPMessage, actual *Path) { + pathAttributes := expected.Body.(*bgp.BGPUpdate).PathAttributes + + expectedNexthopAttr := pathAttributes[0] + attr := actual.getPathAttr(bgp.BGP_ATTR_TYPE_MP_REACH_NLRI) + pathNexthop := attr.(*bgp.PathAttributeMpReachNLRI) + assert.Equal(t, expectedNexthopAttr, pathNexthop) + + expectedOrigin := pathAttributes[1] + attr = actual.getPathAttr(bgp.BGP_ATTR_TYPE_ORIGIN) + pathOrigin := attr.(*bgp.PathAttributeOrigin) + assert.Equal(t, expectedOrigin, pathOrigin) + + expectedAsPath := pathAttributes[2] + attr = actual.getPathAttr(bgp.BGP_ATTR_TYPE_AS_PATH) + pathAspath := attr.(*bgp.PathAttributeAsPath) + assert.Equal(t, expectedAsPath, pathAspath) + + expectedMed := pathAttributes[3] + attr = actual.getPathAttr(bgp.BGP_ATTR_TYPE_MULTI_EXIT_DISC) + pathMed := attr.(*bgp.PathAttributeMultiExitDisc) + assert.Equal(t, expectedMed, pathMed) + // check PathAttribute length + assert.Equal(t, len(pathAttributes), len(path.GetPathAttrs())) + } + + checkPattr(bgpMessage1, path) + + // check destination + expectedPrefix := "2001:123:123:1::/64" + assert.Equal(t, expectedPrefix, path.getPrefix()) +} + +// test: implicit withdrawal case +func TestProcessBGPUpdate_implicit_withdrwal_ipv4(t *testing.T) { + + tm := NewTableManager([]bgp.RouteFamily{bgp.RF_IPv4_UC}) + + // path1 + origin1 := bgp.NewPathAttributeOrigin(0) + aspath1 := createAsPathAttribute([]uint32{65000, 65100, 65200}) + nexthop1 := bgp.NewPathAttributeNextHop("192.168.50.1") + med1 := bgp.NewPathAttributeMultiExitDisc(200) + localpref1 := bgp.NewPathAttributeLocalPref(100) + + pathAttributes1 := []bgp.PathAttributeInterface{ + origin1, aspath1, nexthop1, med1, localpref1, + } + nlri1 := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.10.0")} + bgpMessage1 := bgp.NewBGPUpdateMessage(nil, pathAttributes1, nlri1) + + // path 1 from same peer but short AS_PATH + origin2 := bgp.NewPathAttributeOrigin(0) + aspath2 := createAsPathAttribute([]uint32{65000, 65100}) + nexthop2 := bgp.NewPathAttributeNextHop("192.168.50.1") + med2 := bgp.NewPathAttributeMultiExitDisc(200) + localpref2 := bgp.NewPathAttributeLocalPref(100) + + pathAttributes2 := []bgp.PathAttributeInterface{ + origin2, aspath2, nexthop2, med2, localpref2, + } + nlri2 := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.10.0")} + bgpMessage2 := bgp.NewBGPUpdateMessage(nil, pathAttributes2, nlri2) + + peer1 := peerR1() + pList, err := tm.ProcessUpdate(peer1, bgpMessage1) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + pList, err = tm.ProcessUpdate(peer1, bgpMessage2) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + // check type + path := pList[0] + assert.Equal(t, path.GetRouteFamily(), bgp.RF_IPv4_UC) + + // check PathAttribute + checkPattr := func(expected *bgp.BGPMessage, actual *Path) { + pathAttributes := expected.Body.(*bgp.BGPUpdate).PathAttributes + expectedOrigin := pathAttributes[0] + attr := actual.getPathAttr(bgp.BGP_ATTR_TYPE_ORIGIN) + pathOrigin := attr.(*bgp.PathAttributeOrigin) + assert.Equal(t, expectedOrigin, pathOrigin) + + expectedAsPath := pathAttributes[1] + attr = actual.getPathAttr(bgp.BGP_ATTR_TYPE_AS_PATH) + pathAspath := attr.(*bgp.PathAttributeAsPath) + assert.Equal(t, expectedAsPath, pathAspath) + + expectedNexthopAttr := pathAttributes[2] + attr = actual.getPathAttr(bgp.BGP_ATTR_TYPE_NEXT_HOP) + pathNexthop := attr.(*bgp.PathAttributeNextHop) + assert.Equal(t, expectedNexthopAttr, pathNexthop) + + expectedMed := pathAttributes[3] + attr = actual.getPathAttr(bgp.BGP_ATTR_TYPE_MULTI_EXIT_DISC) + pathMed := attr.(*bgp.PathAttributeMultiExitDisc) + assert.Equal(t, expectedMed, pathMed) + + // check PathAttribute length + assert.Equal(t, len(pathAttributes), len(path.GetPathAttrs())) + } + checkPattr(bgpMessage2, path) + // check destination + expectedPrefix := "10.10.10.0/24" + assert.Equal(t, expectedPrefix, path.getPrefix()) + // check nexthop + expectedNexthop := "192.168.50.1" + assert.Equal(t, expectedNexthop, path.GetNexthop().String()) + +} + +func TestProcessBGPUpdate_implicit_withdrwal_ipv6(t *testing.T) { + + tm := NewTableManager([]bgp.RouteFamily{bgp.RF_IPv6_UC}) + + origin1 := bgp.NewPathAttributeOrigin(0) + aspath1 := createAsPathAttribute([]uint32{65000, 65100, 65200}) + mp_reach1 := createMpReach("2001::192:168:50:1", + []bgp.AddrPrefixInterface{bgp.NewIPv6AddrPrefix(64, "2001:123:123:1::")}) + med1 := bgp.NewPathAttributeMultiExitDisc(200) + localpref1 := bgp.NewPathAttributeLocalPref(200) + + pathAttributes1 := []bgp.PathAttributeInterface{ + mp_reach1, origin1, aspath1, med1, localpref1, + } + + bgpMessage1 := bgp.NewBGPUpdateMessage(nil, pathAttributes1, nil) + + origin2 := bgp.NewPathAttributeOrigin(0) + aspath2 := createAsPathAttribute([]uint32{65000, 65100}) + mp_reach2 := createMpReach("2001::192:168:50:1", + []bgp.AddrPrefixInterface{bgp.NewIPv6AddrPrefix(64, "2001:123:123:1::")}) + med2 := bgp.NewPathAttributeMultiExitDisc(200) + localpref2 := bgp.NewPathAttributeLocalPref(200) + + pathAttributes2 := []bgp.PathAttributeInterface{ + mp_reach2, origin2, aspath2, med2, localpref2, + } + + bgpMessage2 := bgp.NewBGPUpdateMessage(nil, pathAttributes2, nil) + + peer1 := peerR1() + pList, err := tm.ProcessUpdate(peer1, bgpMessage1) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + pList, err = tm.ProcessUpdate(peer1, bgpMessage2) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + // check type + path := pList[0] + assert.Equal(t, path.GetRouteFamily(), bgp.RF_IPv6_UC) + + // check PathAttribute + pathAttributes := bgpMessage2.Body.(*bgp.BGPUpdate).PathAttributes + + expectedNexthopAttr := pathAttributes[0] + attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_MP_REACH_NLRI) + pathNexthop := attr.(*bgp.PathAttributeMpReachNLRI) + assert.Equal(t, expectedNexthopAttr, pathNexthop) + + expectedOrigin := pathAttributes[1] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_ORIGIN) + pathOrigin := attr.(*bgp.PathAttributeOrigin) + assert.Equal(t, expectedOrigin, pathOrigin) + + expectedAsPath := pathAttributes[2] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_AS_PATH) + pathAspath := attr.(*bgp.PathAttributeAsPath) + assert.Equal(t, expectedAsPath, pathAspath) + + expectedMed := pathAttributes[3] + attr = path.getPathAttr(bgp.BGP_ATTR_TYPE_MULTI_EXIT_DISC) + pathMed := attr.(*bgp.PathAttributeMultiExitDisc) + assert.Equal(t, expectedMed, pathMed) + + // check PathAttribute + checkPattr := func(expected *bgp.BGPMessage, actual *Path) { + pathAttributes := expected.Body.(*bgp.BGPUpdate).PathAttributes + + expectedNexthopAttr := pathAttributes[0] + attr := actual.getPathAttr(bgp.BGP_ATTR_TYPE_MP_REACH_NLRI) + pathNexthop := attr.(*bgp.PathAttributeMpReachNLRI) + assert.Equal(t, expectedNexthopAttr, pathNexthop) + + expectedOrigin := pathAttributes[1] + attr = actual.getPathAttr(bgp.BGP_ATTR_TYPE_ORIGIN) + pathOrigin := attr.(*bgp.PathAttributeOrigin) + assert.Equal(t, expectedOrigin, pathOrigin) + + expectedAsPath := pathAttributes[2] + attr = actual.getPathAttr(bgp.BGP_ATTR_TYPE_AS_PATH) + pathAspath := attr.(*bgp.PathAttributeAsPath) + assert.Equal(t, expectedAsPath, pathAspath) + + expectedMed := pathAttributes[3] + attr = actual.getPathAttr(bgp.BGP_ATTR_TYPE_MULTI_EXIT_DISC) + pathMed := attr.(*bgp.PathAttributeMultiExitDisc) + assert.Equal(t, expectedMed, pathMed) + // check PathAttribute length + assert.Equal(t, len(pathAttributes), len(path.GetPathAttrs())) + } + + checkPattr(bgpMessage2, path) + + // check destination + expectedPrefix := "2001:123:123:1::/64" + assert.Equal(t, expectedPrefix, path.getPrefix()) + // check nexthop + expectedNexthop := "2001::192:168:50:1" + assert.Equal(t, expectedNexthop, path.GetNexthop().String()) + +} + +// check multiple paths +func TestProcessBGPUpdate_multiple_nlri_ipv4(t *testing.T) { + + tm := NewTableManager([]bgp.RouteFamily{bgp.RF_IPv4_UC}) + + createPathAttr := func(aspaths []uint32, nh string) []bgp.PathAttributeInterface { + origin := bgp.NewPathAttributeOrigin(0) + aspath := createAsPathAttribute(aspaths) + nexthop := bgp.NewPathAttributeNextHop(nh) + med := bgp.NewPathAttributeMultiExitDisc(200) + localpref := bgp.NewPathAttributeLocalPref(100) + pathAttr := []bgp.PathAttributeInterface{ + origin, aspath, nexthop, med, localpref, + } + return pathAttr + } + + // check PathAttribute + checkPattr := func(expected *bgp.BGPMessage, actual *Path) { + pathAttributes := expected.Body.(*bgp.BGPUpdate).PathAttributes + expectedOrigin := pathAttributes[0] + attr := actual.getPathAttr(bgp.BGP_ATTR_TYPE_ORIGIN) + pathOrigin := attr.(*bgp.PathAttributeOrigin) + assert.Equal(t, expectedOrigin, pathOrigin) + + expectedAsPath := pathAttributes[1] + attr = actual.getPathAttr(bgp.BGP_ATTR_TYPE_AS_PATH) + pathAspath := attr.(*bgp.PathAttributeAsPath) + assert.Equal(t, expectedAsPath, pathAspath) + + expectedNexthopAttr := pathAttributes[2] + attr = actual.getPathAttr(bgp.BGP_ATTR_TYPE_NEXT_HOP) + pathNexthop := attr.(*bgp.PathAttributeNextHop) + assert.Equal(t, expectedNexthopAttr, pathNexthop) + + expectedMed := pathAttributes[3] + attr = actual.getPathAttr(bgp.BGP_ATTR_TYPE_MULTI_EXIT_DISC) + pathMed := attr.(*bgp.PathAttributeMultiExitDisc) + assert.Equal(t, expectedMed, pathMed) + + // check PathAttribute length + assert.Equal(t, len(pathAttributes), len(actual.GetPathAttrs())) + } + + checkBestPathResult := func(rf bgp.RouteFamily, prefix, nexthop string, p *Path, m *bgp.BGPMessage) { + assert.Equal(t, p.GetRouteFamily(), rf) + checkPattr(m, p) + // check destination + assert.Equal(t, prefix, p.getPrefix()) + // check nexthop + assert.Equal(t, nexthop, p.GetNexthop().String()) + } + + // path1 + pathAttributes1 := createPathAttr([]uint32{65000, 65100, 65200}, "192.168.50.1") + nlri1 := []*bgp.IPAddrPrefix{ + bgp.NewIPAddrPrefix(24, "10.10.10.0"), + bgp.NewIPAddrPrefix(24, "20.20.20.0"), + bgp.NewIPAddrPrefix(24, "30.30.30.0"), + bgp.NewIPAddrPrefix(24, "40.40.40.0"), + bgp.NewIPAddrPrefix(24, "50.50.50.0")} + bgpMessage1 := bgp.NewBGPUpdateMessage(nil, pathAttributes1, nlri1) + + // path2 + pathAttributes2 := createPathAttr([]uint32{65000, 65100, 65300}, "192.168.50.1") + nlri2 := []*bgp.IPAddrPrefix{ + bgp.NewIPAddrPrefix(24, "11.11.11.0"), + bgp.NewIPAddrPrefix(24, "22.22.22.0"), + bgp.NewIPAddrPrefix(24, "33.33.33.0"), + bgp.NewIPAddrPrefix(24, "44.44.44.0"), + bgp.NewIPAddrPrefix(24, "55.55.55.0")} + bgpMessage2 := bgp.NewBGPUpdateMessage(nil, pathAttributes2, nlri2) + + // path3 + pathAttributes3 := createPathAttr([]uint32{65000, 65100, 65400}, "192.168.50.1") + nlri3 := []*bgp.IPAddrPrefix{ + bgp.NewIPAddrPrefix(24, "77.77.77.0"), + bgp.NewIPAddrPrefix(24, "88.88.88.0"), + } + bgpMessage3 := bgp.NewBGPUpdateMessage(nil, pathAttributes3, nlri3) + + // path4 + pathAttributes4 := createPathAttr([]uint32{65000, 65100, 65500}, "192.168.50.1") + nlri4 := []*bgp.IPAddrPrefix{ + bgp.NewIPAddrPrefix(24, "99.99.99.0"), + } + bgpMessage4 := bgp.NewBGPUpdateMessage(nil, pathAttributes4, nlri4) + + peer1 := peerR1() + pList, err := tm.ProcessUpdate(peer1, bgpMessage1) + assert.Equal(t, 5, len(pList)) + for _, p := range pList { + assert.Equal(t, p.IsWithdraw, false) + } + assert.NoError(t, err) + + checkBestPathResult(bgp.RF_IPv4_UC, "10.10.10.0/24", "192.168.50.1", pList[0], bgpMessage1) + checkBestPathResult(bgp.RF_IPv4_UC, "20.20.20.0/24", "192.168.50.1", pList[1], bgpMessage1) + checkBestPathResult(bgp.RF_IPv4_UC, "30.30.30.0/24", "192.168.50.1", pList[2], bgpMessage1) + checkBestPathResult(bgp.RF_IPv4_UC, "40.40.40.0/24", "192.168.50.1", pList[3], bgpMessage1) + checkBestPathResult(bgp.RF_IPv4_UC, "50.50.50.0/24", "192.168.50.1", pList[4], bgpMessage1) + + pList, err = tm.ProcessUpdate(peer1, bgpMessage2) + assert.Equal(t, 5, len(pList)) + for _, p := range pList { + assert.Equal(t, p.IsWithdraw, false) + } + assert.NoError(t, err) + + checkBestPathResult(bgp.RF_IPv4_UC, "11.11.11.0/24", "192.168.50.1", pList[0], bgpMessage2) + checkBestPathResult(bgp.RF_IPv4_UC, "22.22.22.0/24", "192.168.50.1", pList[1], bgpMessage2) + checkBestPathResult(bgp.RF_IPv4_UC, "33.33.33.0/24", "192.168.50.1", pList[2], bgpMessage2) + checkBestPathResult(bgp.RF_IPv4_UC, "44.44.44.0/24", "192.168.50.1", pList[3], bgpMessage2) + checkBestPathResult(bgp.RF_IPv4_UC, "55.55.55.0/24", "192.168.50.1", pList[4], bgpMessage2) + + pList, err = tm.ProcessUpdate(peer1, bgpMessage3) + assert.Equal(t, 2, len(pList)) + for _, p := range pList { + assert.Equal(t, p.IsWithdraw, false) + } + assert.NoError(t, err) + + pList, err = tm.ProcessUpdate(peer1, bgpMessage4) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + // check table + table := tm.Tables[bgp.RF_IPv4_UC] + assert.Equal(t, 13, len(table.GetDestinations())) + +} + +// check multiple paths +func TestProcessBGPUpdate_multiple_nlri_ipv6(t *testing.T) { + + tm := NewTableManager([]bgp.RouteFamily{bgp.RF_IPv6_UC}) + + createPathAttr := func(aspaths []uint32) []bgp.PathAttributeInterface { + origin := bgp.NewPathAttributeOrigin(0) + aspath := createAsPathAttribute(aspaths) + med := bgp.NewPathAttributeMultiExitDisc(100) + localpref := bgp.NewPathAttributeLocalPref(100) + pathAttr := []bgp.PathAttributeInterface{ + origin, aspath, med, localpref, + } + return pathAttr + } + + // check PathAttribute + checkPattr := func(expected *bgp.BGPMessage, actual *Path) { + pathAttributes := expected.Body.(*bgp.BGPUpdate).PathAttributes + pathNexthop := pathAttributes[4] + attr := actual.getPathAttr(bgp.BGP_ATTR_TYPE_MP_REACH_NLRI) + expectedNexthopAttr := attr.(*bgp.PathAttributeMpReachNLRI) + assert.Equal(t, expectedNexthopAttr, pathNexthop) + + expectedOrigin := pathAttributes[0] + attr = actual.getPathAttr(bgp.BGP_ATTR_TYPE_ORIGIN) + pathOrigin := attr.(*bgp.PathAttributeOrigin) + assert.Equal(t, expectedOrigin, pathOrigin) + + expectedAsPath := pathAttributes[1] + attr = actual.getPathAttr(bgp.BGP_ATTR_TYPE_AS_PATH) + pathAspath := attr.(*bgp.PathAttributeAsPath) + assert.Equal(t, expectedAsPath, pathAspath) + + expectedMed := pathAttributes[2] + attr = actual.getPathAttr(bgp.BGP_ATTR_TYPE_MULTI_EXIT_DISC) + pathMed := attr.(*bgp.PathAttributeMultiExitDisc) + assert.Equal(t, expectedMed, pathMed) + + expectedLocalpref := pathAttributes[3] + attr = actual.getPathAttr(bgp.BGP_ATTR_TYPE_LOCAL_PREF) + localpref := attr.(*bgp.PathAttributeLocalPref) + assert.Equal(t, expectedLocalpref, localpref) + + // check PathAttribute length + assert.Equal(t, len(pathAttributes), len(actual.GetPathAttrs())) + + } + + checkBestPathResult := func(rf bgp.RouteFamily, prefix, nexthop string, p *Path, m *bgp.BGPMessage) { + assert.Equal(t, p.GetRouteFamily(), rf) + checkPattr(m, p) + // check destination + assert.Equal(t, prefix, p.getPrefix()) + // check nexthop + assert.Equal(t, nexthop, p.GetNexthop().String()) + } + + // path1 + pathAttributes1 := createPathAttr([]uint32{65000, 65100, 65200}) + mpreach1 := createMpReach("2001::192:168:50:1", []bgp.AddrPrefixInterface{ + bgp.NewIPv6AddrPrefix(64, "2001:123:1210:11::"), + bgp.NewIPv6AddrPrefix(64, "2001:123:1220:11::"), + bgp.NewIPv6AddrPrefix(64, "2001:123:1230:11::"), + bgp.NewIPv6AddrPrefix(64, "2001:123:1240:11::"), + bgp.NewIPv6AddrPrefix(64, "2001:123:1250:11::"), + }) + pathAttributes1 = append(pathAttributes1, mpreach1) + bgpMessage1 := bgp.NewBGPUpdateMessage(nil, pathAttributes1, nil) + + // path2 + pathAttributes2 := createPathAttr([]uint32{65000, 65100, 65300}) + mpreach2 := createMpReach("2001::192:168:50:1", []bgp.AddrPrefixInterface{ + bgp.NewIPv6AddrPrefix(64, "2001:123:1211:11::"), + bgp.NewIPv6AddrPrefix(64, "2001:123:1222:11::"), + bgp.NewIPv6AddrPrefix(64, "2001:123:1233:11::"), + bgp.NewIPv6AddrPrefix(64, "2001:123:1244:11::"), + bgp.NewIPv6AddrPrefix(64, "2001:123:1255:11::"), + }) + pathAttributes2 = append(pathAttributes2, mpreach2) + bgpMessage2 := bgp.NewBGPUpdateMessage(nil, pathAttributes2, nil) + + // path3 + pathAttributes3 := createPathAttr([]uint32{65000, 65100, 65400}) + mpreach3 := createMpReach("2001::192:168:50:1", []bgp.AddrPrefixInterface{ + bgp.NewIPv6AddrPrefix(64, "2001:123:1277:11::"), + bgp.NewIPv6AddrPrefix(64, "2001:123:1288:11::"), + }) + pathAttributes3 = append(pathAttributes3, mpreach3) + bgpMessage3 := bgp.NewBGPUpdateMessage(nil, pathAttributes3, nil) + + // path4 + pathAttributes4 := createPathAttr([]uint32{65000, 65100, 65500}) + mpreach4 := createMpReach("2001::192:168:50:1", []bgp.AddrPrefixInterface{ + bgp.NewIPv6AddrPrefix(64, "2001:123:1299:11::"), + }) + pathAttributes4 = append(pathAttributes4, mpreach4) + bgpMessage4 := bgp.NewBGPUpdateMessage(nil, pathAttributes4, nil) + + peer1 := peerR1() + pList, err := tm.ProcessUpdate(peer1, bgpMessage1) + assert.Equal(t, 5, len(pList)) + for _, p := range pList { + assert.Equal(t, p.IsWithdraw, false) + } + assert.NoError(t, err) + + checkBestPathResult(bgp.RF_IPv6_UC, "2001:123:1210:11::/64", "2001::192:168:50:1", pList[0], bgpMessage1) + checkBestPathResult(bgp.RF_IPv6_UC, "2001:123:1220:11::/64", "2001::192:168:50:1", pList[1], bgpMessage1) + checkBestPathResult(bgp.RF_IPv6_UC, "2001:123:1230:11::/64", "2001::192:168:50:1", pList[2], bgpMessage1) + checkBestPathResult(bgp.RF_IPv6_UC, "2001:123:1240:11::/64", "2001::192:168:50:1", pList[3], bgpMessage1) + checkBestPathResult(bgp.RF_IPv6_UC, "2001:123:1250:11::/64", "2001::192:168:50:1", pList[4], bgpMessage1) + + pList, err = tm.ProcessUpdate(peer1, bgpMessage2) + assert.Equal(t, 5, len(pList)) + for _, p := range pList { + assert.Equal(t, p.IsWithdraw, false) + } + assert.NoError(t, err) + + checkBestPathResult(bgp.RF_IPv6_UC, "2001:123:1211:11::/64", "2001::192:168:50:1", pList[0], bgpMessage2) + checkBestPathResult(bgp.RF_IPv6_UC, "2001:123:1222:11::/64", "2001::192:168:50:1", pList[1], bgpMessage2) + checkBestPathResult(bgp.RF_IPv6_UC, "2001:123:1233:11::/64", "2001::192:168:50:1", pList[2], bgpMessage2) + checkBestPathResult(bgp.RF_IPv6_UC, "2001:123:1244:11::/64", "2001::192:168:50:1", pList[3], bgpMessage2) + checkBestPathResult(bgp.RF_IPv6_UC, "2001:123:1255:11::/64", "2001::192:168:50:1", pList[4], bgpMessage2) + + pList, err = tm.ProcessUpdate(peer1, bgpMessage3) + assert.Equal(t, 2, len(pList)) + for _, p := range pList { + assert.Equal(t, p.IsWithdraw, false) + } + assert.NoError(t, err) + + pList, err = tm.ProcessUpdate(peer1, bgpMessage4) + assert.Equal(t, 1, len(pList)) + assert.Equal(t, pList[0].IsWithdraw, false) + assert.NoError(t, err) + + // check table + table := tm.Tables[bgp.RF_IPv6_UC] + assert.Equal(t, 13, len(table.GetDestinations())) + +} + +func TestProcessBGPUpdate_Timestamp(t *testing.T) { + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAs4PathParam(2, []uint32{65000})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("192.168.50.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + + pathAttributes := []bgp.PathAttributeInterface{ + origin, + aspath, + nexthop, + med, + } + + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.10.0")} + + adjRib := NewAdjRib([]bgp.RouteFamily{bgp.RF_IPv4_UC, bgp.RF_IPv6_UC}) + m1 := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + peer := peerR1() + pList1 := ProcessMessage(m1, peer, time.Now()) + path1 := pList1[0] + t1 := path1.GetTimestamp() + adjRib.Update(pList1) + + m2 := bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) + pList2 := ProcessMessage(m2, peer, time.Now()) + //path2 := pList2[0].(*IPv4Path) + //t2 = path2.timestamp + adjRib.Update(pList2) + + inList := adjRib.PathList([]bgp.RouteFamily{bgp.RF_IPv4_UC}, false) + assert.Equal(t, len(inList), 1) + assert.Equal(t, inList[0].GetTimestamp(), t1) + + med2 := bgp.NewPathAttributeMultiExitDisc(1) + pathAttributes2 := []bgp.PathAttributeInterface{ + origin, + aspath, + nexthop, + med2, + } + + m3 := bgp.NewBGPUpdateMessage(nil, pathAttributes2, nlri) + pList3 := ProcessMessage(m3, peer, time.Now()) + t3 := pList3[0].GetTimestamp() + adjRib.Update(pList3) + + inList = adjRib.PathList([]bgp.RouteFamily{bgp.RF_IPv4_UC}, false) + assert.Equal(t, len(inList), 1) + assert.Equal(t, inList[0].GetTimestamp(), t3) +} + +func update_fromR1() *bgp.BGPMessage { + + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAs4PathParam(2, []uint32{65000})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("192.168.50.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + + pathAttributes := []bgp.PathAttributeInterface{ + origin, + aspath, + nexthop, + med, + } + + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.10.0")} + + return bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) +} + +func update_fromR1_ipv6() *bgp.BGPMessage { + + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAs4PathParam(2, []uint32{65000})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + + mp_nlri := []bgp.AddrPrefixInterface{bgp.NewIPv6AddrPrefix(64, "2001:123:123:1::")} + mp_reach := bgp.NewPathAttributeMpReachNLRI("2001::192:168:50:1", mp_nlri) + med := bgp.NewPathAttributeMultiExitDisc(0) + + pathAttributes := []bgp.PathAttributeInterface{ + mp_reach, + origin, + aspath, + med, + } + return bgp.NewBGPUpdateMessage(nil, pathAttributes, nil) +} + +func update_fromR2() *bgp.BGPMessage { + + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAs4PathParam(2, []uint32{65100})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("192.168.100.1") + med := bgp.NewPathAttributeMultiExitDisc(100) + + pathAttributes := []bgp.PathAttributeInterface{ + origin, + aspath, + nexthop, + med, + } + + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "20.20.20.0")} + return bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) +} + +func update_fromR2_ipv6() *bgp.BGPMessage { + + origin := bgp.NewPathAttributeOrigin(0) + aspath := createAsPathAttribute([]uint32{65100}) + mp_reach := createMpReach("2001::192:168:100:1", + []bgp.AddrPrefixInterface{bgp.NewIPv6AddrPrefix(64, "2002:223:123:1::")}) + med := bgp.NewPathAttributeMultiExitDisc(100) + + pathAttributes := []bgp.PathAttributeInterface{ + mp_reach, + origin, + aspath, + med, + } + return bgp.NewBGPUpdateMessage(nil, pathAttributes, nil) +} + +func createAsPathAttribute(ases []uint32) *bgp.PathAttributeAsPath { + aspathParam := []bgp.AsPathParamInterface{bgp.NewAs4PathParam(2, ases)} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + return aspath +} + +func createMpReach(nexthop string, prefix []bgp.AddrPrefixInterface) *bgp.PathAttributeMpReachNLRI { + mp_reach := bgp.NewPathAttributeMpReachNLRI(nexthop, prefix) + return mp_reach +} + +func createMpUNReach(nlri string, len uint8) *bgp.PathAttributeMpUnreachNLRI { + mp_nlri := []bgp.AddrPrefixInterface{bgp.NewIPv6AddrPrefix(len, nlri)} + mp_unreach := bgp.NewPathAttributeMpUnreachNLRI(mp_nlri) + return mp_unreach +} + +func update_fromR2viaR1() *bgp.BGPMessage { + + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAs4PathParam(2, []uint32{65000, 65100})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("192.168.50.1") + + pathAttributes := []bgp.PathAttributeInterface{ + origin, + aspath, + nexthop, + } + + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "20.20.20.0")} + return bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) +} + +func update_fromR2viaR1_ipv6() *bgp.BGPMessage { + + origin := bgp.NewPathAttributeOrigin(0) + aspath := createAsPathAttribute([]uint32{65000, 65100}) + mp_reach := createMpReach("2001::192:168:50:1", + []bgp.AddrPrefixInterface{bgp.NewIPv6AddrPrefix(64, "2002:223:123:1::")}) + med := bgp.NewPathAttributeMultiExitDisc(100) + + pathAttributes := []bgp.PathAttributeInterface{ + mp_reach, + origin, + aspath, + med, + } + return bgp.NewBGPUpdateMessage(nil, pathAttributes, nil) + +} diff --git a/internal/pkg/table/table_test.go b/internal/pkg/table/table_test.go new file mode 100644 index 00000000..1e91aa6b --- /dev/null +++ b/internal/pkg/table/table_test.go @@ -0,0 +1,180 @@ +// Copyright (C) 2014 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 table + +import ( + "testing" + "time" + + "github.com/osrg/gobgp/pkg/packet/bgp" + + "github.com/stretchr/testify/assert" +) + +func TestTableDeleteDestByNlri(t *testing.T) { + peerT := TableCreatePeer() + pathT := TableCreatePath(peerT) + ipv4t := NewTable(bgp.RF_IPv4_UC) + for _, path := range pathT { + dest := NewDestination(path.GetNlri(), 0) + ipv4t.setDestination(dest) + } + gdest := ipv4t.GetDestination(pathT[0].GetNlri()) + rdest := ipv4t.deleteDestByNlri(pathT[0].GetNlri()) + assert.Equal(t, rdest, gdest) +} + +func TestTableDeleteDest(t *testing.T) { + peerT := TableCreatePeer() + pathT := TableCreatePath(peerT) + ipv4t := NewTable(bgp.RF_IPv4_UC) + for _, path := range pathT { + dest := NewDestination(path.GetNlri(), 0) + ipv4t.setDestination(dest) + } + dest := NewDestination(pathT[0].GetNlri(), 0) + ipv4t.setDestination(dest) + ipv4t.deleteDest(dest) + gdest := ipv4t.GetDestination(pathT[0].GetNlri()) + assert.Nil(t, gdest) +} + +func TestTableGetRouteFamily(t *testing.T) { + ipv4t := NewTable(bgp.RF_IPv4_UC) + rf := ipv4t.GetRoutefamily() + assert.Equal(t, rf, bgp.RF_IPv4_UC) +} + +func TestTableSetDestinations(t *testing.T) { + peerT := TableCreatePeer() + pathT := TableCreatePath(peerT) + ipv4t := NewTable(bgp.RF_IPv4_UC) + destinations := make(map[string]*Destination) + for _, path := range pathT { + tableKey := ipv4t.tableKey(path.GetNlri()) + dest := NewDestination(path.GetNlri(), 0) + destinations[tableKey] = dest + } + ipv4t.setDestinations(destinations) + ds := ipv4t.GetDestinations() + assert.Equal(t, ds, destinations) +} +func TestTableGetDestinations(t *testing.T) { + peerT := DestCreatePeer() + pathT := DestCreatePath(peerT) + ipv4t := NewTable(bgp.RF_IPv4_UC) + destinations := make(map[string]*Destination) + for _, path := range pathT { + tableKey := ipv4t.tableKey(path.GetNlri()) + dest := NewDestination(path.GetNlri(), 0) + destinations[tableKey] = dest + } + ipv4t.setDestinations(destinations) + ds := ipv4t.GetDestinations() + assert.Equal(t, ds, destinations) +} + +func TestTableKey(t *testing.T) { + tb := NewTable(bgp.RF_IPv4_UC) + n1, _ := bgp.NewPrefixFromRouteFamily(bgp.AFI_IP, bgp.SAFI_UNICAST, "0.0.0.0/0") + d1 := NewDestination(n1, 0) + n2, _ := bgp.NewPrefixFromRouteFamily(bgp.AFI_IP, bgp.SAFI_UNICAST, "0.0.0.0/1") + d2 := NewDestination(n2, 0) + assert.Equal(t, len(tb.tableKey(d1.GetNlri())), 5) + tb.setDestination(d1) + tb.setDestination(d2) + assert.Equal(t, len(tb.GetDestinations()), 2) +} + +func TableCreatePeer() []*PeerInfo { + peerT1 := &PeerInfo{AS: 65000} + peerT2 := &PeerInfo{AS: 65001} + peerT3 := &PeerInfo{AS: 65002} + peerT := []*PeerInfo{peerT1, peerT2, peerT3} + return peerT +} + +func TableCreatePath(peerT []*PeerInfo) []*Path { + bgpMsgT1 := updateMsgT1() + bgpMsgT2 := updateMsgT2() + bgpMsgT3 := updateMsgT3() + pathT := make([]*Path, 3) + for i, msg := range []*bgp.BGPMessage{bgpMsgT1, bgpMsgT2, bgpMsgT3} { + updateMsgT := msg.Body.(*bgp.BGPUpdate) + nlriList := updateMsgT.NLRI + pathAttributes := updateMsgT.PathAttributes + nlri_info := nlriList[0] + pathT[i] = NewPath(peerT[i], nlri_info, false, pathAttributes, time.Now(), false) + } + return pathT +} + +func updateMsgT1() *bgp.BGPMessage { + + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65000})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("192.168.50.1") + med := bgp.NewPathAttributeMultiExitDisc(0) + + pathAttributes := []bgp.PathAttributeInterface{ + origin, + aspath, + nexthop, + med, + } + + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "10.10.10.0")} + return bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) +} + +func updateMsgT2() *bgp.BGPMessage { + + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65100})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("192.168.100.1") + med := bgp.NewPathAttributeMultiExitDisc(100) + + pathAttributes := []bgp.PathAttributeInterface{ + origin, + aspath, + nexthop, + med, + } + + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "20.20.20.0")} + return bgp.NewBGPUpdateMessage(nil, pathAttributes, nlri) +} +func updateMsgT3() *bgp.BGPMessage { + origin := bgp.NewPathAttributeOrigin(0) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAsPathParam(2, []uint16{65100})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + nexthop := bgp.NewPathAttributeNextHop("192.168.150.1") + med := bgp.NewPathAttributeMultiExitDisc(100) + + pathAttributes := []bgp.PathAttributeInterface{ + origin, + aspath, + nexthop, + med, + } + + nlri := []*bgp.IPAddrPrefix{bgp.NewIPAddrPrefix(24, "30.30.30.0")} + w1 := bgp.NewIPAddrPrefix(23, "40.40.40.0") + withdrawnRoutes := []*bgp.IPAddrPrefix{w1} + return bgp.NewBGPUpdateMessage(withdrawnRoutes, pathAttributes, nlri) +} diff --git a/internal/pkg/table/vrf.go b/internal/pkg/table/vrf.go new file mode 100644 index 00000000..053f85ce --- /dev/null +++ b/internal/pkg/table/vrf.go @@ -0,0 +1,53 @@ +// 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 table + +import ( + "github.com/osrg/gobgp/pkg/packet/bgp" +) + +type Vrf struct { + Name string + Id uint32 + Rd bgp.RouteDistinguisherInterface + ImportRt []bgp.ExtendedCommunityInterface + ExportRt []bgp.ExtendedCommunityInterface +} + +func (v *Vrf) Clone() *Vrf { + f := func(rt []bgp.ExtendedCommunityInterface) []bgp.ExtendedCommunityInterface { + l := make([]bgp.ExtendedCommunityInterface, 0, len(rt)) + return append(l, rt...) + } + return &Vrf{ + Name: v.Name, + Id: v.Id, + Rd: v.Rd, + ImportRt: f(v.ImportRt), + ExportRt: f(v.ExportRt), + } +} + +func isLastTargetUser(vrfs map[string]*Vrf, target bgp.ExtendedCommunityInterface) bool { + for _, vrf := range vrfs { + for _, rt := range vrf.ImportRt { + if target.String() == rt.String() { + return false + } + } + } + return true +} diff --git a/internal/pkg/zebra/afi_string.go b/internal/pkg/zebra/afi_string.go new file mode 100644 index 00000000..6c07a09d --- /dev/null +++ b/internal/pkg/zebra/afi_string.go @@ -0,0 +1,17 @@ +// Code generated by "stringer -type=AFI"; DO NOT EDIT. + +package zebra + +import "fmt" + +const _AFI_name = "AFI_IPAFI_IP6AFI_ETHERAFI_MAX" + +var _AFI_index = [...]uint8{0, 6, 13, 22, 29} + +func (i AFI) String() string { + i -= 1 + if i >= AFI(len(_AFI_index)-1) { + return fmt.Sprintf("AFI(%d)", i+1) + } + return _AFI_name[_AFI_index[i]:_AFI_index[i+1]] +} diff --git a/internal/pkg/zebra/api_type_string.go b/internal/pkg/zebra/api_type_string.go new file mode 100644 index 00000000..e97059b1 --- /dev/null +++ b/internal/pkg/zebra/api_type_string.go @@ -0,0 +1,16 @@ +// Code generated by "stringer -type=API_TYPE"; DO NOT EDIT. + +package zebra + +import "fmt" + +const _API_TYPE_name = "FRR_INTERFACE_ADDINTERFACE_ADDINTERFACE_DELETEINTERFACE_ADDRESS_ADDINTERFACE_ADDRESS_DELETEINTERFACE_UPINTERFACE_DOWNIPV4_ROUTE_ADDIPV4_ROUTE_DELETEIPV6_ROUTE_ADDIPV6_ROUTE_DELETEREDISTRIBUTE_ADDREDISTRIBUTE_DELETEREDISTRIBUTE_DEFAULT_ADDREDISTRIBUTE_DEFAULT_DELETEIPV4_NEXTHOP_LOOKUPIPV6_NEXTHOP_LOOKUPIPV4_IMPORT_LOOKUPIPV6_IMPORT_LOOKUPINTERFACE_RENAMEROUTER_ID_ADDROUTER_ID_DELETEROUTER_ID_UPDATEHELLOIPV4_NEXTHOP_LOOKUP_MRIBVRF_UNREGISTERINTERFACE_LINK_PARAMSNEXTHOP_REGISTERNEXTHOP_UNREGISTERNEXTHOP_UPDATEMESSAGE_MAXFRR_BFD_DEST_REPLAYFRR_REDISTRIBUTE_IPV4_ADDFRR_REDISTRIBUTE_IPV4_DELFRR_REDISTRIBUTE_IPV6_ADDFRR_REDISTRIBUTE_IPV6_DELFRR_VRF_UNREGISTERFRR_VRF_ADDFRR_VRF_DELETEFRR_INTERFACE_VRF_UPDATEFRR_BFD_CLIENT_REGISTERFRR_INTERFACE_ENABLE_RADVFRR_INTERFACE_DISABLE_RADVFRR_IPV4_NEXTHOP_LOOKUP_MRIBFRR_INTERFACE_LINK_PARAMSFRR_MPLS_LABELS_ADDFRR_MPLS_LABELS_DELETEFRR_IPV4_NEXTHOP_ADDFRR_IPV4_NEXTHOP_DELETEFRR_IPV6_NEXTHOP_ADDFRR_IPV6_NEXTHOP_DELETEFRR_IPMR_ROUTE_STATSFRR_LABEL_MANAGER_CONNECTFRR_GET_LABEL_CHUNKFRR_RELEASE_LABEL_CHUNKFRR_PW_ADDFRR_PW_DELETEFRR_PW_SETFRR_PW_UNSETFRR_PW_STATUS_UPDATE" + +var _API_TYPE_index = [...]uint16{0, 17, 30, 46, 67, 91, 103, 117, 131, 148, 162, 179, 195, 214, 238, 265, 284, 303, 321, 339, 355, 368, 384, 400, 405, 429, 443, 464, 480, 498, 512, 523, 542, 567, 592, 617, 642, 660, 671, 685, 709, 732, 757, 783, 811, 836, 855, 877, 897, 920, 940, 963, 983, 1008, 1027, 1050, 1060, 1073, 1083, 1095, 1115} + +func (i API_TYPE) String() string { + if i >= API_TYPE(len(_API_TYPE_index)-1) { + return fmt.Sprintf("API_TYPE(%d)", i) + } + return _API_TYPE_name[_API_TYPE_index[i]:_API_TYPE_index[i+1]] +} diff --git a/internal/pkg/zebra/link_type_string.go b/internal/pkg/zebra/link_type_string.go new file mode 100644 index 00000000..9db8544b --- /dev/null +++ b/internal/pkg/zebra/link_type_string.go @@ -0,0 +1,16 @@ +// Code generated by "stringer -type=LINK_TYPE"; DO NOT EDIT. + +package zebra + +import "fmt" + +const _LINK_TYPE_name = "LINK_TYPE_UNKNOWNLINK_TYPE_ETHERLINK_TYPE_EETHERLINK_TYPE_AX25LINK_TYPE_PRONETLINK_TYPE_IEEE802LINK_TYPE_ARCNETLINK_TYPE_APPLETLKLINK_TYPE_DLCILINK_TYPE_ATMLINK_TYPE_METRICOMLINK_TYPE_IEEE1394LINK_TYPE_EUI64LINK_TYPE_INFINIBANDLINK_TYPE_SLIPLINK_TYPE_CSLIPLINK_TYPE_SLIP6LINK_TYPE_CSLIP6LINK_TYPE_RSRVDLINK_TYPE_ADAPTLINK_TYPE_ROSELINK_TYPE_X25LINK_TYPE_PPPLINK_TYPE_CHDLCLINK_TYPE_LAPBLINK_TYPE_RAWHDLCLINK_TYPE_IPIPLINK_TYPE_IPIP6LINK_TYPE_FRADLINK_TYPE_SKIPLINK_TYPE_LOOPBACKLINK_TYPE_LOCALTLKLINK_TYPE_FDDILINK_TYPE_SITLINK_TYPE_IPDDPLINK_TYPE_IPGRELINK_TYPE_IP6GRELINK_TYPE_PIMREGLINK_TYPE_HIPPILINK_TYPE_ECONETLINK_TYPE_IRDALINK_TYPE_FCPPLINK_TYPE_FCALLINK_TYPE_FCPLLINK_TYPE_FCFABRICLINK_TYPE_IEEE802_TRLINK_TYPE_IEEE80211LINK_TYPE_IEEE80211_RADIOTAPLINK_TYPE_IEEE802154LINK_TYPE_IEEE802154_PHY" + +var _LINK_TYPE_index = [...]uint16{0, 17, 32, 48, 62, 78, 95, 111, 129, 143, 156, 174, 192, 207, 227, 241, 256, 271, 287, 302, 317, 331, 344, 357, 372, 386, 403, 417, 432, 446, 460, 478, 496, 510, 523, 538, 553, 569, 585, 600, 616, 630, 644, 658, 672, 690, 710, 729, 757, 777, 801} + +func (i LINK_TYPE) String() string { + if i >= LINK_TYPE(len(_LINK_TYPE_index)-1) { + return fmt.Sprintf("LINK_TYPE(%d)", i) + } + return _LINK_TYPE_name[_LINK_TYPE_index[i]:_LINK_TYPE_index[i+1]] +} diff --git a/internal/pkg/zebra/nexthop_flag_string.go b/internal/pkg/zebra/nexthop_flag_string.go new file mode 100644 index 00000000..38f08b8a --- /dev/null +++ b/internal/pkg/zebra/nexthop_flag_string.go @@ -0,0 +1,17 @@ +// Code generated by "stringer -type=NEXTHOP_FLAG"; DO NOT EDIT. + +package zebra + +import "fmt" + +const _NEXTHOP_FLAG_name = "NEXTHOP_IFINDEXNEXTHOP_IFNAMENEXTHOP_IPV4NEXTHOP_IPV4_IFINDEXNEXTHOP_IPV4_IFNAMENEXTHOP_IPV6NEXTHOP_IPV6_IFINDEXNEXTHOP_IPV6_IFNAMENEXTHOP_BLACKHOLE" + +var _NEXTHOP_FLAG_index = [...]uint8{0, 15, 29, 41, 61, 80, 92, 112, 131, 148} + +func (i NEXTHOP_FLAG) String() string { + i -= 1 + if i >= NEXTHOP_FLAG(len(_NEXTHOP_FLAG_index)-1) { + return fmt.Sprintf("NEXTHOP_FLAG(%d)", i+1) + } + return _NEXTHOP_FLAG_name[_NEXTHOP_FLAG_index[i]:_NEXTHOP_FLAG_index[i+1]] +} diff --git a/internal/pkg/zebra/ptm_enable_string.go b/internal/pkg/zebra/ptm_enable_string.go new file mode 100644 index 00000000..d750542e --- /dev/null +++ b/internal/pkg/zebra/ptm_enable_string.go @@ -0,0 +1,16 @@ +// Code generated by "stringer -type=PTM_ENABLE"; DO NOT EDIT. + +package zebra + +import "fmt" + +const _PTM_ENABLE_name = "PTM_ENABLE_OFFPTM_ENABLE_ONPTM_ENABLE_UNSPEC" + +var _PTM_ENABLE_index = [...]uint8{0, 14, 27, 44} + +func (i PTM_ENABLE) String() string { + if i >= PTM_ENABLE(len(_PTM_ENABLE_index)-1) { + return fmt.Sprintf("PTM_ENABLE(%d)", i) + } + return _PTM_ENABLE_name[_PTM_ENABLE_index[i]:_PTM_ENABLE_index[i+1]] +} diff --git a/internal/pkg/zebra/ptm_status_string.go b/internal/pkg/zebra/ptm_status_string.go new file mode 100644 index 00000000..464233b7 --- /dev/null +++ b/internal/pkg/zebra/ptm_status_string.go @@ -0,0 +1,16 @@ +// Code generated by "stringer -type=PTM_STATUS"; DO NOT EDIT. + +package zebra + +import "fmt" + +const _PTM_STATUS_name = "PTM_STATUS_DOWNPTM_STATUS_UPPTM_STATUS_UNKNOWN" + +var _PTM_STATUS_index = [...]uint8{0, 15, 28, 46} + +func (i PTM_STATUS) String() string { + if i >= PTM_STATUS(len(_PTM_STATUS_index)-1) { + return fmt.Sprintf("PTM_STATUS(%d)", i) + } + return _PTM_STATUS_name[_PTM_STATUS_index[i]:_PTM_STATUS_index[i+1]] +} diff --git a/internal/pkg/zebra/route_type_string.go b/internal/pkg/zebra/route_type_string.go new file mode 100644 index 00000000..e2ad6c97 --- /dev/null +++ b/internal/pkg/zebra/route_type_string.go @@ -0,0 +1,16 @@ +// Code generated by "stringer -type=ROUTE_TYPE"; DO NOT EDIT. + +package zebra + +import "fmt" + +const _ROUTE_TYPE_name = "ROUTE_SYSTEMROUTE_KERNELROUTE_CONNECTROUTE_STATICROUTE_RIPROUTE_RIPNGROUTE_OSPFROUTE_OSPF6ROUTE_ISISROUTE_BGPROUTE_PIMROUTE_HSLSROUTE_OLSRROUTE_BABELROUTE_MAXFRR_ROUTE_VNCFRR_ROUTE_VNC_DIRECTFRR_ROUTE_VNC_DIRECT_RHFRR_ROUTE_BGP_DIRECTFRR_ROUTE_BGP_DIRECT_EXTFRR_ROUTE_ALLFRR_ROUTE_MAX" + +var _ROUTE_TYPE_index = [...]uint16{0, 12, 24, 37, 49, 58, 69, 79, 90, 100, 109, 118, 128, 138, 149, 158, 171, 191, 214, 234, 258, 271, 284} + +func (i ROUTE_TYPE) String() string { + if i >= ROUTE_TYPE(len(_ROUTE_TYPE_index)-1) { + return fmt.Sprintf("ROUTE_TYPE(%d)", i) + } + return _ROUTE_TYPE_name[_ROUTE_TYPE_index[i]:_ROUTE_TYPE_index[i+1]] +} diff --git a/internal/pkg/zebra/safi_string.go b/internal/pkg/zebra/safi_string.go new file mode 100644 index 00000000..ab491cb6 --- /dev/null +++ b/internal/pkg/zebra/safi_string.go @@ -0,0 +1,17 @@ +// Code generated by "stringer -type=SAFI"; DO NOT EDIT. + +package zebra + +import "fmt" + +const _SAFI_name = "SAFI_UNICASTSAFI_MULTICASTSAFI_RESERVED_3SAFI_MPLS_VPNSAFI_MAX" + +var _SAFI_index = [...]uint8{0, 12, 26, 41, 54, 62} + +func (i SAFI) String() string { + i -= 1 + if i >= SAFI(len(_SAFI_index)-1) { + return fmt.Sprintf("SAFI(%d)", i+1) + } + return _SAFI_name[_SAFI_index[i]:_SAFI_index[i+1]] +} diff --git a/internal/pkg/zebra/zapi.go b/internal/pkg/zebra/zapi.go new file mode 100644 index 00000000..eb443d05 --- /dev/null +++ b/internal/pkg/zebra/zapi.go @@ -0,0 +1,1917 @@ +// Copyright (C) 2014, 2015 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 zebra + +import ( + "encoding/binary" + "fmt" + "io" + "net" + "strings" + "syscall" + + log "github.com/sirupsen/logrus" + + "github.com/osrg/gobgp/pkg/packet/bgp" +) + +const ( + HEADER_MARKER = 255 + FRR_HEADER_MARKER = 254 + INTERFACE_NAMSIZ = 20 +) + +// Internal Interface Status. +type INTERFACE_STATUS uint8 + +const ( + INTERFACE_ACTIVE INTERFACE_STATUS = 0x01 + INTERFACE_SUB INTERFACE_STATUS = 0x02 + INTERFACE_LINKDETECTION INTERFACE_STATUS = 0x04 + INTERFACE_VRF_LOOPBACK INTERFACE_STATUS = 0x08 +) + +// Interface Link Layer Types. +//go:generate stringer -type=LINK_TYPE +type LINK_TYPE uint32 + +const ( + LINK_TYPE_UNKNOWN LINK_TYPE = iota + LINK_TYPE_ETHER + LINK_TYPE_EETHER + LINK_TYPE_AX25 + LINK_TYPE_PRONET + LINK_TYPE_IEEE802 + LINK_TYPE_ARCNET + LINK_TYPE_APPLETLK + LINK_TYPE_DLCI + LINK_TYPE_ATM + LINK_TYPE_METRICOM + LINK_TYPE_IEEE1394 + LINK_TYPE_EUI64 + LINK_TYPE_INFINIBAND + LINK_TYPE_SLIP + LINK_TYPE_CSLIP + LINK_TYPE_SLIP6 + LINK_TYPE_CSLIP6 + LINK_TYPE_RSRVD + LINK_TYPE_ADAPT + LINK_TYPE_ROSE + LINK_TYPE_X25 + LINK_TYPE_PPP + LINK_TYPE_CHDLC + LINK_TYPE_LAPB + LINK_TYPE_RAWHDLC + LINK_TYPE_IPIP + LINK_TYPE_IPIP6 + LINK_TYPE_FRAD + LINK_TYPE_SKIP + LINK_TYPE_LOOPBACK + LINK_TYPE_LOCALTLK + LINK_TYPE_FDDI + LINK_TYPE_SIT + LINK_TYPE_IPDDP + LINK_TYPE_IPGRE + LINK_TYPE_IP6GRE + LINK_TYPE_PIMREG + LINK_TYPE_HIPPI + LINK_TYPE_ECONET + LINK_TYPE_IRDA + LINK_TYPE_FCPP + LINK_TYPE_FCAL + LINK_TYPE_FCPL + LINK_TYPE_FCFABRIC + LINK_TYPE_IEEE802_TR + LINK_TYPE_IEEE80211 + LINK_TYPE_IEEE80211_RADIOTAP + LINK_TYPE_IEEE802154 + LINK_TYPE_IEEE802154_PHY +) + +const VRF_DEFAULT = 0 + +func HeaderSize(version uint8) uint16 { + switch version { + case 3, 4: + return 8 + default: + return 6 + } +} + +func (t INTERFACE_STATUS) String() string { + ss := make([]string, 0, 3) + if t&INTERFACE_ACTIVE > 0 { + ss = append(ss, "ACTIVE") + } + if t&INTERFACE_SUB > 0 { + ss = append(ss, "SUB") + } + if t&INTERFACE_LINKDETECTION > 0 { + ss = append(ss, "LINKDETECTION") + } + if t&INTERFACE_VRF_LOOPBACK > 0 { + ss = append(ss, "VRF_LOOPBACK") + } + return strings.Join(ss, "|") +} + +// Interface Connected Address Flags +type INTERFACE_ADDRESS_FLAG uint8 + +const ( + INTERFACE_ADDRESS_SECONDARY INTERFACE_ADDRESS_FLAG = 0x01 + INTERFACE_ADDRESS_PEER INTERFACE_ADDRESS_FLAG = 0x02 + INTERFACE_ADDRESS_UNNUMBERED INTERFACE_ADDRESS_FLAG = 0x04 +) + +func (t INTERFACE_ADDRESS_FLAG) String() string { + ss := make([]string, 0, 3) + if t&INTERFACE_ADDRESS_SECONDARY > 0 { + ss = append(ss, "SECONDARY") + } + if t&INTERFACE_ADDRESS_PEER > 0 { + ss = append(ss, "PEER") + } + if t&INTERFACE_ADDRESS_UNNUMBERED > 0 { + ss = append(ss, "UNNUMBERED") + } + return strings.Join(ss, "|") +} + +// Address Family Identifier. +//go:generate stringer -type=AFI +type AFI uint8 + +const ( + AFI_IP AFI = 1 + AFI_IP6 AFI = 2 + AFI_ETHER AFI = 3 + AFI_MAX AFI = 4 +) + +// Subsequent Address Family Identifier. +//go:generate stringer -type=SAFI +type SAFI uint8 + +const ( + _ SAFI = iota + SAFI_UNICAST + SAFI_MULTICAST + SAFI_RESERVED_3 + SAFI_MPLS_VPN + SAFI_MAX +) + +// API Types. +//go:generate stringer -type=API_TYPE +type API_TYPE uint16 + +// For Quagga. +const ( + _ API_TYPE = iota + INTERFACE_ADD + INTERFACE_DELETE + INTERFACE_ADDRESS_ADD + INTERFACE_ADDRESS_DELETE + INTERFACE_UP + INTERFACE_DOWN + IPV4_ROUTE_ADD + IPV4_ROUTE_DELETE + IPV6_ROUTE_ADD + IPV6_ROUTE_DELETE + REDISTRIBUTE_ADD + REDISTRIBUTE_DELETE + REDISTRIBUTE_DEFAULT_ADD + REDISTRIBUTE_DEFAULT_DELETE + IPV4_NEXTHOP_LOOKUP + IPV6_NEXTHOP_LOOKUP + IPV4_IMPORT_LOOKUP + IPV6_IMPORT_LOOKUP + INTERFACE_RENAME + ROUTER_ID_ADD + ROUTER_ID_DELETE + ROUTER_ID_UPDATE + HELLO + IPV4_NEXTHOP_LOOKUP_MRIB + VRF_UNREGISTER + INTERFACE_LINK_PARAMS + NEXTHOP_REGISTER + NEXTHOP_UNREGISTER + NEXTHOP_UPDATE + MESSAGE_MAX +) + +// For FRRouting. +const ( + FRR_INTERFACE_ADD API_TYPE = iota + FRR_INTERFACE_DELETE + FRR_INTERFACE_ADDRESS_ADD + FRR_INTERFACE_ADDRESS_DELETE + FRR_INTERFACE_UP + FRR_INTERFACE_DOWN + FRR_IPV4_ROUTE_ADD + FRR_IPV4_ROUTE_DELETE + FRR_IPV6_ROUTE_ADD + FRR_IPV6_ROUTE_DELETE + FRR_REDISTRIBUTE_ADD + FRR_REDISTRIBUTE_DELETE + FRR_REDISTRIBUTE_DEFAULT_ADD + FRR_REDISTRIBUTE_DEFAULT_DELETE + FRR_ROUTER_ID_ADD + FRR_ROUTER_ID_DELETE + FRR_ROUTER_ID_UPDATE + FRR_HELLO + FRR_NEXTHOP_REGISTER + FRR_NEXTHOP_UNREGISTER + FRR_NEXTHOP_UPDATE + FRR_INTERFACE_NBR_ADDRESS_ADD + FRR_INTERFACE_NBR_ADDRESS_DELETE + FRR_INTERFACE_BFD_DEST_UPDATE + FRR_IMPORT_ROUTE_REGISTER + FRR_IMPORT_ROUTE_UNREGISTER + FRR_IMPORT_CHECK_UPDATE + FRR_IPV4_ROUTE_IPV6_NEXTHOP_ADD + FRR_BFD_DEST_REGISTER + FRR_BFD_DEST_DEREGISTER + FRR_BFD_DEST_UPDATE + FRR_BFD_DEST_REPLAY + FRR_REDISTRIBUTE_IPV4_ADD + FRR_REDISTRIBUTE_IPV4_DEL + FRR_REDISTRIBUTE_IPV6_ADD + FRR_REDISTRIBUTE_IPV6_DEL + FRR_VRF_UNREGISTER + FRR_VRF_ADD + FRR_VRF_DELETE + FRR_INTERFACE_VRF_UPDATE + FRR_BFD_CLIENT_REGISTER + FRR_INTERFACE_ENABLE_RADV + FRR_INTERFACE_DISABLE_RADV + FRR_IPV4_NEXTHOP_LOOKUP_MRIB + FRR_INTERFACE_LINK_PARAMS + FRR_MPLS_LABELS_ADD + FRR_MPLS_LABELS_DELETE + FRR_IPV4_NEXTHOP_ADD + FRR_IPV4_NEXTHOP_DELETE + FRR_IPV6_NEXTHOP_ADD + FRR_IPV6_NEXTHOP_DELETE + FRR_IPMR_ROUTE_STATS + FRR_LABEL_MANAGER_CONNECT + FRR_GET_LABEL_CHUNK + FRR_RELEASE_LABEL_CHUNK + FRR_PW_ADD + FRR_PW_DELETE + FRR_PW_SET + FRR_PW_UNSET + FRR_PW_STATUS_UPDATE +) + +// Route Types. +//go:generate stringer -type=ROUTE_TYPE +type ROUTE_TYPE uint8 + +// For Quagga. +const ( + ROUTE_SYSTEM ROUTE_TYPE = iota + ROUTE_KERNEL + ROUTE_CONNECT + ROUTE_STATIC + ROUTE_RIP + ROUTE_RIPNG + ROUTE_OSPF + ROUTE_OSPF6 + ROUTE_ISIS + ROUTE_BGP + ROUTE_PIM + ROUTE_HSLS + ROUTE_OLSR + ROUTE_BABEL + ROUTE_MAX +) + +// For FRRouting. +const ( + FRR_ROUTE_SYSTEM ROUTE_TYPE = iota + FRR_ROUTE_KERNEL + FRR_ROUTE_CONNECT + FRR_ROUTE_STATIC + FRR_ROUTE_RIP + FRR_ROUTE_RIPNG + FRR_ROUTE_OSPF + FRR_ROUTE_OSPF6 + FRR_ROUTE_ISIS + FRR_ROUTE_BGP + FRR_ROUTE_PIM + FRR_ROUTE_HSLS + FRR_ROUTE_OLSR + FRR_ROUTE_TABLE + FRR_ROUTE_LDP + FRR_ROUTE_VNC + FRR_ROUTE_VNC_DIRECT + FRR_ROUTE_VNC_DIRECT_RH + FRR_ROUTE_BGP_DIRECT + FRR_ROUTE_BGP_DIRECT_EXT + FRR_ROUTE_ALL + FRR_ROUTE_MAX +) + +var routeTypeValueMap = map[string]ROUTE_TYPE{ + "system": ROUTE_SYSTEM, + "kernel": ROUTE_KERNEL, + "connect": ROUTE_CONNECT, // hack for backyard compatibility + "directly-connected": ROUTE_CONNECT, + "static": ROUTE_STATIC, + "rip": ROUTE_RIP, + "ripng": ROUTE_RIPNG, + "ospf": ROUTE_OSPF, + "ospf3": ROUTE_OSPF6, + "isis": ROUTE_ISIS, + "bgp": ROUTE_BGP, + "pim": ROUTE_PIM, + "hsls": ROUTE_HSLS, + "olsr": ROUTE_OLSR, + "babel": ROUTE_BABEL, + "table": FRR_ROUTE_TABLE, + "ldp": FRR_ROUTE_LDP, + "vnc": FRR_ROUTE_VNC, + "vnc-direct": FRR_ROUTE_VNC_DIRECT, + "vnc-direct-rh": FRR_ROUTE_VNC_DIRECT_RH, + "bgp-direct": FRR_ROUTE_BGP_DIRECT, + "bgp-direct-ext": FRR_ROUTE_BGP_DIRECT_EXT, + "all": FRR_ROUTE_ALL, +} + +func RouteTypeFromString(typ string) (ROUTE_TYPE, error) { + t, ok := routeTypeValueMap[typ] + if ok { + return t, nil + } + return t, fmt.Errorf("unknown route type: %s", typ) +} + +// API Message Flags. +type MESSAGE_FLAG uint8 + +// For Quagga. +const ( + MESSAGE_NEXTHOP MESSAGE_FLAG = 0x01 + MESSAGE_IFINDEX MESSAGE_FLAG = 0x02 + MESSAGE_DISTANCE MESSAGE_FLAG = 0x04 + MESSAGE_METRIC MESSAGE_FLAG = 0x08 + MESSAGE_MTU MESSAGE_FLAG = 0x10 + MESSAGE_TAG MESSAGE_FLAG = 0x20 +) + +func (t MESSAGE_FLAG) String() string { + var ss []string + if t&MESSAGE_NEXTHOP > 0 { + ss = append(ss, "NEXTHOP") + } + if t&MESSAGE_IFINDEX > 0 { + ss = append(ss, "IFINDEX") + } + if t&MESSAGE_DISTANCE > 0 { + ss = append(ss, "DISTANCE") + } + if t&MESSAGE_METRIC > 0 { + ss = append(ss, "METRIC") + } + if t&MESSAGE_MTU > 0 { + ss = append(ss, "MTU") + } + if t&MESSAGE_TAG > 0 { + ss = append(ss, "TAG") + } + return strings.Join(ss, "|") +} + +// For FRRouting. +const ( + FRR_MESSAGE_NEXTHOP MESSAGE_FLAG = 0x01 + FRR_MESSAGE_IFINDEX MESSAGE_FLAG = 0x02 + FRR_MESSAGE_DISTANCE MESSAGE_FLAG = 0x04 + FRR_MESSAGE_METRIC MESSAGE_FLAG = 0x08 + FRR_MESSAGE_TAG MESSAGE_FLAG = 0x10 + FRR_MESSAGE_MTU MESSAGE_FLAG = 0x20 + FRR_MESSAGE_SRCPFX MESSAGE_FLAG = 0x40 +) + +// Message Flags +type FLAG uint64 + +const ( + FLAG_INTERNAL FLAG = 0x01 + FLAG_SELFROUTE FLAG = 0x02 + FLAG_BLACKHOLE FLAG = 0x04 + FLAG_IBGP FLAG = 0x08 + FLAG_SELECTED FLAG = 0x10 + FLAG_CHANGED FLAG = 0x20 + FLAG_STATIC FLAG = 0x40 + FLAG_REJECT FLAG = 0x80 + FLAG_SCOPE_LINK FLAG = 0x100 + FLAG_FIB_OVERRIDE FLAG = 0x200 +) + +func (t FLAG) String() string { + var ss []string + if t&FLAG_INTERNAL > 0 { + ss = append(ss, "FLAG_INTERNAL") + } + if t&FLAG_SELFROUTE > 0 { + ss = append(ss, "FLAG_SELFROUTE") + } + if t&FLAG_BLACKHOLE > 0 { + ss = append(ss, "FLAG_BLACKHOLE") + } + if t&FLAG_IBGP > 0 { + ss = append(ss, "FLAG_IBGP") + } + if t&FLAG_SELECTED > 0 { + ss = append(ss, "FLAG_SELECTED") + } + if t&FLAG_CHANGED > 0 { + ss = append(ss, "FLAG_CHANGED") + } + if t&FLAG_STATIC > 0 { + ss = append(ss, "FLAG_STATIC") + } + if t&FLAG_REJECT > 0 { + ss = append(ss, "FLAG_REJECT") + } + if t&FLAG_SCOPE_LINK > 0 { + ss = append(ss, "FLAG_SCOPE_LINK") + } + if t&FLAG_FIB_OVERRIDE > 0 { + ss = append(ss, "FLAG_FIB_OVERRIDE") + } + return strings.Join(ss, "|") +} + +// Nexthop Flags. +//go:generate stringer -type=NEXTHOP_FLAG +type NEXTHOP_FLAG uint8 + +// For Quagga. +const ( + _ NEXTHOP_FLAG = iota + NEXTHOP_IFINDEX + NEXTHOP_IFNAME + NEXTHOP_IPV4 + NEXTHOP_IPV4_IFINDEX + NEXTHOP_IPV4_IFNAME + NEXTHOP_IPV6 + NEXTHOP_IPV6_IFINDEX + NEXTHOP_IPV6_IFNAME + NEXTHOP_BLACKHOLE +) + +// For FRRouting. +const ( + _ NEXTHOP_FLAG = iota + FRR_NEXTHOP_IFINDEX + FRR_NEXTHOP_IPV4 + FRR_NEXTHOP_IPV4_IFINDEX + FRR_NEXTHOP_IPV6 + FRR_NEXTHOP_IPV6_IFINDEX + FRR_NEXTHOP_BLACKHOLE +) + +// Interface PTM Enable Configuration. +//go:generate stringer -type=PTM_ENABLE +type PTM_ENABLE uint8 + +const ( + PTM_ENABLE_OFF PTM_ENABLE = 0 + PTM_ENABLE_ON PTM_ENABLE = 1 + PTM_ENABLE_UNSPEC PTM_ENABLE = 2 +) + +// PTM Status. +//go:generate stringer -type=PTM_STATUS +type PTM_STATUS uint8 + +const ( + PTM_STATUS_DOWN PTM_STATUS = 0 + PTM_STATUS_UP PTM_STATUS = 1 + PTM_STATUS_UNKNOWN PTM_STATUS = 2 +) + +type Client struct { + outgoing chan *Message + incoming chan *Message + redistDefault ROUTE_TYPE + conn net.Conn + Version uint8 +} + +func NewClient(network, address string, typ ROUTE_TYPE, version uint8) (*Client, error) { + conn, err := net.Dial(network, address) + if err != nil { + return nil, err + } + outgoing := make(chan *Message) + incoming := make(chan *Message, 64) + if version < 2 { + version = 2 + } else if version > 4 { + version = 4 + } + + c := &Client{ + outgoing: outgoing, + incoming: incoming, + redistDefault: typ, + conn: conn, + Version: version, + } + + go func() { + for { + m, more := <-outgoing + if more { + b, err := m.Serialize() + if err != nil { + log.WithFields(log.Fields{ + "Topic": "Zebra", + }).Warnf("failed to serialize: %v", m) + continue + } + + _, err = conn.Write(b) + if err != nil { + log.WithFields(log.Fields{ + "Topic": "Zebra", + }).Errorf("failed to write: %s", err) + close(outgoing) + } + } else { + log.Debug("finish outgoing loop") + return + } + } + }() + + // Send HELLO/ROUTER_ID_ADD messages to negotiate the Zebra message version. + c.SendHello() + c.SendRouterIDAdd() + + receiveSingleMsg := func() (*Message, error) { + headerBuf, err := readAll(conn, int(HeaderSize(version))) + if err != nil { + log.WithFields(log.Fields{ + "Topic": "Zebra", + "Error": err, + }).Error("failed to read header") + return nil, err + } + + hd := &Header{} + err = hd.DecodeFromBytes(headerBuf) + if err != nil { + log.WithFields(log.Fields{ + "Topic": "Zebra", + "Data": headerBuf, + "Error": err, + }).Error("failed to decode header") + return nil, err + } + + bodyBuf, err := readAll(conn, int(hd.Len-HeaderSize(version))) + if err != nil { + log.WithFields(log.Fields{ + "Topic": "Zebra", + "Header": hd, + "Error": err, + }).Error("failed to read body") + return nil, err + } + + m, err := ParseMessage(hd, bodyBuf) + if err != nil { + // Just outputting warnings (not error message) and ignore this + // error considering the case that body parser is not implemented + // yet. + log.WithFields(log.Fields{ + "Topic": "Zebra", + "Header": hd, + "Data": bodyBuf, + "Error": err, + }).Warn("failed to decode body") + return nil, nil + } + log.WithFields(log.Fields{ + "Topic": "Zebra", + "Message": m, + }).Debug("read message from zebra") + + return m, nil + } + + // Try to receive the first message from Zebra. + if m, err := receiveSingleMsg(); err != nil { + c.Close() + // Return error explicitly in order to retry connection. + return nil, err + } else if m != nil { + incoming <- m + } + + // Start receive loop only when the first message successfully received. + go func() { + defer close(incoming) + for { + if m, err := receiveSingleMsg(); err != nil { + return + } else if m != nil { + incoming <- m + } + } + }() + + return c, nil +} + +func readAll(conn net.Conn, length int) ([]byte, error) { + buf := make([]byte, length) + _, err := io.ReadFull(conn, buf) + return buf, err +} + +func (c *Client) Receive() chan *Message { + return c.incoming +} + +func (c *Client) Send(m *Message) { + defer func() { + if err := recover(); err != nil { + log.WithFields(log.Fields{ + "Topic": "Zebra", + }).Debugf("recovered: %s", err) + } + }() + log.WithFields(log.Fields{ + "Topic": "Zebra", + "Header": m.Header, + "Body": m.Body, + }).Debug("send command to zebra") + c.outgoing <- m +} + +func (c *Client) SendCommand(command API_TYPE, vrfId uint16, body Body) error { + var marker uint8 = HEADER_MARKER + if c.Version >= 4 { + marker = FRR_HEADER_MARKER + } + m := &Message{ + Header: Header{ + Len: HeaderSize(c.Version), + Marker: marker, + Version: c.Version, + VrfId: vrfId, + Command: command, + }, + Body: body, + } + c.Send(m) + return nil +} + +func (c *Client) SendHello() error { + if c.redistDefault > 0 { + command := HELLO + body := &HelloBody{ + RedistDefault: c.redistDefault, + Instance: 0, + } + if c.Version >= 4 { + command = FRR_HELLO + } + return c.SendCommand(command, VRF_DEFAULT, body) + } + return nil +} + +func (c *Client) SendRouterIDAdd() error { + command := ROUTER_ID_ADD + if c.Version >= 4 { + command = FRR_ROUTER_ID_ADD + } + return c.SendCommand(command, VRF_DEFAULT, nil) +} + +func (c *Client) SendInterfaceAdd() error { + command := INTERFACE_ADD + if c.Version >= 4 { + command = FRR_INTERFACE_ADD + } + return c.SendCommand(command, VRF_DEFAULT, nil) +} + +func (c *Client) SendRedistribute(t ROUTE_TYPE, vrfId uint16) error { + command := REDISTRIBUTE_ADD + if c.redistDefault != t { + bodies := make([]*RedistributeBody, 0) + if c.Version <= 3 { + bodies = append(bodies, &RedistributeBody{ + Redist: t, + }) + } else { // version >= 4 + command = FRR_REDISTRIBUTE_ADD + for _, afi := range []AFI{AFI_IP, AFI_IP6} { + bodies = append(bodies, &RedistributeBody{ + Afi: afi, + Redist: t, + Instance: 0, + }) + } + } + + for _, body := range bodies { + return c.SendCommand(command, vrfId, body) + } + } + + return nil +} + +func (c *Client) SendRedistributeDelete(t ROUTE_TYPE) error { + if t < ROUTE_MAX { + command := REDISTRIBUTE_DELETE + if c.Version >= 4 { + command = FRR_REDISTRIBUTE_DELETE + } + body := &RedistributeBody{ + Redist: t, + } + return c.SendCommand(command, VRF_DEFAULT, body) + } else { + return fmt.Errorf("unknown route type: %d", t) + } +} + +func (c *Client) SendIPRoute(vrfId uint16, body *IPRouteBody, isWithdraw bool) error { + command := IPV4_ROUTE_ADD + if c.Version <= 3 { + if body.Prefix.To4() != nil { + if isWithdraw { + command = IPV4_ROUTE_DELETE + } + } else { + if isWithdraw { + command = IPV6_ROUTE_DELETE + } else { + command = IPV6_ROUTE_ADD + } + } + } else { // version >= 4 + if body.Prefix.To4() != nil { + if isWithdraw { + command = FRR_IPV4_ROUTE_DELETE + } else { + command = FRR_IPV4_ROUTE_ADD + } + } else { + if isWithdraw { + command = FRR_IPV6_ROUTE_DELETE + } else { + command = FRR_IPV6_ROUTE_ADD + } + } + } + return c.SendCommand(command, vrfId, body) +} + +func (c *Client) SendNexthopRegister(vrfId uint16, body *NexthopRegisterBody, isWithdraw bool) error { + // Note: NEXTHOP_REGISTER and NEXTHOP_UNREGISTER messages are not + // supported in Zebra protocol version<3. + if c.Version < 3 { + return fmt.Errorf("NEXTHOP_REGISTER/NEXTHOP_UNREGISTER are not supported in version: %d", c.Version) + } + command := NEXTHOP_REGISTER + if c.Version == 3 { + if isWithdraw { + command = NEXTHOP_UNREGISTER + } + } else { // version >= 4 + if isWithdraw { + command = FRR_NEXTHOP_UNREGISTER + } else { + command = FRR_NEXTHOP_REGISTER + } + } + return c.SendCommand(command, vrfId, body) +} + +func (c *Client) Close() error { + close(c.outgoing) + return c.conn.Close() +} + +type Header struct { + Len uint16 + Marker uint8 + Version uint8 + VrfId uint16 + Command API_TYPE +} + +func (h *Header) Serialize() ([]byte, error) { + buf := make([]byte, HeaderSize(h.Version)) + binary.BigEndian.PutUint16(buf[0:2], h.Len) + buf[2] = h.Marker + buf[3] = h.Version + switch h.Version { + case 2: + binary.BigEndian.PutUint16(buf[4:6], uint16(h.Command)) + case 3, 4: + binary.BigEndian.PutUint16(buf[4:6], uint16(h.VrfId)) + binary.BigEndian.PutUint16(buf[6:8], uint16(h.Command)) + default: + return nil, fmt.Errorf("Unsupported ZAPI version: %d", h.Version) + } + return buf, nil +} + +func (h *Header) DecodeFromBytes(data []byte) error { + if uint16(len(data)) < 4 { + return fmt.Errorf("Not all ZAPI message header") + } + h.Len = binary.BigEndian.Uint16(data[0:2]) + h.Marker = data[2] + h.Version = data[3] + if uint16(len(data)) < HeaderSize(h.Version) { + return fmt.Errorf("Not all ZAPI message header") + } + switch h.Version { + case 2: + h.Command = API_TYPE(binary.BigEndian.Uint16(data[4:6])) + case 3, 4: + h.VrfId = binary.BigEndian.Uint16(data[4:6]) + h.Command = API_TYPE(binary.BigEndian.Uint16(data[6:8])) + default: + return fmt.Errorf("Unsupported ZAPI version: %d", h.Version) + } + return nil +} + +type Body interface { + DecodeFromBytes([]byte, uint8) error + Serialize(uint8) ([]byte, error) + String() string +} + +type UnknownBody struct { + Data []byte +} + +func (b *UnknownBody) DecodeFromBytes(data []byte, version uint8) error { + b.Data = data + return nil +} + +func (b *UnknownBody) Serialize(version uint8) ([]byte, error) { + return b.Data, nil +} + +func (b *UnknownBody) String() string { + return fmt.Sprintf("data: %v", b.Data) +} + +type HelloBody struct { + RedistDefault ROUTE_TYPE + Instance uint16 +} + +func (b *HelloBody) DecodeFromBytes(data []byte, version uint8) error { + b.RedistDefault = ROUTE_TYPE(data[0]) + if version >= 4 { + b.Instance = binary.BigEndian.Uint16(data[1:3]) + } + return nil +} + +func (b *HelloBody) Serialize(version uint8) ([]byte, error) { + if version <= 3 { + return []byte{uint8(b.RedistDefault)}, nil + } else { // version >= 4 + buf := make([]byte, 3) + buf[0] = uint8(b.RedistDefault) + binary.BigEndian.PutUint16(buf[1:3], b.Instance) + return buf, nil + + } +} + +func (b *HelloBody) String() string { + return fmt.Sprintf( + "route_type: %s, instance :%d", + b.RedistDefault.String(), b.Instance) +} + +type RedistributeBody struct { + Afi AFI + Redist ROUTE_TYPE + Instance uint16 +} + +func (b *RedistributeBody) DecodeFromBytes(data []byte, version uint8) error { + if version <= 3 { + b.Redist = ROUTE_TYPE(data[0]) + } else { // version >= 4 + b.Afi = AFI(data[0]) + b.Redist = ROUTE_TYPE(data[1]) + b.Instance = binary.BigEndian.Uint16(data[2:4]) + } + return nil +} + +func (b *RedistributeBody) Serialize(version uint8) ([]byte, error) { + if version <= 3 { + return []byte{uint8(b.Redist)}, nil + } else { // version >= 4 + buf := make([]byte, 4) + buf[0] = uint8(b.Afi) + buf[1] = uint8(b.Redist) + binary.BigEndian.PutUint16(buf[2:4], b.Instance) + return buf, nil + } +} + +func (b *RedistributeBody) String() string { + return fmt.Sprintf( + "afi: %s, route_type: %s, instance :%d", + b.Afi.String(), b.Redist.String(), b.Instance) +} + +type InterfaceUpdateBody struct { + Name string + Index uint32 + Status INTERFACE_STATUS + Flags uint64 + PTMEnable PTM_ENABLE + PTMStatus PTM_STATUS + Metric uint32 + Speed uint32 + MTU uint32 + MTU6 uint32 + Bandwidth uint32 + Linktype LINK_TYPE + HardwareAddr net.HardwareAddr +} + +func (b *InterfaceUpdateBody) DecodeFromBytes(data []byte, version uint8) error { + if len(data) < INTERFACE_NAMSIZ+29 { + return fmt.Errorf("lack of bytes. need %d but %d", INTERFACE_NAMSIZ+29, len(data)) + } + + b.Name = strings.Trim(string(data[:INTERFACE_NAMSIZ]), "\u0000") + data = data[INTERFACE_NAMSIZ:] + b.Index = binary.BigEndian.Uint32(data[0:4]) + b.Status = INTERFACE_STATUS(data[4]) + b.Flags = binary.BigEndian.Uint64(data[5:13]) + if version >= 4 { + b.PTMEnable = PTM_ENABLE(data[13]) + b.PTMStatus = PTM_STATUS(data[14]) + b.Metric = binary.BigEndian.Uint32(data[15:19]) + b.Speed = binary.BigEndian.Uint32(data[19:23]) + data = data[23:] + } else { + b.Metric = binary.BigEndian.Uint32(data[13:17]) + data = data[17:] + } + b.MTU = binary.BigEndian.Uint32(data[0:4]) + b.MTU6 = binary.BigEndian.Uint32(data[4:8]) + b.Bandwidth = binary.BigEndian.Uint32(data[8:12]) + if version >= 3 { + b.Linktype = LINK_TYPE(binary.BigEndian.Uint32(data[12:16])) + data = data[16:] + } else { + data = data[12:] + } + l := binary.BigEndian.Uint32(data[:4]) + if l > 0 { + if len(data) < 4+int(l) { + return fmt.Errorf("lack of bytes. need %d but %d", 4+l, len(data)) + } + b.HardwareAddr = data[4 : 4+l] + } + return nil +} + +func (b *InterfaceUpdateBody) Serialize(version uint8) ([]byte, error) { + return []byte{}, nil +} + +func (b *InterfaceUpdateBody) String() string { + s := fmt.Sprintf( + "name: %s, idx: %d, status: %s, flags: %s, ptm_enable: %s, ptm_status: %s, metric: %d, speed: %d, mtu: %d, mtu6: %d, bandwidth: %d, linktype: %s", + b.Name, b.Index, b.Status.String(), intfflag2string(b.Flags), b.PTMEnable.String(), b.PTMStatus.String(), b.Metric, b.Speed, b.MTU, b.MTU6, b.Bandwidth, b.Linktype.String()) + if len(b.HardwareAddr) > 0 { + return s + fmt.Sprintf(", mac: %s", b.HardwareAddr.String()) + } + return s +} + +type InterfaceAddressUpdateBody struct { + Index uint32 + Flags INTERFACE_ADDRESS_FLAG + Prefix net.IP + Length uint8 + Destination net.IP +} + +func (b *InterfaceAddressUpdateBody) DecodeFromBytes(data []byte, version uint8) error { + b.Index = binary.BigEndian.Uint32(data[:4]) + b.Flags = INTERFACE_ADDRESS_FLAG(data[4]) + family := data[5] + var addrlen int8 + switch family { + case syscall.AF_INET: + addrlen = net.IPv4len + case syscall.AF_INET6: + addrlen = net.IPv6len + default: + return fmt.Errorf("unknown address family: %d", family) + } + b.Prefix = data[6 : 6+addrlen] + b.Length = data[6+addrlen] + b.Destination = data[7+addrlen : 7+addrlen*2] + return nil +} + +func (b *InterfaceAddressUpdateBody) Serialize(version uint8) ([]byte, error) { + return []byte{}, nil +} + +func (b *InterfaceAddressUpdateBody) String() string { + return fmt.Sprintf( + "idx: %d, flags: %s, addr: %s/%d", + b.Index, b.Flags.String(), b.Prefix.String(), b.Length) +} + +type RouterIDUpdateBody struct { + Length uint8 + Prefix net.IP +} + +func (b *RouterIDUpdateBody) DecodeFromBytes(data []byte, version uint8) error { + family := data[0] + var addrlen int8 + switch family { + case syscall.AF_INET: + addrlen = net.IPv4len + case syscall.AF_INET6: + addrlen = net.IPv6len + default: + return fmt.Errorf("unknown address family: %d", family) + } + b.Prefix = data[1 : 1+addrlen] + b.Length = data[1+addrlen] + return nil +} + +func (b *RouterIDUpdateBody) Serialize(version uint8) ([]byte, error) { + return []byte{}, nil +} + +func (b *RouterIDUpdateBody) String() string { + return fmt.Sprintf("id: %s/%d", b.Prefix.String(), b.Length) +} + +type IPRouteBody struct { + Type ROUTE_TYPE + Instance uint16 + Flags FLAG + Message MESSAGE_FLAG + SAFI SAFI + Prefix net.IP + PrefixLength uint8 + SrcPrefix net.IP + SrcPrefixLength uint8 + Nexthops []net.IP + Ifindexs []uint32 + Distance uint8 + Metric uint32 + Mtu uint32 + Tag uint32 + Api API_TYPE +} + +func (b *IPRouteBody) RouteFamily() bgp.RouteFamily { + switch b.Api { + case IPV4_ROUTE_ADD, IPV4_ROUTE_DELETE, FRR_REDISTRIBUTE_IPV4_ADD, FRR_REDISTRIBUTE_IPV4_DEL: + return bgp.RF_IPv4_UC + case IPV6_ROUTE_ADD, IPV6_ROUTE_DELETE, FRR_REDISTRIBUTE_IPV6_ADD, FRR_REDISTRIBUTE_IPV6_DEL: + return bgp.RF_IPv6_UC + default: + return bgp.RF_OPAQUE + } +} + +func (b *IPRouteBody) IsWithdraw() bool { + switch b.Api { + case IPV4_ROUTE_DELETE, FRR_REDISTRIBUTE_IPV4_DEL, IPV6_ROUTE_DELETE, FRR_REDISTRIBUTE_IPV6_DEL: + return true + default: + return false + } +} + +func (b *IPRouteBody) Serialize(version uint8) ([]byte, error) { + + var buf []byte + nhfIPv4 := uint8(NEXTHOP_IPV4) + nhfIPv6 := uint8(NEXTHOP_IPV6) + nhfIndx := uint8(NEXTHOP_IFINDEX) + nhfBlkH := uint8(NEXTHOP_BLACKHOLE) + if version <= 3 { + buf = make([]byte, 5) + buf[0] = uint8(b.Type) + buf[1] = uint8(b.Flags) + buf[2] = uint8(b.Message) + binary.BigEndian.PutUint16(buf[3:5], uint16(b.SAFI)) + } else { // version >= 4 + buf = make([]byte, 10) + buf[0] = uint8(b.Type) + binary.BigEndian.PutUint16(buf[1:3], uint16(b.Instance)) + binary.BigEndian.PutUint32(buf[3:7], uint32(b.Flags)) + buf[7] = uint8(b.Message) + binary.BigEndian.PutUint16(buf[8:10], uint16(b.SAFI)) + nhfIPv4 = uint8(FRR_NEXTHOP_IPV4) + nhfIPv6 = uint8(FRR_NEXTHOP_IPV6) + nhfIndx = uint8(FRR_NEXTHOP_IFINDEX) + nhfBlkH = uint8(FRR_NEXTHOP_BLACKHOLE) + } + byteLen := (int(b.PrefixLength) + 7) / 8 + buf = append(buf, b.PrefixLength) + buf = append(buf, b.Prefix[:byteLen]...) + if b.Message&FRR_MESSAGE_SRCPFX > 0 { + byteLen = (int(b.SrcPrefixLength) + 7) / 8 + buf = append(buf, b.SrcPrefixLength) + buf = append(buf, b.SrcPrefix[:byteLen]...) + } + + if b.Message&MESSAGE_NEXTHOP > 0 { + if b.Flags&FLAG_BLACKHOLE > 0 { + buf = append(buf, []byte{1, nhfBlkH}...) + } else { + buf = append(buf, uint8(len(b.Nexthops)+len(b.Ifindexs))) + } + + for _, v := range b.Nexthops { + if v.To4() != nil { + buf = append(buf, nhfIPv4) + buf = append(buf, v.To4()...) + } else { + buf = append(buf, nhfIPv6) + buf = append(buf, v.To16()...) + } + } + + for _, v := range b.Ifindexs { + buf = append(buf, nhfIndx) + bbuf := make([]byte, 4) + binary.BigEndian.PutUint32(bbuf, v) + buf = append(buf, bbuf...) + } + } + + if b.Message&MESSAGE_DISTANCE > 0 { + buf = append(buf, b.Distance) + } + if b.Message&MESSAGE_METRIC > 0 { + bbuf := make([]byte, 4) + binary.BigEndian.PutUint32(bbuf, b.Metric) + buf = append(buf, bbuf...) + } + if version <= 3 { + if b.Message&MESSAGE_MTU > 0 { + bbuf := make([]byte, 4) + binary.BigEndian.PutUint32(bbuf, b.Mtu) + buf = append(buf, bbuf...) + } + if b.Message&MESSAGE_TAG > 0 { + bbuf := make([]byte, 4) + binary.BigEndian.PutUint32(bbuf, b.Tag) + buf = append(buf, bbuf...) + } + } else { // version >= 4 + if b.Message&FRR_MESSAGE_TAG > 0 { + bbuf := make([]byte, 4) + binary.BigEndian.PutUint32(bbuf, b.Tag) + buf = append(buf, bbuf...) + } + if b.Message&FRR_MESSAGE_MTU > 0 { + bbuf := make([]byte, 4) + binary.BigEndian.PutUint32(bbuf, b.Mtu) + buf = append(buf, bbuf...) + } + } + return buf, nil +} + +func (b *IPRouteBody) DecodeFromBytes(data []byte, version uint8) error { + isV4 := true + if version <= 3 { + isV4 = b.Api == IPV4_ROUTE_ADD || b.Api == IPV4_ROUTE_DELETE + } else { + isV4 = b.Api == FRR_REDISTRIBUTE_IPV4_ADD || b.Api == FRR_REDISTRIBUTE_IPV4_DEL + } + var addrLen uint8 = net.IPv4len + if !isV4 { + addrLen = net.IPv6len + } + + b.Type = ROUTE_TYPE(data[0]) + if version <= 3 { + b.Flags = FLAG(data[1]) + data = data[2:] + } else { // version >= 4 + b.Instance = binary.BigEndian.Uint16(data[1:3]) + b.Flags = FLAG(binary.BigEndian.Uint32(data[3:7])) + data = data[7:] + } + + b.Message = MESSAGE_FLAG(data[0]) + b.SAFI = SAFI(SAFI_UNICAST) + + b.PrefixLength = data[1] + if b.PrefixLength > addrLen*8 { + return fmt.Errorf("prefix length is greater than %d", addrLen*8) + } + pos := 2 + buf := make([]byte, addrLen) + byteLen := int((b.PrefixLength + 7) / 8) + copy(buf, data[pos:pos+byteLen]) + if isV4 { + b.Prefix = net.IP(buf).To4() + } else { + b.Prefix = net.IP(buf).To16() + } + pos += byteLen + + if b.Message&FRR_MESSAGE_SRCPFX > 0 { + b.SrcPrefixLength = data[pos] + pos += 1 + buf = make([]byte, addrLen) + byteLen = int((b.SrcPrefixLength + 7) / 8) + copy(buf, data[pos:pos+byteLen]) + if isV4 { + b.SrcPrefix = net.IP(buf).To4() + } else { + b.SrcPrefix = net.IP(buf).To16() + } + pos += byteLen + } + + rest := 0 + var numNexthop int + if b.Message&MESSAGE_NEXTHOP > 0 { + numNexthop = int(data[pos]) + // rest = numNexthop(1) + (nexthop(4 or 16) + placeholder(1) + ifindex(4)) * numNexthop + rest += 1 + numNexthop*(int(addrLen)+5) + } + if b.Message&MESSAGE_DISTANCE > 0 { + // distance(1) + rest += 1 + } + if b.Message&MESSAGE_METRIC > 0 { + // metric(4) + rest += 4 + } + if version <= 3 { + if b.Message&MESSAGE_MTU > 0 { + // mtu(4) + rest += 4 + } + if b.Message&MESSAGE_TAG > 0 { + // tag(4) + rest += 4 + } + } else { // version >= 4 + if b.Message&FRR_MESSAGE_TAG > 0 { + // tag(4) + rest += 4 + } + if b.Message&FRR_MESSAGE_MTU > 0 { + // mtu(4) + rest += 4 + } + } + + if len(data[pos:]) != rest { + return fmt.Errorf("message length invalid") + } + + b.Nexthops = []net.IP{} + b.Ifindexs = []uint32{} + + if b.Message&MESSAGE_NEXTHOP > 0 { + pos += 1 + for i := 0; i < numNexthop; i++ { + addr := data[pos : pos+int(addrLen)] + var nexthop net.IP + if isV4 { + nexthop = net.IP(addr).To4() + } else { + nexthop = net.IP(addr).To16() + } + b.Nexthops = append(b.Nexthops, nexthop) + + // skip nexthop and 1byte place holder + pos += int(addrLen + 1) + ifidx := binary.BigEndian.Uint32(data[pos : pos+4]) + b.Ifindexs = append(b.Ifindexs, ifidx) + pos += 4 + } + } + + if b.Message&MESSAGE_DISTANCE > 0 { + b.Distance = data[pos] + pos += 1 + } + if b.Message&MESSAGE_METRIC > 0 { + b.Metric = binary.BigEndian.Uint32(data[pos : pos+4]) + pos += 4 + } + if version <= 3 { + if b.Message&MESSAGE_MTU > 0 { + b.Mtu = binary.BigEndian.Uint32(data[pos : pos+4]) + pos += 4 + } + if b.Message&MESSAGE_TAG > 0 { + b.Tag = binary.BigEndian.Uint32(data[pos : pos+4]) + pos += 4 + } + } else { + if b.Message&FRR_MESSAGE_TAG > 0 { + b.Tag = binary.BigEndian.Uint32(data[pos : pos+4]) + pos += 4 + } + if b.Message&FRR_MESSAGE_MTU > 0 { + b.Mtu = binary.BigEndian.Uint32(data[pos : pos+4]) + pos += 4 + } + } + + return nil +} + +func (b *IPRouteBody) String() string { + s := fmt.Sprintf( + "type: %s, instance: %d, flags: %s, message: %d, safi: %s, prefix: %s/%d, src_prefix: %s/%d", + b.Type.String(), b.Instance, b.Flags.String(), b.Message, b.SAFI.String(), b.Prefix.String(), b.PrefixLength, b.SrcPrefix.String(), b.SrcPrefixLength) + for i, nh := range b.Nexthops { + s += fmt.Sprintf(", nexthops[%d]: %s", i, nh.String()) + } + for i, idx := range b.Ifindexs { + s += fmt.Sprintf(", ifindex[%d]: %d", i, idx) + } + return s + fmt.Sprintf( + ", distance: %d, metric: %d, mtu: %d, tag: %d", + b.Distance, b.Metric, b.Mtu, b.Tag) +} + +type NexthopLookupBody struct { + Api API_TYPE + Addr net.IP + Distance uint8 + Metric uint32 + Nexthops []*Nexthop +} + +type Nexthop struct { + Ifname string + Ifindex uint32 + Type NEXTHOP_FLAG + Addr net.IP +} + +func (n *Nexthop) String() string { + s := fmt.Sprintf( + "type: %s, addr: %s, ifindex: %d, ifname: %s", + n.Type.String(), n.Addr.String(), n.Ifindex, n.Ifname) + return s +} + +func decodeNexthopsFromBytes(nexthops *[]*Nexthop, data []byte, isV4 bool, version uint8) (int, error) { + addrLen := net.IPv4len + if !isV4 { + addrLen = net.IPv6len + } + nhIfindex := NEXTHOP_IFINDEX + nhIfname := NEXTHOP_IFNAME + nhIPv4 := NEXTHOP_IPV4 + nhIPv4Ifindex := NEXTHOP_IPV4_IFINDEX + nhIPv4Ifname := NEXTHOP_IPV4_IFNAME + nhIPv6 := NEXTHOP_IPV6 + nhIPv6Ifindex := NEXTHOP_IPV6_IFINDEX + nhIPv6Ifname := NEXTHOP_IPV6_IFNAME + if version >= 4 { + nhIfindex = FRR_NEXTHOP_IFINDEX + nhIfname = NEXTHOP_FLAG(0) + nhIPv4 = FRR_NEXTHOP_IPV4 + nhIPv4Ifindex = FRR_NEXTHOP_IPV4_IFINDEX + nhIPv4Ifname = NEXTHOP_FLAG(0) + nhIPv6 = FRR_NEXTHOP_IPV6 + nhIPv6Ifindex = FRR_NEXTHOP_IPV6_IFINDEX + nhIPv6Ifname = NEXTHOP_FLAG(0) + } + + numNexthop := int(data[0]) + offset := 1 + + for i := 0; i < numNexthop; i++ { + nh := &Nexthop{} + nh.Type = NEXTHOP_FLAG(data[offset]) + offset += 1 + + switch nh.Type { + case nhIfindex, nhIfname: + nh.Ifindex = binary.BigEndian.Uint32(data[offset : offset+4]) + offset += 4 + + case nhIPv4, nhIPv6: + if isV4 { + nh.Addr = net.IP(data[offset : offset+addrLen]).To4() + } else { + nh.Addr = net.IP(data[offset : offset+addrLen]).To16() + } + offset += addrLen + if version >= 4 { + // On FRRouting version 3.0 or later, NEXTHOP_IPV4 and + // NEXTHOP_IPV6 have the same structure with + // NEXTHOP_TYPE_IPV4_IFINDEX and NEXTHOP_TYPE_IPV6_IFINDEX. + nh.Ifindex = binary.BigEndian.Uint32(data[offset : offset+4]) + offset += 4 + } + + case nhIPv4Ifindex, nhIPv4Ifname, nhIPv6Ifindex, nhIPv6Ifname: + if isV4 { + nh.Addr = net.IP(data[offset : offset+addrLen]).To4() + } else { + nh.Addr = net.IP(data[offset : offset+addrLen]).To16() + } + offset += addrLen + nh.Ifindex = binary.BigEndian.Uint32(data[offset : offset+4]) + offset += 4 + } + *nexthops = append(*nexthops, nh) + } + + return offset, nil +} + +func (b *NexthopLookupBody) Serialize(version uint8) ([]byte, error) { + isV4 := false + if version <= 3 { + isV4 = b.Api == IPV4_NEXTHOP_LOOKUP + } else { // version >= 4 + isV4 = b.Api == FRR_IPV4_NEXTHOP_LOOKUP_MRIB + } + + buf := make([]byte, 0) + + if isV4 { + buf = append(buf, b.Addr.To4()...) + } else { + buf = append(buf, b.Addr.To16()...) + } + return buf, nil +} + +func (b *NexthopLookupBody) DecodeFromBytes(data []byte, version uint8) error { + isV4 := false + if version <= 3 { + isV4 = b.Api == IPV4_NEXTHOP_LOOKUP + } else { // version >= 4 + isV4 = b.Api == FRR_IPV4_NEXTHOP_LOOKUP_MRIB + } + addrLen := net.IPv4len + if !isV4 { + addrLen = net.IPv6len + } + + if len(data) < addrLen { + return fmt.Errorf("message length invalid") + } + + buf := make([]byte, addrLen) + copy(buf, data[0:addrLen]) + pos := addrLen + + if isV4 { + b.Addr = net.IP(buf).To4() + } else { + b.Addr = net.IP(buf).To16() + } + + if version >= 4 { + b.Distance = data[pos] + pos++ + } + + if len(data[pos:]) > int(1+addrLen) { + b.Metric = binary.BigEndian.Uint32(data[pos : pos+4]) + pos += 4 + b.Nexthops = []*Nexthop{} + if nexthopsByteLen, err := decodeNexthopsFromBytes(&b.Nexthops, data[pos:], isV4, version); err != nil { + return err + } else { + pos += nexthopsByteLen + } + } + + return nil +} + +func (b *NexthopLookupBody) String() string { + s := fmt.Sprintf( + "addr: %s, distance:%d, metric: %d", + b.Addr.String(), b.Distance, b.Metric) + if len(b.Nexthops) > 0 { + for _, nh := range b.Nexthops { + s = s + fmt.Sprintf(", nexthop:{%s}", nh.String()) + } + } + return s +} + +type ImportLookupBody struct { + Api API_TYPE + PrefixLength uint8 + Prefix net.IP + Addr net.IP + Metric uint32 + Nexthops []*Nexthop +} + +func (b *ImportLookupBody) Serialize(version uint8) ([]byte, error) { + buf := make([]byte, 1) + buf[0] = b.PrefixLength + buf = append(buf, b.Addr.To4()...) + return buf, nil +} + +func (b *ImportLookupBody) DecodeFromBytes(data []byte, version uint8) error { + isV4 := b.Api == IPV4_IMPORT_LOOKUP + addrLen := net.IPv4len + if !isV4 { + addrLen = net.IPv6len + } + + if len(data) < addrLen { + return fmt.Errorf("message length invalid") + } + + buf := make([]byte, addrLen) + copy(buf, data[0:addrLen]) + pos := addrLen + + b.Addr = net.IP(buf).To4() + + if len(data[pos:]) > int(1+addrLen) { + b.Metric = binary.BigEndian.Uint32(data[pos : pos+4]) + pos += 4 + b.Nexthops = []*Nexthop{} + if nexthopsByteLen, err := decodeNexthopsFromBytes(&b.Nexthops, data[pos:], isV4, version); err != nil { + return err + } else { + pos += nexthopsByteLen + } + } + + return nil +} + +func (b *ImportLookupBody) String() string { + s := fmt.Sprintf( + "prefix: %s/%d, addr: %s, metric: %d", + b.Prefix.String(), b.PrefixLength, b.Addr.String(), b.Metric) + if len(b.Nexthops) > 0 { + for _, nh := range b.Nexthops { + s = s + fmt.Sprintf(", nexthop:{%s}", nh.String()) + } + } + return s +} + +type RegisteredNexthop struct { + Connected uint8 + Family uint16 + // Note: Ignores PrefixLength (uint8), + // because this field should be always: + // - 32 if Address Family is AF_INET + // - 128 if Address Family is AF_INET6 + Prefix net.IP +} + +func (n *RegisteredNexthop) Len() int { + // Connected (1 byte) + Address Family (2 bytes) + Prefix Length (1 byte) + Prefix (variable) + if n.Family == uint16(syscall.AF_INET) { + return 4 + net.IPv4len + } else { + return 4 + net.IPv6len + } +} + +func (n *RegisteredNexthop) Serialize() ([]byte, error) { + // Connected (1 byte) + buf := make([]byte, 4) + buf[0] = byte(n.Connected) + + // Address Family (2 bytes) + binary.BigEndian.PutUint16(buf[1:3], n.Family) + + // Prefix Length (1 byte) + Prefix (variable) + switch n.Family { + case uint16(syscall.AF_INET): + buf[3] = byte(net.IPv4len * 8) + buf = append(buf, n.Prefix.To4()...) + case uint16(syscall.AF_INET6): + buf[3] = byte(net.IPv6len * 8) + buf = append(buf, n.Prefix.To16()...) + default: + return nil, fmt.Errorf("invalid address family: %d", n.Family) + } + + return buf, nil +} + +func (n *RegisteredNexthop) DecodeFromBytes(data []byte) error { + // Connected (1 byte) + n.Connected = uint8(data[0]) + offset := 1 + + // Address Family (2 bytes) + n.Family = binary.BigEndian.Uint16(data[offset : offset+2]) + isV4 := n.Family == uint16(syscall.AF_INET) + addrLen := int(net.IPv4len) + if !isV4 { + addrLen = net.IPv6len + } + // Note: Ignores Prefix Length (1 byte) + offset += 3 + + // Prefix (variable) + if isV4 { + n.Prefix = net.IP(data[offset : offset+addrLen]).To4() + } else { + n.Prefix = net.IP(data[offset : offset+addrLen]).To16() + } + + return nil +} + +func (n *RegisteredNexthop) String() string { + return fmt.Sprintf( + "connected: %d, family: %d, prefix: %s", + n.Connected, n.Family, n.Prefix.String()) +} + +type NexthopRegisterBody struct { + Api API_TYPE + Nexthops []*RegisteredNexthop +} + +func (b *NexthopRegisterBody) Serialize(version uint8) ([]byte, error) { + buf := make([]byte, 0) + + // List of Registered Nexthops + for _, nh := range b.Nexthops { + nhBuf, err := nh.Serialize() + if err != nil { + return nil, err + } + buf = append(buf, nhBuf...) + } + + return buf, nil +} + +func (b *NexthopRegisterBody) DecodeFromBytes(data []byte, version uint8) error { + offset := 0 + + // List of Registered Nexthops + b.Nexthops = []*RegisteredNexthop{} + for len(data[offset:]) > 0 { + nh := new(RegisteredNexthop) + err := nh.DecodeFromBytes(data[offset:]) + if err != nil { + return err + } + b.Nexthops = append(b.Nexthops, nh) + + offset += nh.Len() + if len(data) < offset { + break + } + } + + return nil +} + +func (b *NexthopRegisterBody) String() string { + s := make([]string, 0) + for _, nh := range b.Nexthops { + s = append(s, fmt.Sprintf("nexthop:{%s}", nh.String())) + } + return strings.Join(s, ", ") +} + +type NexthopUpdateBody struct { + Api API_TYPE + Family uint16 + // Note: Ignores PrefixLength (uint8), + // because this field should be always: + // - 32 if Address Family is AF_INET + // - 128 if Address Family is AF_INET6 + Prefix net.IP + Distance uint8 + Metric uint32 + Nexthops []*Nexthop +} + +func (b *NexthopUpdateBody) Serialize(version uint8) ([]byte, error) { + // Address Family (2 bytes) + buf := make([]byte, 3) + binary.BigEndian.PutUint16(buf, b.Family) + + // Prefix Length (1 byte) + Prefix (variable) + switch b.Family { + case uint16(syscall.AF_INET): + buf[2] = byte(net.IPv4len * 8) + buf = append(buf, b.Prefix.To4()...) + case uint16(syscall.AF_INET6): + buf[2] = byte(net.IPv6len * 8) + buf = append(buf, b.Prefix.To16()...) + default: + return nil, fmt.Errorf("invalid address family: %d", b.Family) + } + + return buf, nil +} + +func (b *NexthopUpdateBody) DecodeFromBytes(data []byte, version uint8) error { + // Address Family (2 bytes) + b.Family = binary.BigEndian.Uint16(data[0:2]) + isV4 := b.Family == uint16(syscall.AF_INET) + addrLen := int(net.IPv4len) + if !isV4 { + addrLen = net.IPv6len + } + // Note: Ignores Prefix Length (1 byte) + offset := 3 + + // Prefix (variable) + if isV4 { + b.Prefix = net.IP(data[offset : offset+addrLen]).To4() + } else { + b.Prefix = net.IP(data[offset : offset+addrLen]).To16() + } + offset += addrLen + + // Distance (1 byte) (if version>=4) + // Metric (4 bytes) + // Number of Nexthops (1 byte) + if version >= 4 { + if len(data[offset:]) < 6 { + return fmt.Errorf("invalid message length: missing distance(1 byte), metric(4 bytes) or nexthops(1 byte): %d<6", len(data[offset:])) + } + b.Distance = data[offset] + offset += 1 + } else if len(data[offset:]) < 5 { + return fmt.Errorf("invalid message length: missing metric(4 bytes) or nexthops(1 byte): %d<5", len(data[offset:])) + } + b.Metric = binary.BigEndian.Uint32(data[offset : offset+4]) + offset += 4 + + // List of Nexthops + b.Nexthops = []*Nexthop{} + if nexthopsByteLen, err := decodeNexthopsFromBytes(&b.Nexthops, data[offset:], isV4, version); err != nil { + return err + } else { + offset += nexthopsByteLen + } + + return nil +} + +func (b *NexthopUpdateBody) String() string { + s := fmt.Sprintf( + "family: %d, prefix: %s, distance: %d, metric: %d", + b.Family, b.Prefix.String(), b.Distance, b.Metric) + for _, nh := range b.Nexthops { + s = s + fmt.Sprintf(", nexthop:{%s}", nh.String()) + } + return s +} + +type Message struct { + Header Header + Body Body +} + +func (m *Message) Serialize() ([]byte, error) { + var body []byte + if m.Body != nil { + var err error + body, err = m.Body.Serialize(m.Header.Version) + if err != nil { + return nil, err + } + } + m.Header.Len = uint16(len(body)) + HeaderSize(m.Header.Version) + hdr, err := m.Header.Serialize() + if err != nil { + return nil, err + } + return append(hdr, body...), nil +} + +func (m *Message) parseMessage(data []byte) error { + switch m.Header.Command { + case INTERFACE_ADD, INTERFACE_DELETE, INTERFACE_UP, INTERFACE_DOWN: + m.Body = &InterfaceUpdateBody{} + case INTERFACE_ADDRESS_ADD, INTERFACE_ADDRESS_DELETE: + m.Body = &InterfaceAddressUpdateBody{} + case ROUTER_ID_UPDATE: + m.Body = &RouterIDUpdateBody{} + case IPV4_ROUTE_ADD, IPV6_ROUTE_ADD, IPV4_ROUTE_DELETE, IPV6_ROUTE_DELETE: + m.Body = &IPRouteBody{Api: m.Header.Command} + case IPV4_NEXTHOP_LOOKUP, IPV6_NEXTHOP_LOOKUP: + m.Body = &NexthopLookupBody{Api: m.Header.Command} + case IPV4_IMPORT_LOOKUP: + m.Body = &ImportLookupBody{Api: m.Header.Command} + case NEXTHOP_UPDATE: + m.Body = &NexthopUpdateBody{Api: m.Header.Command} + default: + m.Body = &UnknownBody{} + } + return m.Body.DecodeFromBytes(data, m.Header.Version) +} + +func (m *Message) parseFrrMessage(data []byte) error { + switch m.Header.Command { + case FRR_INTERFACE_ADD, FRR_INTERFACE_DELETE, FRR_INTERFACE_UP, FRR_INTERFACE_DOWN: + m.Body = &InterfaceUpdateBody{} + case FRR_INTERFACE_ADDRESS_ADD, FRR_INTERFACE_ADDRESS_DELETE: + m.Body = &InterfaceAddressUpdateBody{} + case FRR_ROUTER_ID_UPDATE: + m.Body = &RouterIDUpdateBody{} + case FRR_NEXTHOP_UPDATE: + m.Body = &NexthopUpdateBody{} + case FRR_INTERFACE_NBR_ADDRESS_ADD, FRR_INTERFACE_NBR_ADDRESS_DELETE: + // TODO + m.Body = &UnknownBody{} + case FRR_INTERFACE_BFD_DEST_UPDATE: + // TODO + m.Body = &UnknownBody{} + case FRR_IMPORT_CHECK_UPDATE: + // TODO + m.Body = &UnknownBody{} + case FRR_BFD_DEST_REPLAY: + // TODO + m.Body = &UnknownBody{} + case FRR_REDISTRIBUTE_IPV4_ADD, FRR_REDISTRIBUTE_IPV4_DEL, FRR_REDISTRIBUTE_IPV6_ADD, FRR_REDISTRIBUTE_IPV6_DEL: + m.Body = &IPRouteBody{Api: m.Header.Command} + case FRR_INTERFACE_VRF_UPDATE: + // TODO + m.Body = &UnknownBody{} + case FRR_INTERFACE_LINK_PARAMS: + // TODO + m.Body = &UnknownBody{} + case FRR_PW_STATUS_UPDATE: + // TODO + m.Body = &UnknownBody{} + default: + m.Body = &UnknownBody{} + } + return m.Body.DecodeFromBytes(data, m.Header.Version) +} + +func ParseMessage(hdr *Header, data []byte) (m *Message, err error) { + m = &Message{Header: *hdr} + if m.Header.Version == 4 { + err = m.parseFrrMessage(data) + } else { + err = m.parseMessage(data) + } + if err != nil { + return nil, err + } + return m, nil +} diff --git a/internal/pkg/zebra/zapi_bsd.go b/internal/pkg/zebra/zapi_bsd.go new file mode 100644 index 00000000..8960e796 --- /dev/null +++ b/internal/pkg/zebra/zapi_bsd.go @@ -0,0 +1,58 @@ +// Copyright (C) 2014, 2015 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. + +// +build freebsd netbsd openbsd + +package zebra + +import ( + "strings" + "syscall" +) + +func intfflag2string(flag uint64) string { + ss := make([]string, 0, 10) + if flag&syscall.IFF_UP > 0 { + ss = append(ss, "UP") + } + if flag&syscall.IFF_BROADCAST > 0 { + ss = append(ss, "BROADCAST") + } + if flag&syscall.IFF_DEBUG > 0 { + ss = append(ss, "DEBUG") + } + if flag&syscall.IFF_LOOPBACK > 0 { + ss = append(ss, "LOOPBACK") + } + if flag&syscall.IFF_POINTOPOINT > 0 { + ss = append(ss, "POINTOPOINT") + } + if flag&syscall.IFF_RUNNING > 0 { + ss = append(ss, "RUNNING") + } + if flag&syscall.IFF_NOARP > 0 { + ss = append(ss, "NOARP") + } + if flag&syscall.IFF_PROMISC > 0 { + ss = append(ss, "PROMISC") + } + if flag&syscall.IFF_ALLMULTI > 0 { + ss = append(ss, "ALLMULTI") + } + if flag&syscall.IFF_MULTICAST > 0 { + ss = append(ss, "MULTICAST") + } + return strings.Join(ss, " | ") +} diff --git a/internal/pkg/zebra/zapi_darwin.go b/internal/pkg/zebra/zapi_darwin.go new file mode 100644 index 00000000..a2536913 --- /dev/null +++ b/internal/pkg/zebra/zapi_darwin.go @@ -0,0 +1,59 @@ +// Copyright (C) 2014, 2015 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 zebra + +import ( + "strings" + "syscall" +) + +func intfflag2string(flag uint64) string { + ss := make([]string, 0, 10) + if flag&syscall.IFF_UP > 0 { + ss = append(ss, "UP") + } + if flag&syscall.IFF_BROADCAST > 0 { + ss = append(ss, "BROADCAST") + } + if flag&syscall.IFF_DEBUG > 0 { + ss = append(ss, "DEBUG") + } + if flag&syscall.IFF_LOOPBACK > 0 { + ss = append(ss, "LOOPBACK") + } + if flag&syscall.IFF_POINTOPOINT > 0 { + ss = append(ss, "POINTOPOINT") + } + if flag&syscall.IFF_NOTRAILERS > 0 { + ss = append(ss, "NOTRAILERS") + } + if flag&syscall.IFF_RUNNING > 0 { + ss = append(ss, "RUNNING") + } + if flag&syscall.IFF_NOARP > 0 { + ss = append(ss, "NOARP") + } + if flag&syscall.IFF_PROMISC > 0 { + ss = append(ss, "PROMISC") + } + if flag&syscall.IFF_ALLMULTI > 0 { + ss = append(ss, "ALLMULTI") + } + if flag&syscall.IFF_MULTICAST > 0 { + ss = append(ss, "MULTICAST") + } + return strings.Join(ss, " | ") +} diff --git a/internal/pkg/zebra/zapi_linux.go b/internal/pkg/zebra/zapi_linux.go new file mode 100644 index 00000000..66fccb74 --- /dev/null +++ b/internal/pkg/zebra/zapi_linux.go @@ -0,0 +1,83 @@ +// Copyright (C) 2014, 2015 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 zebra + +import ( + "strings" + "syscall" +) + +func intfflag2string(flag uint64) string { + ss := make([]string, 0, 10) + if flag&syscall.IFF_UP > 0 { + ss = append(ss, "UP") + } + if flag&syscall.IFF_BROADCAST > 0 { + ss = append(ss, "BROADCAST") + } + if flag&syscall.IFF_DEBUG > 0 { + ss = append(ss, "DEBUG") + } + if flag&syscall.IFF_LOOPBACK > 0 { + ss = append(ss, "LOOPBACK") + } + if flag&syscall.IFF_POINTOPOINT > 0 { + ss = append(ss, "POINTOPOINT") + } + if flag&syscall.IFF_NOTRAILERS > 0 { + ss = append(ss, "NOTRAILERS") + } + if flag&syscall.IFF_RUNNING > 0 { + ss = append(ss, "RUNNING") + } + if flag&syscall.IFF_NOARP > 0 { + ss = append(ss, "NOARP") + } + if flag&syscall.IFF_PROMISC > 0 { + ss = append(ss, "PROMISC") + } + if flag&syscall.IFF_ALLMULTI > 0 { + ss = append(ss, "ALLMULTI") + } + if flag&syscall.IFF_MASTER > 0 { + ss = append(ss, "MASTER") + } + if flag&syscall.IFF_SLAVE > 0 { + ss = append(ss, "SLAVE") + } + if flag&syscall.IFF_MULTICAST > 0 { + ss = append(ss, "MULTICAST") + } + if flag&syscall.IFF_PORTSEL > 0 { + ss = append(ss, "PORTSEL") + } + if flag&syscall.IFF_AUTOMEDIA > 0 { + ss = append(ss, "AUTOMEDIA") + } + if flag&syscall.IFF_DYNAMIC > 0 { + ss = append(ss, "DYNAMIC") + } + // if flag&syscall.IFF_LOWER_UP > 0 { + // ss = append(ss, "LOWER_UP") + // } + // if flag&syscall.IFF_DORMANT > 0 { + // ss = append(ss, "DORMANT") + // } + // if flag&syscall.IFF_ECHO > 0 { + // ss = append(ss, "ECHO") + // } + return strings.Join(ss, " | ") +} diff --git a/internal/pkg/zebra/zapi_test.go b/internal/pkg/zebra/zapi_test.go new file mode 100644 index 00000000..9fda5416 --- /dev/null +++ b/internal/pkg/zebra/zapi_test.go @@ -0,0 +1,531 @@ +// Copyright (C) 2014, 2015 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 zebra + +import ( + "encoding/binary" + "net" + "syscall" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/stretchr/testify/assert" +) + +func Test_Header(t *testing.T) { + assert := assert.New(t) + + //DecodeFromBytes + buf := make([]byte, 6) + binary.BigEndian.PutUint16(buf[0:], 10) + buf[2] = HEADER_MARKER + buf[3] = 2 + binary.BigEndian.PutUint16(buf[4:], uint16(IPV4_ROUTE_ADD)) + h := &Header{} + err := h.DecodeFromBytes(buf) + assert.Equal(nil, err) + + //Serialize + buf, err = h.Serialize() + assert.Equal(nil, err) + h2 := &Header{} + err = h2.DecodeFromBytes(buf) + assert.Equal(nil, err) + assert.Equal(h, h2) + + // header_size mismatch + buf = make([]byte, HeaderSize(2)-1) + binary.BigEndian.PutUint16(buf[0:], 10) + buf[2] = 0xff + buf[3] = 0x02 + h3 := &Header{} + err = h3.DecodeFromBytes(buf) + assert.NotEqual(nil, err) +} + +func Test_InterfaceUpdateBody(t *testing.T) { + assert := assert.New(t) + + //DecodeFromBytes + buf := make([]byte, INTERFACE_NAMSIZ+49) + pos := INTERFACE_NAMSIZ + binary.BigEndian.PutUint32(buf[pos:], 1) + pos += 4 + buf[pos] = byte(INTERFACE_ACTIVE) + pos += 1 + binary.BigEndian.PutUint64(buf[pos:], 1) + pos += 8 // flags + binary.BigEndian.PutUint32(buf[pos:], 1) + pos += 4 // metric + binary.BigEndian.PutUint32(buf[pos:], 1500) + pos += 4 // MTU + binary.BigEndian.PutUint32(buf[pos:], 1500) + pos += 4 // MTU6 + binary.BigEndian.PutUint32(buf[pos:], 200) + pos += 4 // bandwidth + binary.BigEndian.PutUint32(buf[pos:], 6) + pos += 4 // hwaddr_len + mac, _ := net.ParseMAC("01:23:45:67:89:ab") + copy(buf[pos:pos+6], []byte(mac)) + pos += 4 + b := &InterfaceUpdateBody{} + err := b.DecodeFromBytes(buf, 2) + assert.Equal(nil, err) + assert.Equal("01:23:45:67:89:ab", b.HardwareAddr.String()) + + buf = make([]byte, INTERFACE_NAMSIZ+28) + b = &InterfaceUpdateBody{} + err = b.DecodeFromBytes(buf, 2) + assert.NotEqual(nil, err) +} + +func Test_InterfaceAddressUpdateBody(t *testing.T) { + assert := assert.New(t) + + //DecodeFromBytes + buf := make([]byte, 15) + pos := 0 + binary.BigEndian.PutUint32(buf[pos:], 0) // index + pos += 4 + buf[pos] = 0x01 // flags + pos += 1 + buf[pos] = 0x2 // family + pos += 1 + ip := net.ParseIP("192.168.100.1").To4() // prefix + copy(buf[pos:pos+4], []byte(ip)) + pos += 4 + buf[pos] = byte(24) // prefix len + pos += 1 + dst := net.ParseIP("192.168.100.255").To4() // destination + copy(buf[pos:pos+4], []byte(dst)) + + b := &InterfaceAddressUpdateBody{} + err := b.DecodeFromBytes(buf, 2) + require.NoError(t, err) + + assert.Equal(uint32(0), b.Index) + assert.Equal(INTERFACE_ADDRESS_FLAG(1), b.Flags) + assert.Equal("192.168.100.1", b.Prefix.String()) + assert.Equal(uint8(24), b.Length) + assert.Equal("192.168.100.255", b.Destination.String()) + + // af invalid + buf[5] = 0x4 + pos += 1 + b = &InterfaceAddressUpdateBody{} + err = b.DecodeFromBytes(buf, 2) + assert.NotEqual(nil, err) +} + +func Test_RouterIDUpdateBody(t *testing.T) { + assert := assert.New(t) + + //DecodeFromBytes + buf := make([]byte, 6) + pos := 0 + buf[pos] = 0x2 + pos += 1 + ip := net.ParseIP("192.168.100.1").To4() + copy(buf[pos:pos+4], []byte(ip)) + pos += 4 + buf[pos] = byte(32) + + b := &RouterIDUpdateBody{} + err := b.DecodeFromBytes(buf, 2) + assert.Equal(nil, err) + assert.Equal("192.168.100.1", b.Prefix.String()) + assert.Equal(uint8(32), b.Length) + + // af invalid + buf[0] = 0x4 + pos += 1 + b = &RouterIDUpdateBody{} + err = b.DecodeFromBytes(buf, 2) + assert.NotEqual(nil, err) +} + +func Test_IPRouteBody_IPv4(t *testing.T) { + assert := assert.New(t) + + //DecodeFromBytes IPV4_ROUTE + buf := make([]byte, 26) + buf[0] = byte(ROUTE_CONNECT) + buf[1] = byte(FLAG_SELECTED) + buf[2] = byte(MESSAGE_NEXTHOP | MESSAGE_DISTANCE | MESSAGE_METRIC | MESSAGE_MTU) + buf[3] = 24 + ip := net.ParseIP("192.168.100.0").To4() + copy(buf[4:7], []byte(ip)) + + buf[7] = 1 + nexthop := net.ParseIP("0.0.0.0").To4() + copy(buf[8:12], []byte(nexthop)) + + buf[12] = 1 + binary.BigEndian.PutUint32(buf[13:], 1) + buf[17] = 0 // distance + binary.BigEndian.PutUint32(buf[18:], 1) + binary.BigEndian.PutUint32(buf[22:], 1) + r := &IPRouteBody{Api: IPV4_ROUTE_ADD} + err := r.DecodeFromBytes(buf, 2) + + assert.Equal(nil, err) + assert.Equal("192.168.100.0", r.Prefix.String()) + assert.Equal(uint8(0x18), r.PrefixLength) + assert.Equal(MESSAGE_NEXTHOP|MESSAGE_DISTANCE|MESSAGE_METRIC|MESSAGE_MTU, r.Message) + assert.Equal("0.0.0.0", r.Nexthops[0].String()) + assert.Equal(uint32(1), r.Ifindexs[0]) + assert.Equal(uint8(0), r.Distance) + assert.Equal(uint32(1), r.Metric) + assert.Equal(uint32(1), r.Mtu) + + //Serialize + buf, err = r.Serialize(2) + assert.Equal(nil, err) + assert.Equal([]byte{0x2, 0x10, 0x1d}, buf[0:3]) + assert.Equal([]byte{0x0, 0x1}, buf[3:5]) + assert.Equal(byte(24), buf[5]) + ip = net.ParseIP("192.168.100.0").To4() + assert.Equal([]byte(ip)[0:3], buf[6:9]) + assert.Equal(byte(NEXTHOP_IPV4), buf[10]) + assert.Equal(byte(NEXTHOP_IFINDEX), buf[15]) + assert.Equal(byte(0x0), buf[20]) + + bi := make([]byte, 4) + binary.BigEndian.PutUint32(bi, 1) + assert.Equal(bi, buf[21:25]) + assert.Equal(bi, buf[25:]) + + // length invalid + buf = make([]byte, 18) + buf[0] = byte(ROUTE_CONNECT) + buf[1] = byte(FLAG_SELECTED) + buf[2] = byte(MESSAGE_NEXTHOP | MESSAGE_DISTANCE | MESSAGE_METRIC) + buf[3] = 24 + ip = net.ParseIP("192.168.100.0").To4() + copy(buf[4:7], []byte(ip)) + buf[7] = 1 + nexthop = net.ParseIP("0.0.0.0").To4() + copy(buf[8:12], []byte(nexthop)) + buf[12] = 1 + binary.BigEndian.PutUint32(buf[13:], 1) + + r = &IPRouteBody{Api: IPV4_ROUTE_ADD} + err = r.DecodeFromBytes(buf, 2) + assert.Equal("message length invalid", err.Error()) + + // no nexthop + buf = make([]byte, 12) + buf[0] = byte(ROUTE_CONNECT) + buf[1] = byte(FLAG_SELECTED) + buf[2] = byte(MESSAGE_DISTANCE | MESSAGE_METRIC) + buf[3] = 24 + ip = net.ParseIP("192.168.100.0").To4() + copy(buf[4:7], []byte(ip)) + buf[7] = 1 + binary.BigEndian.PutUint32(buf[8:], 0) + r = &IPRouteBody{Api: IPV4_ROUTE_ADD} + err = r.DecodeFromBytes(buf, 2) + assert.Equal(nil, err) + +} + +func Test_IPRouteBody_IPv6(t *testing.T) { + assert := assert.New(t) + + //DecodeFromBytes IPV6_ROUTE + buf := make([]byte, 43) + buf[0] = byte(ROUTE_CONNECT) + buf[1] = byte(FLAG_SELECTED) + buf[2] = byte(MESSAGE_NEXTHOP | MESSAGE_DISTANCE | MESSAGE_METRIC | MESSAGE_MTU) + buf[3] = 64 + ip := net.ParseIP("2001:db8:0:f101::").To16() + copy(buf[4:12], []byte(ip)) + + buf[12] = 1 + nexthop := net.ParseIP("::").To16() + copy(buf[13:29], []byte(nexthop)) + // ifindex + buf[29] = 1 + binary.BigEndian.PutUint32(buf[30:], 1) + + buf[34] = 0 // distance + binary.BigEndian.PutUint32(buf[35:], 1) + binary.BigEndian.PutUint32(buf[39:], 1) + r := &IPRouteBody{Api: IPV6_ROUTE_ADD} + err := r.DecodeFromBytes(buf, 2) + + assert.Equal(nil, err) + assert.Equal("2001:db8:0:f101::", r.Prefix.String()) + assert.Equal(uint8(64), r.PrefixLength) + assert.Equal(MESSAGE_NEXTHOP|MESSAGE_DISTANCE|MESSAGE_METRIC|MESSAGE_MTU, r.Message) + assert.Equal("::", r.Nexthops[0].String()) + assert.Equal(uint32(1), r.Ifindexs[0]) + assert.Equal(uint8(0), r.Distance) + assert.Equal(uint32(1), r.Metric) + assert.Equal(uint32(1), r.Mtu) + + //Serialize + buf, err = r.Serialize(2) + assert.Equal(nil, err) + assert.Equal([]byte{0x2, 0x10, 0x1d}, buf[0:3]) + assert.Equal([]byte{0x0, 0x1}, buf[3:5]) + assert.Equal(byte(64), buf[5]) + ip = net.ParseIP("2001:db8:0:f101::").To16() + assert.Equal([]byte(ip)[0:8], buf[6:14]) + assert.Equal(byte(2), buf[14]) + assert.Equal(byte(NEXTHOP_IPV6), buf[15]) + ip = net.ParseIP("::").To16() + assert.Equal([]byte(ip), buf[16:32]) + assert.Equal(byte(NEXTHOP_IFINDEX), buf[32]) + bi := make([]byte, 4) + binary.BigEndian.PutUint32(bi, 1) + assert.Equal(bi, buf[33:37]) + + //distance + assert.Equal(byte(0), buf[37]) + bi = make([]byte, 4) + binary.BigEndian.PutUint32(bi, 1) + assert.Equal(bi, buf[38:42]) + assert.Equal(bi, buf[42:]) + + // length invalid + buf = make([]byte, 50) + buf[0] = byte(ROUTE_CONNECT) + buf[1] = byte(FLAG_SELECTED) + buf[2] = byte(MESSAGE_NEXTHOP | MESSAGE_DISTANCE | MESSAGE_METRIC) + buf[3] = 24 + ip = net.ParseIP("2001:db8:0:f101::").To4() + copy(buf[4:12], []byte(ip)) + buf[13] = 1 + nexthop = net.ParseIP("::").To16() + copy(buf[14:30], []byte(nexthop)) + buf[31] = 1 + binary.BigEndian.PutUint32(buf[32:], 1) + + r = &IPRouteBody{Api: IPV6_ROUTE_ADD} + err = r.DecodeFromBytes(buf, 2) + assert.Equal("message length invalid", err.Error()) + + // no nexthop + buf = make([]byte, 11) + buf[0] = byte(ROUTE_CONNECT) + buf[1] = byte(FLAG_SELECTED) + buf[2] = byte(MESSAGE_DISTANCE | MESSAGE_METRIC) + buf[3] = 16 + ip = net.ParseIP("2501::").To16() + copy(buf[4:6], []byte(ip)) + buf[6] = 1 + binary.BigEndian.PutUint32(buf[7:], 0) + r = &IPRouteBody{Api: IPV6_ROUTE_ADD} + err = r.DecodeFromBytes(buf, 2) + assert.Equal(nil, err) +} + +func Test_NexthopLookupBody(t *testing.T) { + assert := assert.New(t) + + //ipv4 + //DecodeFromBytes + pos := 0 + buf := make([]byte, 18) + ip := net.ParseIP("192.168.50.0").To4() + copy(buf[0:4], []byte(ip)) + pos += 4 + binary.BigEndian.PutUint32(buf[pos:], 10) + pos += 4 + buf[pos] = byte(1) + pos += 1 + buf[pos] = byte(4) + pos += 1 + ip = net.ParseIP("172.16.1.101").To4() + copy(buf[pos:pos+4], []byte(ip)) + pos += 4 + binary.BigEndian.PutUint32(buf[pos:], 3) + + b := &NexthopLookupBody{Api: IPV4_NEXTHOP_LOOKUP} + err := b.DecodeFromBytes(buf, 2) + assert.Equal(nil, err) + assert.Equal("192.168.50.0", b.Addr.String()) + assert.Equal(uint32(10), b.Metric) + assert.Equal(uint32(3), b.Nexthops[0].Ifindex) + assert.Equal(NEXTHOP_FLAG(4), b.Nexthops[0].Type) + assert.Equal("172.16.1.101", b.Nexthops[0].Addr.String()) + + //Serialize + buf, err = b.Serialize(2) + ip = net.ParseIP("192.168.50.0").To4() + assert.Equal(nil, err) + assert.Equal([]byte(ip)[0:4], buf[0:4]) + + // length invalid + buf = make([]byte, 3) + b = &NexthopLookupBody{Api: IPV4_NEXTHOP_LOOKUP} + err = b.DecodeFromBytes(buf, 2) + assert.NotEqual(nil, err) + + //ipv6 + //DecodeFromBytes + pos = 0 + buf = make([]byte, 46) + ip = net.ParseIP("2001:db8:0:f101::").To16() + copy(buf[0:16], []byte(ip)) + pos += 16 + binary.BigEndian.PutUint32(buf[pos:], 10) + pos += 4 + buf[pos] = byte(1) + pos += 1 + buf[pos] = byte(4) + pos += 1 + ip = net.ParseIP("2001:db8:0:1111::1").To16() + copy(buf[pos:pos+16], []byte(ip)) + pos += 16 + binary.BigEndian.PutUint32(buf[pos:], 3) + + b = &NexthopLookupBody{Api: IPV6_NEXTHOP_LOOKUP} + err = b.DecodeFromBytes(buf, 2) + assert.Equal(nil, err) + assert.Equal("2001:db8:0:f101::", b.Addr.String()) + assert.Equal(uint32(10), b.Metric) + assert.Equal(uint32(3), b.Nexthops[0].Ifindex) + assert.Equal(NEXTHOP_FLAG(4), b.Nexthops[0].Type) + assert.Equal("2001:db8:0:1111::1", b.Nexthops[0].Addr.String()) + + //Serialize + buf, err = b.Serialize(2) + ip = net.ParseIP("2001:db8:0:f101::").To16() + assert.Equal(nil, err) + assert.Equal([]byte(ip)[0:16], buf[0:16]) + + // length invalid + buf = make([]byte, 15) + b = &NexthopLookupBody{Api: IPV6_NEXTHOP_LOOKUP} + err = b.DecodeFromBytes(buf, 2) + assert.NotEqual(nil, err) +} + +func Test_ImportLookupBody(t *testing.T) { + assert := assert.New(t) + + //DecodeFromBytes + pos := 0 + buf := make([]byte, 18) + ip := net.ParseIP("192.168.50.0").To4() + copy(buf[0:4], []byte(ip)) + pos += 4 + binary.BigEndian.PutUint32(buf[pos:], 10) + pos += 4 + buf[pos] = byte(1) + pos += 1 + buf[pos] = byte(4) + pos += 1 + ip = net.ParseIP("172.16.1.101").To4() + copy(buf[pos:pos+4], []byte(ip)) + pos += 4 + binary.BigEndian.PutUint32(buf[pos:], 3) + + b := &ImportLookupBody{Api: IPV4_IMPORT_LOOKUP} + err := b.DecodeFromBytes(buf, 2) + assert.Equal(nil, err) + assert.Equal("192.168.50.0", b.Addr.String()) + assert.Equal(uint32(10), b.Metric) + assert.Equal(uint32(3), b.Nexthops[0].Ifindex) + assert.Equal(NEXTHOP_FLAG(4), b.Nexthops[0].Type) + assert.Equal("172.16.1.101", b.Nexthops[0].Addr.String()) + + //Serialize + b.PrefixLength = uint8(24) + buf, err = b.Serialize(2) + ip = net.ParseIP("192.168.50.0").To4() + assert.Equal(nil, err) + assert.Equal(uint8(24), buf[0]) + assert.Equal([]byte(ip)[0:4], buf[1:5]) + + // length invalid + buf = make([]byte, 3) + b = &ImportLookupBody{Api: IPV4_IMPORT_LOOKUP} + err = b.DecodeFromBytes(buf, 2) + assert.NotEqual(nil, err) +} + +func Test_NexthopRegisterBody(t *testing.T) { + assert := assert.New(t) + + // Input binary + bufIn := []byte{ + 0x01, 0x00, 0x02, 0x20, // connected(1 byte)=1, afi(2 bytes)=AF_INET, prefix_len(1 byte)=32 + 0xc0, 0xa8, 0x01, 0x01, // prefix(4 bytes)="192.168.1.1" + 0x00, 0x00, 0x0a, 0x80, // connected(1 byte)=0, afi(2 bytes)=AF_INET6, prefix_len(1 byte)=128 + 0x20, 0x01, 0x0d, 0xb8, // prefix(16 bytes)="2001:db8:1:1::1" + 0x00, 0x01, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, + } + binary.BigEndian.PutUint16(bufIn[1:], syscall.AF_INET) + binary.BigEndian.PutUint16(bufIn[9:], syscall.AF_INET6) + + // Test DecodeFromBytes() + b := &NexthopRegisterBody{Api: NEXTHOP_REGISTER} + err := b.DecodeFromBytes(bufIn, 3) + assert.Nil(err) + + // Test decoded values + assert.Equal(uint8(1), b.Nexthops[0].Connected) + assert.Equal(uint16(syscall.AF_INET), b.Nexthops[0].Family) + assert.Equal(net.ParseIP("192.168.1.1").To4(), b.Nexthops[0].Prefix) + assert.Equal(uint8(0), b.Nexthops[1].Connected) + assert.Equal(uint16(syscall.AF_INET6), b.Nexthops[1].Family) + assert.Equal(net.ParseIP("2001:db8:1:1::1").To16(), b.Nexthops[1].Prefix) + + // Test Serialize() + bufOut, err := b.Serialize(3) + assert.Nil(err) + + // Test serialised value + assert.Equal(bufIn, bufOut) +} + +func Test_NexthopUpdateBody(t *testing.T) { + assert := assert.New(t) + + // Input binary + bufIn := []byte{ + 0x00, 0x02, 0x20, // afi(2 bytes)=AF_INET, prefix_len(1 byte)=32 + 0xc0, 0xa8, 0x01, 0x01, // prefix(4 bytes)="192.168.1.1" + 0x00, 0x00, 0x00, 0x01, // metric(4 bytes)=1 + 0x01, // nexthops(1 byte)=1 + 0x04, // nexthop_type(1 byte)=NEXTHOP_IPV4_IFINDEX + 0xc0, 0xa8, 0x01, 0x01, // nexthop_ip(4 bytes)="192.168.0.1" + 0x00, 0x00, 0x00, 0x02, // nexthop_ifindex(4 byte)=2 + } + + // Test DecodeFromBytes() + b := &NexthopUpdateBody{Api: NEXTHOP_UPDATE} + err := b.DecodeFromBytes(bufIn, 2) + assert.Nil(err) + + // Test decoded values + assert.Equal(uint16(syscall.AF_INET), b.Family) + assert.Equal(net.ParseIP("192.168.1.1").To4(), b.Prefix) + assert.Equal(uint32(1), b.Metric) + nexthop := &Nexthop{ + Type: NEXTHOP_FLAG(NEXTHOP_IPV4_IFINDEX), + Addr: net.ParseIP("192.168.1.1").To4(), + Ifindex: uint32(2), + } + assert.Equal(1, len(b.Nexthops)) + assert.Equal(nexthop, b.Nexthops[0]) +} diff --git a/internal/pkg/zebra/zapi_windows.go b/internal/pkg/zebra/zapi_windows.go new file mode 100644 index 00000000..d55525db --- /dev/null +++ b/internal/pkg/zebra/zapi_windows.go @@ -0,0 +1,38 @@ +// Copyright (C) 2014, 2015 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 zebra + +import ( + "strings" + "syscall" +) + +func intfflag2string(flag uint64) string { + ss := make([]string, 0, 10) + if flag&syscall.IFF_UP > 0 { + ss = append(ss, "UP") + } + if flag&syscall.IFF_BROADCAST > 0 { + ss = append(ss, "BROADCAST") + } + if flag&syscall.IFF_LOOPBACK > 0 { + ss = append(ss, "LOOPBACK") + } + if flag&syscall.IFF_MULTICAST > 0 { + ss = append(ss, "MULTICAST") + } + return strings.Join(ss, " | ") +} |