go.ligato.io/vpp-agent/v3@v3.5.0/plugins/vpp/srplugin/descriptor/steering.go (about) 1 // Copyright (c) 2019 Bell Canada, Pantheon Technologies and/or its affiliates. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at: 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package descriptor 16 17 import ( 18 "fmt" 19 "net" 20 "reflect" 21 "strings" 22 23 "github.com/pkg/errors" 24 "go.ligato.io/cn-infra/v2/logging" 25 26 scheduler "go.ligato.io/vpp-agent/v3/plugins/kvscheduler/api" 27 "go.ligato.io/vpp-agent/v3/plugins/vpp/srplugin/descriptor/adapter" 28 "go.ligato.io/vpp-agent/v3/plugins/vpp/srplugin/vppcalls" 29 vpp_interfaces "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" 30 vpp_l3 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l3" 31 srv6 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/srv6" 32 ) 33 34 const ( 35 // SteeringDescriptorName is the name of the descriptor for VPP SRv6 steering 36 SteeringDescriptorName = "vpp-sr-steering" 37 38 // dependency labels 39 policyExistsDep = "sr-policy-for-steering-exists" 40 steeringVRFDep = "sr-steering-vrf-table-exists" 41 steeringInterfaceDep = "sr-steering-l2-interface-exists" 42 ) 43 44 // SteeringDescriptor teaches KVScheduler how to configure VPP SRv6 steering. 45 type SteeringDescriptor struct { 46 // dependencies 47 log logging.Logger 48 srHandler vppcalls.SRv6VppAPI 49 } 50 51 // NewSteeringDescriptor creates a new instance of the Srv6 steering descriptor. 52 func NewSteeringDescriptor(srHandler vppcalls.SRv6VppAPI, log logging.PluginLogger) *scheduler.KVDescriptor { 53 ctx := &SteeringDescriptor{ 54 log: log.NewLogger("steering-descriptor"), 55 srHandler: srHandler, 56 } 57 58 typedDescr := &adapter.SteeringDescriptor{ 59 Name: SteeringDescriptorName, 60 NBKeyPrefix: srv6.ModelSteering.KeyPrefix(), 61 ValueTypeName: srv6.ModelSteering.ProtoName(), 62 KeySelector: srv6.ModelSteering.IsKeyValid, 63 KeyLabel: srv6.ModelSteering.StripKeyPrefix, 64 ValueComparator: ctx.EquivalentSteering, 65 Validate: ctx.Validate, 66 Create: ctx.Create, 67 Delete: ctx.Delete, 68 Dependencies: ctx.Dependencies, 69 } 70 return adapter.NewSteeringDescriptor(typedDescr) 71 } 72 73 // EquivalentSteering determines whether 2 steerings are logically equal. This comparison takes into consideration also 74 // semantics that couldn't be modeled into proto models (i.e. SID is IPv6 address and not only string) 75 func (d *SteeringDescriptor) EquivalentSteering(key string, oldSteering, newSteering *srv6.Steering) bool { 76 return strings.TrimSpace(oldSteering.Name) == strings.TrimSpace(newSteering.Name) && 77 d.equivalentPolicy(oldSteering.PolicyRef, newSteering.PolicyRef) && 78 d.equivalentTraffic(oldSteering.Traffic, newSteering.Traffic) 79 } 80 81 func (d *SteeringDescriptor) equivalentPolicy(policy1, policy2 interface{}) bool { 82 if policy1 == nil || policy2 == nil { 83 return policy1 == policy2 84 } 85 if reflect.TypeOf(policy1) != reflect.TypeOf(policy2) { 86 return false 87 } 88 switch policy1typed := policy1.(type) { 89 case *srv6.Steering_PolicyBsid: 90 return equivalentSIDs(policy1typed.PolicyBsid, policy2.(*srv6.Steering_PolicyBsid).PolicyBsid) 91 case *srv6.Steering_PolicyIndex: 92 return policy1typed.PolicyIndex == policy2.(*srv6.Steering_PolicyIndex).PolicyIndex 93 default: 94 d.log.Warn("EquivalentSteering found unknown policy reference type (%T). Using general reflect.DeepEqual for it.", policy1) 95 return reflect.DeepEqual(policy1, policy2) // unknown policies 96 } 97 } 98 99 func (d *SteeringDescriptor) equivalentTraffic(traffic1, traffic2 interface{}) bool { 100 if traffic1 == nil || traffic2 == nil { 101 return traffic1 == traffic2 102 } 103 if reflect.TypeOf(traffic1) != reflect.TypeOf(traffic2) { 104 return false 105 } 106 switch traffic1typed := traffic1.(type) { 107 case *srv6.Steering_L3Traffic_: 108 if traffic1typed.L3Traffic.InstallationVrfId != traffic2.(*srv6.Steering_L3Traffic_).L3Traffic.InstallationVrfId { 109 return false 110 } 111 ip1, ipNet1, err1 := net.ParseCIDR(traffic1typed.L3Traffic.PrefixAddress) 112 ip2, ipNet2, err2 := net.ParseCIDR(traffic2.(*srv6.Steering_L3Traffic_).L3Traffic.PrefixAddress) 113 if err1 != nil || err2 != nil { 114 return equivalentTrimmedLowered(traffic1typed.L3Traffic.PrefixAddress, traffic2.(*srv6.Steering_L3Traffic_).L3Traffic.PrefixAddress) // invalid one of prefixes, but still can equal on string basis 115 } 116 return ip1.Equal(ip2) && ipNet1.IP.Equal(ipNet2.IP) && ipNet1.Mask.String() == ipNet2.Mask.String() 117 case *srv6.Steering_L2Traffic_: 118 return equivalentTrimmedLowered(traffic1typed.L2Traffic.InterfaceName, traffic2.(*srv6.Steering_L2Traffic_).L2Traffic.InterfaceName) 119 default: 120 d.log.Warn("EquivalentSteering found unknown traffic type (%T). Using general reflect.DeepEqual for it.", traffic1) 121 return reflect.DeepEqual(traffic1, traffic2) // unknown policies 122 } 123 } 124 125 // Validate validates VPP SRv6 steerings. 126 func (d *SteeringDescriptor) Validate(key string, steering *srv6.Steering) error { 127 // checking policy reference ("oneof" field) 128 switch ref := steering.PolicyRef.(type) { 129 case *srv6.Steering_PolicyBsid: 130 _, err := ParseIPv6(ref.PolicyBsid) 131 if err != nil { 132 return scheduler.NewInvalidValueError(errors.Errorf("failed to parse steering's policy bsid %s, should be a valid ipv6 address: %v", ref.PolicyBsid, err), "policybsid") 133 } 134 case nil: 135 return scheduler.NewInvalidValueError(errors.New("policy reference must be filled, either by policy bsid or policy index"), "PolicyRef(policybsid/policyindex)") 136 default: 137 return scheduler.NewInvalidValueError(errors.Errorf("policy reference has unexpected model link type %T", ref), "PolicyRef") 138 } 139 140 // checking traffic ("oneof" field) 141 switch t := steering.Traffic.(type) { 142 case *srv6.Steering_L2Traffic_: 143 if strings.TrimSpace(t.L2Traffic.InterfaceName) == "" { 144 return scheduler.NewInvalidValueError(errors.New("(non-empty) interface name must be given (L2 traffic definition)"), "InterfaceName") 145 } 146 case *srv6.Steering_L3Traffic_: 147 _, _, err := net.ParseCIDR(t.L3Traffic.PrefixAddress) 148 if err != nil { 149 return scheduler.NewInvalidValueError(errors.Errorf("steering l3 traffic prefix address %v can't be parsed as CIDR formatted prefix: %v", t.L3Traffic.PrefixAddress, err), "prefixaddress") 150 } 151 case nil: 152 return scheduler.NewInvalidValueError(errors.New("steering must have filled traffic otherwise we don't know which traffic to steer into policy"), "traffic") 153 default: 154 return scheduler.NewInvalidValueError(errors.Errorf("steering's traffic reference has unexpected model link type %T", t), "traffic") 155 } 156 return nil 157 } 158 159 // Create adds new steering into VPP using VPP's binary api 160 func (d *SteeringDescriptor) Create(key string, steering *srv6.Steering) (metadata interface{}, err error) { 161 if err := d.srHandler.AddSteering(steering); err != nil { 162 name, valid := srv6.ModelSteering.ParseKey(key) 163 if !valid { 164 name = fmt.Sprintf("(key %v)", key) 165 } 166 return nil, errors.Errorf("failed to add steering %s: %v", name, err) 167 } 168 return nil, nil 169 } 170 171 // Delete removes steering from VPP using VPP's binary api 172 func (d *SteeringDescriptor) Delete(key string, steering *srv6.Steering, metadata interface{}) error { 173 if err := d.srHandler.RemoveSteering(steering); err != nil { 174 name, valid := srv6.ModelSteering.ParseKey(key) 175 if !valid { 176 name = fmt.Sprintf("(key %v)", key) 177 } 178 return errors.Errorf("failed to remove steering %s: %v", name, err) 179 } 180 return nil 181 } 182 183 // Dependencies defines dependencies of Steering descriptor 184 func (d *SteeringDescriptor) Dependencies(key string, steering *srv6.Steering) (dependencies []scheduler.Dependency) { 185 //TODO support also policy identification by index for Dependency waiting (impl using derived value) and do proper robot test for it (vppcalls and NB support it already) 186 if _, ok := steering.GetPolicyRef().(*srv6.Steering_PolicyBsid); !ok { 187 d.log.Errorf("Non-BSID policy reference in steering is not fully supported (dependency waiting is not "+ 188 "implemented). Using empty dependencies for steering %+v", steering) 189 return dependencies 190 } 191 192 dependencies = append(dependencies, scheduler.Dependency{ 193 Label: policyExistsDep, 194 Key: srv6.PolicyKey(steering.GetPolicyBsid()), 195 }) 196 197 // VRF-table dependency 198 if t, isL3Traffic := steering.Traffic.(*srv6.Steering_L3Traffic_); isL3Traffic { 199 l3Traffic := t.L3Traffic 200 tableID := l3Traffic.InstallationVrfId 201 if tableID > 0 { 202 ip, _, _ := net.ParseCIDR(l3Traffic.PrefixAddress) // PrefixAddress is already validated 203 tableProto := vpp_l3.VrfTable_IPV6 204 if ip.To4() != nil { // IPv4 address 205 tableProto = vpp_l3.VrfTable_IPV4 206 } 207 dependencies = append(dependencies, scheduler.Dependency{ 208 Label: steeringVRFDep, 209 Key: vpp_l3.VrfTableKey(tableID, tableProto), 210 }) 211 } 212 } 213 // Interface dependency 214 if l2Traffic := steering.GetL2Traffic(); l2Traffic != nil { 215 interfaceName := strings.TrimSpace(l2Traffic.InterfaceName) 216 if len(interfaceName) > 0 { 217 dependencies = append(dependencies, scheduler.Dependency{ 218 Label: steeringInterfaceDep, 219 Key: vpp_interfaces.InterfaceKey(interfaceName), 220 }) 221 } 222 } 223 return dependencies 224 }