diff options
author | urban <bodozoglou@gmail.com> | 2018-02-20 21:48:23 +0000 |
---|---|---|
committer | FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp> | 2018-06-05 19:57:21 +0900 |
commit | 5d15b3f290a4305e0c8b7d651834c997d2b09910 (patch) | |
tree | bce6c7e82f8a2c9f1d400ffeabbb3f791a989e68 /table | |
parent | 5c506b6db14dac32e60bb2f880faf6317ede46e6 (diff) |
table: support nexthop match policy
Diffstat (limited to 'table')
-rw-r--r-- | table/policy.go | 182 | ||||
-rw-r--r-- | table/policy_test.go | 64 |
2 files changed, 246 insertions, 0 deletions
diff --git a/table/policy.go b/table/policy.go index 2a99f93d..770eb81b 100644 --- a/table/policy.go +++ b/table/policy.go @@ -37,6 +37,7 @@ import ( type PolicyOptions struct { Info *PeerInfo ValidationResult *Validation + OldNextHop net.IP } type DefinedType int @@ -49,6 +50,7 @@ const ( DEFINED_TYPE_COMMUNITY DEFINED_TYPE_EXT_COMMUNITY DEFINED_TYPE_LARGE_COMMUNITY + DEFINED_TYPE_NEXT_HOP ) type RouteType int @@ -154,6 +156,7 @@ const ( CONDITION_RPKI CONDITION_ROUTE_TYPE CONDITION_LARGE_COMMUNITY + CONDITION_NEXT_HOP ) type ActionType int @@ -535,6 +538,109 @@ func NewPrefixSet(c config.PrefixSet) (*PrefixSet, error) { }, 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 @@ -1240,6 +1346,74 @@ type Condition interface { 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 @@ -2469,6 +2643,9 @@ func (s *Statement) ToConfig() *config.Statement { 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 @@ -2673,6 +2850,9 @@ func NewStatement(c config.Statement) (*Statement, error) { func() (Condition, error) { return NewLargeCommunityCondition(c.Conditions.BgpConditions.MatchLargeCommunitySet) }, + func() (Condition, error) { + return NewNextHopCondition(c.Conditions.BgpConditions.NextHopInList) + }, } cs = make([]Condition, 0, len(cfs)) for _, f := range cfs { @@ -3046,6 +3226,7 @@ func (r *RoutingPolicy) validateCondition(v Condition) (err error) { c := v.(*LargeCommunityCondition) c.set = i.(*LargeCommunitySet) } + case CONDITION_NEXT_HOP: case CONDITION_AS_PATH_LENGTH: case CONDITION_RPKI: } @@ -3155,6 +3336,7 @@ func (r *RoutingPolicy) reload(c config.RoutingPolicy) error { } dmap[DEFINED_TYPE_LARGE_COMMUNITY][y.Name()] = y } + pmap := make(map[string]*Policy) smap := make(map[string]*Statement) for _, x := range c.PolicyDefinitions { diff --git a/table/policy_test.go b/table/policy_test.go index cf02b095..9e990dc9 100644 --- a/table/policy_test.go +++ b/table/policy_test.go @@ -501,6 +501,70 @@ func TestAsPathLengthConditionEvaluate(t *testing.T) { 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 |