go.ligato.io/vpp-agent/v3@v3.5.0/plugins/vpp/srplugin/descriptor/localsid.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  	"net"
    19  	"reflect"
    20  	"strings"
    21  
    22  	"github.com/pkg/errors"
    23  	"go.ligato.io/cn-infra/v2/logging"
    24  
    25  	scheduler "go.ligato.io/vpp-agent/v3/plugins/kvscheduler/api"
    26  	"go.ligato.io/vpp-agent/v3/plugins/vpp/srplugin/descriptor/adapter"
    27  	"go.ligato.io/vpp-agent/v3/plugins/vpp/srplugin/vppcalls"
    28  	interfaces "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces"
    29  	vpp_l3 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l3"
    30  	srv6 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/srv6"
    31  )
    32  
    33  const (
    34  	// LocalSIDDescriptorName is the name of the descriptor for VPP LocalSIDs
    35  	LocalSIDDescriptorName = "vpp-sr-localsid"
    36  
    37  	// dependency labels
    38  	localsidOutgoingInterfaceDep = "sr-localsid-outgoing-interface-exists"
    39  	localsidIncomingInterfaceDep = "sr-localsid-incoming-interface-exists"
    40  	localsidInstallationVRFDep   = "sr-localsid-installation-vrf-table-exists"
    41  	localsidLookupVRFDep         = "sr-localsid-routing-lookup-vrf-table-exists"
    42  )
    43  
    44  // LocalSIDDescriptor teaches KVScheduler how to configure VPP LocalSIDs.
    45  type LocalSIDDescriptor struct {
    46  	// dependencies
    47  	log       logging.Logger
    48  	srHandler vppcalls.SRv6VppAPI
    49  }
    50  
    51  // NewLocalSIDDescriptor creates a new instance of the LocalSID descriptor.
    52  func NewLocalSIDDescriptor(srHandler vppcalls.SRv6VppAPI, log logging.PluginLogger) *scheduler.KVDescriptor {
    53  	ctx := &LocalSIDDescriptor{
    54  		log:       log.NewLogger("localsid-descriptor"),
    55  		srHandler: srHandler,
    56  	}
    57  
    58  	typedDescr := &adapter.LocalSIDDescriptor{
    59  		Name:            LocalSIDDescriptorName,
    60  		NBKeyPrefix:     srv6.ModelLocalSID.KeyPrefix(),
    61  		ValueTypeName:   srv6.ModelLocalSID.ProtoName(),
    62  		KeySelector:     srv6.ModelLocalSID.IsKeyValid,
    63  		KeyLabel:        srv6.ModelLocalSID.StripKeyPrefix,
    64  		ValueComparator: ctx.EquivalentLocalSIDs,
    65  		Validate:        ctx.Validate,
    66  		Create:          ctx.Create,
    67  		Delete:          ctx.Delete,
    68  		Dependencies:    ctx.Dependencies,
    69  	}
    70  	return adapter.NewLocalSIDDescriptor(typedDescr)
    71  }
    72  
    73  // EquivalentLocalSIDs determines whether 2 localSIDs 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 *LocalSIDDescriptor) EquivalentLocalSIDs(key string, oldLocalSID, newLocalSID *srv6.LocalSID) bool {
    76  	return oldLocalSID.InstallationVrfId == newLocalSID.InstallationVrfId &&
    77  		equivalentSIDs(oldLocalSID.Sid, newLocalSID.Sid) &&
    78  		d.equivalentEndFunctions(oldLocalSID.EndFunction, newLocalSID.EndFunction)
    79  }
    80  
    81  func (d *LocalSIDDescriptor) equivalentEndFunctions(ef1, ef2 interface{}) bool {
    82  	if ef1 == nil || ef2 == nil {
    83  		return ef1 == ef2
    84  	}
    85  	if reflect.TypeOf(ef1) != reflect.TypeOf(ef2) {
    86  		return false
    87  	}
    88  	switch ef1typed := ef1.(type) {
    89  	case *srv6.LocalSID_BaseEndFunction:
    90  		return true
    91  	case *srv6.LocalSID_EndFunctionX:
    92  		return ef1typed.EndFunctionX.Psp == ef2.(*srv6.LocalSID_EndFunctionX).EndFunctionX.Psp &&
    93  			equivalentIPv6(ef1typed.EndFunctionX.NextHop, ef2.(*srv6.LocalSID_EndFunctionX).EndFunctionX.NextHop) &&
    94  			equivalentTrimmedLowered(ef1typed.EndFunctionX.OutgoingInterface, ef2.(*srv6.LocalSID_EndFunctionX).EndFunctionX.OutgoingInterface)
    95  	case *srv6.LocalSID_EndFunctionT:
    96  		return ef1typed.EndFunctionT.Psp == ef2.(*srv6.LocalSID_EndFunctionT).EndFunctionT.Psp &&
    97  			ef1typed.EndFunctionT.VrfId == ef2.(*srv6.LocalSID_EndFunctionT).EndFunctionT.VrfId
    98  	case *srv6.LocalSID_EndFunctionDx2:
    99  		return ef1typed.EndFunctionDx2.VlanTag == ef2.(*srv6.LocalSID_EndFunctionDx2).EndFunctionDx2.VlanTag &&
   100  			equivalentTrimmedLowered(ef1typed.EndFunctionDx2.OutgoingInterface, ef2.(*srv6.LocalSID_EndFunctionDx2).EndFunctionDx2.OutgoingInterface)
   101  	case *srv6.LocalSID_EndFunctionDx4:
   102  		return equivalentIPv4(ef1typed.EndFunctionDx4.NextHop, ef2.(*srv6.LocalSID_EndFunctionDx4).EndFunctionDx4.NextHop) &&
   103  			equivalentTrimmedLowered(ef1typed.EndFunctionDx4.OutgoingInterface, ef2.(*srv6.LocalSID_EndFunctionDx4).EndFunctionDx4.OutgoingInterface)
   104  	case *srv6.LocalSID_EndFunctionDx6:
   105  		return equivalentIPv4(ef1typed.EndFunctionDx6.NextHop, ef2.(*srv6.LocalSID_EndFunctionDx6).EndFunctionDx6.NextHop) &&
   106  			equivalentTrimmedLowered(ef1typed.EndFunctionDx6.OutgoingInterface, ef2.(*srv6.LocalSID_EndFunctionDx6).EndFunctionDx6.OutgoingInterface)
   107  	case *srv6.LocalSID_EndFunctionDt4:
   108  		return ef1typed.EndFunctionDt4.VrfId == ef2.(*srv6.LocalSID_EndFunctionDt4).EndFunctionDt4.VrfId
   109  	case *srv6.LocalSID_EndFunctionDt6:
   110  		return ef1typed.EndFunctionDt6.VrfId == ef2.(*srv6.LocalSID_EndFunctionDt6).EndFunctionDt6.VrfId
   111  	case *srv6.LocalSID_EndFunctionAd:
   112  		return equivalentTrimmedLowered(ef1typed.EndFunctionAd.OutgoingInterface, ef2.(*srv6.LocalSID_EndFunctionAd).EndFunctionAd.OutgoingInterface) &&
   113  			equivalentTrimmedLowered(ef1typed.EndFunctionAd.IncomingInterface, ef2.(*srv6.LocalSID_EndFunctionAd).EndFunctionAd.IncomingInterface) &&
   114  			(equivalentIPv4(ef1typed.EndFunctionAd.L3ServiceAddress, ef2.(*srv6.LocalSID_EndFunctionAd).EndFunctionAd.L3ServiceAddress) || // l3 ipv4 service
   115  				equivalentIPv6(ef1typed.EndFunctionAd.L3ServiceAddress, ef2.(*srv6.LocalSID_EndFunctionAd).EndFunctionAd.L3ServiceAddress) || // l3 ipv6 service
   116  				(strings.TrimSpace(ef1typed.EndFunctionAd.L3ServiceAddress) == "" && strings.TrimSpace(ef2.(*srv6.LocalSID_EndFunctionAd).EndFunctionAd.L3ServiceAddress) == "")) // l2 service
   117  	default:
   118  		d.log.Warn("EquivalentSteering found unknown end function type (%T). Using general reflect.DeepEqual for it.", ef1)
   119  		return reflect.DeepEqual(ef1, ef2) // unknown end function type
   120  	}
   121  }
   122  
   123  // Validate validates VPP LocalSIDs.
   124  func (d *LocalSIDDescriptor) Validate(key string, localSID *srv6.LocalSID) error {
   125  	// checking basic attributes
   126  	_, err := ParseIPv6(localSID.GetSid())
   127  	if err != nil {
   128  		return scheduler.NewInvalidValueError(errors.Errorf("failed to parse local sid %s, should be a valid ipv6 address: %v", localSID.GetSid(), err), "sid")
   129  	}
   130  
   131  	// checking end functions
   132  	switch ef := localSID.EndFunction.(type) {
   133  	case *srv6.LocalSID_BaseEndFunction:
   134  	case *srv6.LocalSID_EndFunctionX:
   135  		_, err := ParseIPv6(ef.EndFunctionX.NextHop)
   136  		if err != nil {
   137  			return scheduler.NewInvalidValueError(errors.Errorf("failed to parse next hop %s, should be a valid ipv6 address: %v", ef.EndFunctionX.NextHop, err), "EndFunctionX.NextHop")
   138  		}
   139  	case *srv6.LocalSID_EndFunctionT:
   140  	case *srv6.LocalSID_EndFunctionDx2:
   141  	case *srv6.LocalSID_EndFunctionDx4:
   142  		_, err := ParseIPv4(ef.EndFunctionDx4.NextHop)
   143  		if err != nil {
   144  			return scheduler.NewInvalidValueError(errors.Errorf("failed to parse next hop %s, should be a valid ipv4 address: %v", ef.EndFunctionDx4.NextHop, err), "EndFunctionDX4.NextHop")
   145  		}
   146  	case *srv6.LocalSID_EndFunctionDx6:
   147  		_, err := ParseIPv6(ef.EndFunctionDx6.NextHop)
   148  		if err != nil {
   149  			return scheduler.NewInvalidValueError(errors.Errorf("failed to parse next hop %s, should be a valid ipv6 address: %v", ef.EndFunctionDx6.NextHop, err), "EndFunctionDX6.NextHop")
   150  		}
   151  	case *srv6.LocalSID_EndFunctionDt4:
   152  	case *srv6.LocalSID_EndFunctionDt6:
   153  	case *srv6.LocalSID_EndFunctionAd:
   154  		if strings.TrimSpace(ef.EndFunctionAd.L3ServiceAddress) == "" {
   155  			return nil // l2 service
   156  		}
   157  		// l3 service
   158  		ip := net.ParseIP(ef.EndFunctionAd.L3ServiceAddress)
   159  		if ip == nil {
   160  			return scheduler.NewInvalidValueError(errors.Errorf("failed to parse service address %s, should be a valid ip address(ipv4 or ipv6) or empty(case of l2 service): %v", ef.EndFunctionAd.L3ServiceAddress, err), "EndFunctionAD.L3ServiceAddress")
   161  		}
   162  	case nil:
   163  		return scheduler.NewInvalidValueError(errors.New("end function must be provided"), "endfunction")
   164  	default:
   165  		return scheduler.NewInvalidValueError(errors.Errorf("end function has unexpected model link type %T", ef), "endfunction")
   166  	}
   167  
   168  	return nil
   169  }
   170  
   171  // Create creates new Local SID into VPP using VPP's binary api
   172  func (d *LocalSIDDescriptor) Create(key string, value *srv6.LocalSID) (metadata interface{}, err error) {
   173  	if err := d.srHandler.AddLocalSid(value); err != nil {
   174  		return nil, errors.Errorf("failed to add local sid %s: %v", value.GetSid(), err)
   175  	}
   176  	return nil, nil
   177  }
   178  
   179  // Delete removes Local SID from VPP using VPP's binary api
   180  func (d *LocalSIDDescriptor) Delete(key string, value *srv6.LocalSID, metadata interface{}) error {
   181  	if err := d.srHandler.DeleteLocalSid(value); err != nil {
   182  		return errors.Errorf("failed to delete local sid %s: %v", value.GetSid(), err)
   183  	}
   184  	return nil
   185  }
   186  
   187  // Dependencies for LocalSIDs are represented by interface (interface in up state)
   188  func (d *LocalSIDDescriptor) Dependencies(key string, localSID *srv6.LocalSID) (dependencies []scheduler.Dependency) {
   189  	dependencies = append(dependencies, scheduler.Dependency{
   190  		Label: localsidInstallationVRFDep,
   191  		Key:   vpp_l3.VrfTableKey(localSID.InstallationVrfId, vpp_l3.VrfTable_IPV6),
   192  	})
   193  
   194  	switch ef := localSID.EndFunction.(type) {
   195  	case *srv6.LocalSID_EndFunctionT:
   196  		if ef.EndFunctionT.VrfId != 0 { // VRF 0 is in VPP by default
   197  			dependencies = append(dependencies, scheduler.Dependency{
   198  				Label: localsidLookupVRFDep,
   199  				Key:   vpp_l3.VrfTableKey(ef.EndFunctionT.VrfId, vpp_l3.VrfTable_IPV6), // T refers to IPv6 VRF table
   200  			})
   201  		}
   202  	case *srv6.LocalSID_EndFunctionX:
   203  		dependencies = append(dependencies, scheduler.Dependency{
   204  			Label: localsidOutgoingInterfaceDep,
   205  			Key:   interfaces.InterfaceKey(ef.EndFunctionX.OutgoingInterface),
   206  		})
   207  	case *srv6.LocalSID_EndFunctionDx2:
   208  		dependencies = append(dependencies, scheduler.Dependency{
   209  			Label: localsidOutgoingInterfaceDep,
   210  			Key:   interfaces.InterfaceKey(ef.EndFunctionDx2.OutgoingInterface),
   211  		})
   212  	case *srv6.LocalSID_EndFunctionDx4:
   213  		dependencies = append(dependencies, scheduler.Dependency{
   214  			Label: localsidOutgoingInterfaceDep,
   215  			Key:   interfaces.InterfaceKey(ef.EndFunctionDx4.OutgoingInterface),
   216  		})
   217  	case *srv6.LocalSID_EndFunctionDx6:
   218  		dependencies = append(dependencies, scheduler.Dependency{
   219  			Label: localsidOutgoingInterfaceDep,
   220  			Key:   interfaces.InterfaceKey(ef.EndFunctionDx6.OutgoingInterface),
   221  		})
   222  	case *srv6.LocalSID_EndFunctionDt4:
   223  		if ef.EndFunctionDt4.VrfId != 0 { // VRF 0 is in VPP by default
   224  			dependencies = append(dependencies, scheduler.Dependency{
   225  				Label: localsidLookupVRFDep,
   226  				Key:   vpp_l3.VrfTableKey(ef.EndFunctionDt4.VrfId, vpp_l3.VrfTable_IPV4), // we want ipv4 VRF because DT4
   227  			})
   228  		}
   229  	case *srv6.LocalSID_EndFunctionDt6:
   230  		if ef.EndFunctionDt6.VrfId != 0 { // VRF 0 is in VPP by default
   231  			dependencies = append(dependencies, scheduler.Dependency{
   232  				Label: localsidLookupVRFDep,
   233  				Key:   vpp_l3.VrfTableKey(ef.EndFunctionDt6.VrfId, vpp_l3.VrfTable_IPV6), // we want ipv6 VRF because DT6
   234  			})
   235  		}
   236  	case *srv6.LocalSID_EndFunctionAd:
   237  		dependencies = append(dependencies, scheduler.Dependency{
   238  			Label: localsidOutgoingInterfaceDep,
   239  			Key:   interfaces.InterfaceKey(ef.EndFunctionAd.OutgoingInterface),
   240  		})
   241  		dependencies = append(dependencies, scheduler.Dependency{
   242  			Label: localsidIncomingInterfaceDep,
   243  			Key:   interfaces.InterfaceKey(ef.EndFunctionAd.IncomingInterface),
   244  		})
   245  	}
   246  
   247  	return dependencies
   248  }
   249  
   250  // ParseIPv6 parses string <str> to IPv6 address (including IPv4 address converted to IPv6 address)
   251  func ParseIPv6(str string) (net.IP, error) {
   252  	ip := net.ParseIP(str)
   253  	if ip == nil {
   254  		return nil, errors.Errorf(" %q is not ip address", str)
   255  	}
   256  	ipv6 := ip.To16()
   257  	if ipv6 == nil {
   258  		return nil, errors.Errorf(" %q is not ipv6 address", str)
   259  	}
   260  	return ipv6, nil
   261  }
   262  
   263  // ParseIPv4 parses string <str> to IPv4 address
   264  func ParseIPv4(str string) (net.IP, error) {
   265  	ip := net.ParseIP(str)
   266  	if ip == nil {
   267  		return nil, errors.Errorf(" %q is not ip address", str)
   268  	}
   269  	ipv4 := ip.To4()
   270  	if ipv4 == nil {
   271  		return nil, errors.Errorf(" %q is not ipv4 address", str)
   272  	}
   273  	return ipv4, nil
   274  }
   275  
   276  func equivalentSIDs(sid1, sid2 string) bool {
   277  	return equivalentIPv6(sid1, sid2)
   278  }
   279  
   280  func equivalentIPv6(ip1Str, ip2str string) bool {
   281  	ip1, err1 := ParseIPv6(ip1Str)
   282  	ip2, err2 := ParseIPv6(ip2str)
   283  	if err1 != nil || err2 != nil { // one of values is invalid, but that will handle validator -> compare by strings
   284  		return equivalentTrimmedLowered(ip1Str, ip2str)
   285  	}
   286  	return ip1.Equal(ip2) // form doesn't matter, are they representig the same IP value ?
   287  }
   288  
   289  func equivalentIPv4(ip1str, ip2str string) bool {
   290  	ip1, err1 := ParseIPv4(ip1str)
   291  	ip2, err2 := ParseIPv4(ip2str)
   292  	if err1 != nil || err2 != nil { // one of values is invalid, but that will handle validator -> compare by strings
   293  		return equivalentTrimmedLowered(ip1str, ip2str)
   294  	}
   295  	return ip1.Equal(ip2) // form doesn't matter, are they representig the same IP value ?
   296  }
   297  
   298  func equivalentTrimmedLowered(str1, str2 string) bool {
   299  	return strings.TrimSpace(strings.ToLower(str1)) == strings.TrimSpace(strings.ToLower(str2))
   300  }