go.ligato.io/vpp-agent/v3@v3.5.0/plugins/linux/l3plugin/descriptor/arp.go (about) 1 // Copyright (c) 2018 Cisco 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 "strings" 20 21 "github.com/pkg/errors" 22 "github.com/vishvananda/netlink" 23 24 "go.ligato.io/cn-infra/v2/logging" 25 26 kvs "go.ligato.io/vpp-agent/v3/plugins/kvscheduler/api" 27 28 "go.ligato.io/vpp-agent/v3/plugins/linux/ifplugin" 29 ifdescriptor "go.ligato.io/vpp-agent/v3/plugins/linux/ifplugin/descriptor" 30 "go.ligato.io/vpp-agent/v3/plugins/linux/l3plugin/descriptor/adapter" 31 l3linuxcalls "go.ligato.io/vpp-agent/v3/plugins/linux/l3plugin/linuxcalls" 32 "go.ligato.io/vpp-agent/v3/plugins/linux/nsplugin" 33 nslinuxcalls "go.ligato.io/vpp-agent/v3/plugins/linux/nsplugin/linuxcalls" 34 "go.ligato.io/vpp-agent/v3/plugins/netalloc" 35 netalloc_descr "go.ligato.io/vpp-agent/v3/plugins/netalloc/descriptor" 36 ifmodel "go.ligato.io/vpp-agent/v3/proto/ligato/linux/interfaces" 37 l3 "go.ligato.io/vpp-agent/v3/proto/ligato/linux/l3" 38 netalloc_api "go.ligato.io/vpp-agent/v3/proto/ligato/netalloc" 39 ) 40 41 const ( 42 // ARPDescriptorName is the name of the descriptor for Linux ARP entries. 43 ARPDescriptorName = "linux-arp" 44 45 // dependency labels 46 arpInterfaceDep = "interface-is-up" 47 arpInterfaceIPDep = "interface-has-ip-address" 48 ) 49 50 // A list of non-retriable errors: 51 var ( 52 // ErrARPWithoutInterface is returned when Linux ARP configuration is missing 53 // interface reference. 54 ErrARPWithoutInterface = errors.New("Linux ARP entry defined without interface reference") 55 56 // ErrARPWithInvalidIP is returned when Linux ARP configuration contains IP address that cannot be parsed. 57 ErrARPWithInvalidIP = errors.New("Linux ARP entry defined with invalid IP address") 58 59 // ErrARPWithoutHwAddr is returned when Linux ARP configuration is missing 60 // MAC address. 61 ErrARPWithoutHwAddr = errors.New("Linux ARP entry defined without MAC address") 62 63 // ErrARPWithInvalidHwAddr is returned when Linux ARP configuration contains MAC address that cannot be parsed. 64 ErrARPWithInvalidHwAddr = errors.New("Linux ARP entry defined with invalid MAC address") 65 ) 66 67 // ARPDescriptor teaches KVScheduler how to configure Linux ARP entries. 68 type ARPDescriptor struct { 69 log logging.Logger 70 l3Handler l3linuxcalls.NetlinkAPI 71 ifPlugin ifplugin.API 72 nsPlugin nsplugin.API 73 addrAlloc netalloc.AddressAllocator 74 scheduler kvs.KVScheduler 75 76 // parallelization of the Retrieve operation 77 goRoutinesCnt int 78 } 79 80 // NewARPDescriptor creates a new instance of the ARP descriptor. 81 func NewARPDescriptor( 82 scheduler kvs.KVScheduler, ifPlugin ifplugin.API, nsPlugin nsplugin.API, addrAlloc netalloc.AddressAllocator, 83 l3Handler l3linuxcalls.NetlinkAPI, log logging.PluginLogger, goRoutinesCnt int) *kvs.KVDescriptor { 84 85 ctx := &ARPDescriptor{ 86 scheduler: scheduler, 87 l3Handler: l3Handler, 88 ifPlugin: ifPlugin, 89 nsPlugin: nsPlugin, 90 addrAlloc: addrAlloc, 91 goRoutinesCnt: goRoutinesCnt, 92 log: log.NewLogger("arp-descriptor"), 93 } 94 95 typedDescr := &adapter.ARPDescriptor{ 96 Name: ARPDescriptorName, 97 NBKeyPrefix: l3.ModelARPEntry.KeyPrefix(), 98 ValueTypeName: l3.ModelARPEntry.ProtoName(), 99 KeySelector: l3.ModelARPEntry.IsKeyValid, 100 KeyLabel: l3.ModelARPEntry.StripKeyPrefix, 101 ValueComparator: ctx.EquivalentARPs, 102 Validate: ctx.Validate, 103 Create: ctx.Create, 104 Delete: ctx.Delete, 105 Update: ctx.Update, 106 Retrieve: ctx.Retrieve, 107 Dependencies: ctx.Dependencies, 108 RetrieveDependencies: []string{ 109 netalloc_descr.IPAllocDescriptorName, 110 ifdescriptor.InterfaceDescriptorName}, 111 } 112 return adapter.NewARPDescriptor(typedDescr) 113 } 114 115 // EquivalentARPs is case-insensitive comparison function for l3.LinuxARPEntry. 116 // Only MAC addresses are compared - interface and IP address are part of the key 117 // which is already given to be the same for the two values. 118 func (d *ARPDescriptor) EquivalentARPs(key string, oldArp, NewArp *l3.ARPEntry) bool { 119 // compare MAC addresses case-insensitively 120 return strings.EqualFold(oldArp.HwAddress, NewArp.HwAddress) 121 } 122 123 // Validate validates ARP entry configuration. 124 func (d *ARPDescriptor) Validate(key string, arp *l3.ARPEntry) (err error) { 125 if arp.Interface == "" { 126 return kvs.NewInvalidValueError(ErrARPWithoutInterface, "interface") 127 } 128 if arp.HwAddress == "" { 129 return kvs.NewInvalidValueError(ErrARPWithoutHwAddr, "hw_address") 130 } 131 return d.addrAlloc.ValidateIPAddress(arp.IpAddress, "", "ip_address", netalloc.GWRefAllowed) 132 } 133 134 // Create creates ARP entry. 135 func (d *ARPDescriptor) Create(key string, arp *l3.ARPEntry) (metadata interface{}, err error) { 136 err = d.updateARPEntry(arp, "add", d.l3Handler.SetARPEntry) 137 return nil, err 138 } 139 140 // Delete removes ARP entry. 141 func (d *ARPDescriptor) Delete(key string, arp *l3.ARPEntry, metadata interface{}) error { 142 return d.updateARPEntry(arp, "delete", d.l3Handler.DelARPEntry) 143 } 144 145 // Update is able to change MAC address of the ARP entry. 146 func (d *ARPDescriptor) Update(key string, oldARP, newARP *l3.ARPEntry, oldMetadata interface{}) (newMetadata interface{}, err error) { 147 err = d.updateARPEntry(newARP, "modify", d.l3Handler.SetARPEntry) 148 return nil, err 149 } 150 151 // updateARPEntry adds, modifies or deletes an ARP entry. 152 func (d *ARPDescriptor) updateARPEntry(arp *l3.ARPEntry, actionName string, actionClb func(arpEntry *netlink.Neigh) error) error { 153 var err error 154 155 // Prepare ARP entry object 156 neigh := &netlink.Neigh{} 157 158 // Get interface metadata 159 ifMeta, found := d.ifPlugin.GetInterfaceIndex().LookupByName(arp.Interface) 160 if !found || ifMeta == nil { 161 err = errors.Errorf("failed to obtain metadata for interface %s", arp.Interface) 162 d.log.Error(err) 163 return err 164 } 165 166 // set link index 167 neigh.LinkIndex = ifMeta.LinuxIfIndex 168 169 // set IP address 170 ipAddr, err := d.addrAlloc.GetOrParseIPAddress(arp.IpAddress, "", 171 netalloc_api.IPAddressForm_ADDR_ONLY) 172 if err != nil { 173 d.log.Error(err) 174 return err 175 } 176 neigh.IP = ipAddr.IP 177 178 // set MAC address 179 mac, err := net.ParseMAC(arp.HwAddress) 180 if err != nil { 181 err = ErrARPWithInvalidHwAddr 182 d.log.Error(err) 183 return err 184 } 185 neigh.HardwareAddr = mac 186 187 // set ARP entry state (always permanent for static ARPs configured by the agent) 188 neigh.State = netlink.NUD_PERMANENT 189 190 // set ip family based on the IP address 191 if neigh.IP.To4() != nil { 192 neigh.Family = netlink.FAMILY_V4 193 } else { 194 neigh.Family = netlink.FAMILY_V6 195 } 196 197 // move to the namespace of the associated interface 198 nsCtx := nslinuxcalls.NewNamespaceMgmtCtx() 199 revertNs, err := d.nsPlugin.SwitchToNamespace(nsCtx, ifMeta.Namespace) 200 if err != nil { 201 err = errors.Errorf("failed to switch namespace: %v", err) 202 d.log.Error(err) 203 return err 204 } 205 defer revertNs() 206 207 // update ARP entry in the interface namespace 208 err = actionClb(neigh) 209 if err != nil { 210 err = errors.Errorf("failed to %s linux ARP entry: %v", actionName, err) 211 d.log.Error(err) 212 return err 213 } 214 215 return nil 216 } 217 218 // Dependencies lists dependencies for a Linux ARP entry. 219 func (d *ARPDescriptor) Dependencies(key string, arp *l3.ARPEntry) (deps []kvs.Dependency) { 220 // the associated interface must exist, but also must be UP and have at least 221 // one IP address assigned (to be in the L3 mode) 222 if arp.Interface != "" { 223 deps = []kvs.Dependency{ 224 { 225 Label: arpInterfaceDep, 226 Key: ifmodel.InterfaceStateKey(arp.Interface, true), 227 }, 228 { 229 Label: arpInterfaceIPDep, 230 AnyOf: kvs.AnyOfDependency{ 231 KeyPrefixes: []string{ifmodel.InterfaceAddressPrefix(arp.Interface)}, 232 }, 233 }, 234 } 235 } 236 // if IP is only a symlink to netalloc address pool, then wait for it to be allocated first 237 allocDep, hasAllocDep := d.addrAlloc.GetAddressAllocDep(arp.IpAddress, "", "") 238 if hasAllocDep { 239 deps = append(deps, allocDep) 240 } 241 return deps 242 } 243 244 // Retrieve returns all ARP entries associated with interfaces managed by this agent. 245 func (d *ARPDescriptor) Retrieve(correlate []adapter.ARPKVWithMetadata) ([]adapter.ARPKVWithMetadata, error) { 246 var values []adapter.ARPKVWithMetadata 247 248 hwLabel := func(arp *l3.ARPEntry) string { 249 return arp.Interface + "/" + strings.ToLower(arp.HwAddress) 250 } 251 expCfg := make(map[string]*l3.ARPEntry) // Interface+MAC -> expected ARP config 252 for _, kv := range correlate { 253 expCfg[hwLabel(kv.Value)] = kv.Value 254 } 255 256 arpDetails, err := d.l3Handler.DumpARPEntries() 257 if err != nil { 258 return nil, errors.Errorf("Failed to retrieve linux ARPs: %v", err) 259 } 260 261 for _, arpDetail := range arpDetails { 262 // Convert to key-value object with metadata 263 arp := adapter.ARPKVWithMetadata{ 264 Key: l3.ArpKey(arpDetail.ARP.Interface, arpDetail.ARP.IpAddress), 265 Value: &l3.ARPEntry{ 266 Interface: arpDetail.ARP.Interface, 267 IpAddress: arpDetail.ARP.IpAddress, 268 HwAddress: arpDetail.ARP.HwAddress, 269 }, 270 Origin: kvs.UnknownOrigin, // let the scheduler to determine the origin 271 } 272 if expCfg, hasExpCfg := expCfg[hwLabel(arp.Value)]; hasExpCfg { 273 arp.Value.IpAddress = d.addrAlloc.CorrelateRetrievedIPs( 274 []string{expCfg.IpAddress}, []string{arp.Value.IpAddress}, 275 "", netalloc_api.IPAddressForm_ADDR_ONLY)[0] 276 // recreate key in case the IP address was replaced with a netalloc link 277 arp.Key = l3.ArpKey(arp.Value.Interface, arp.Value.IpAddress) 278 } 279 values = append(values, arp) 280 } 281 282 return values, nil 283 }