go.ligato.io/vpp-agent/v3@v3.5.0/plugins/linux/ifplugin/descriptor/watcher.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 "context" 19 "fmt" 20 "net" 21 "strings" 22 "sync" 23 "time" 24 25 "github.com/pkg/errors" 26 "github.com/vishvananda/netlink" 27 "go.ligato.io/cn-infra/v2/logging" 28 "google.golang.org/protobuf/proto" 29 "google.golang.org/protobuf/types/known/emptypb" 30 31 kvs "go.ligato.io/vpp-agent/v3/plugins/kvscheduler/api" 32 "go.ligato.io/vpp-agent/v3/plugins/linux/ifplugin/linuxcalls" 33 ifmodel "go.ligato.io/vpp-agent/v3/proto/ligato/linux/interfaces" 34 ) 35 36 const ( 37 // InterfaceWatcherName is the name of the descriptor watching Linux interfaces 38 // in the default namespace. 39 InterfaceWatcherName = "linux-interface-watcher" 40 41 // Interfaces go down for very very short time (typically <1ms) when changes 42 // are being made and we do not want to react to those (would trigger re-creation 43 // of everything that depends on it). 44 // When interface is found to be UP we react immediately so that other objects 45 // that depend on it are created ASAP (e.g. af-packet), but we afford to delay 46 // actions that follow from interface going down. The exception is when watched 47 // interface is completely removed -- in that case we should react immediately, 48 // because for example even only few packets sent over af-packet attached to 49 // a removed interface might "break" VPP. 50 linkDownDelay = 10 * time.Millisecond 51 ) 52 53 // InterfaceWatcher watches default namespace for newly added/removed Linux interfaces. 54 type InterfaceWatcher struct { 55 // input arguments 56 log logging.Logger 57 kvscheduler kvs.KVScheduler 58 ifHandler linuxcalls.NetlinkAPIRead 59 60 // go routine management 61 ctx context.Context 62 cancel context.CancelFunc 63 wg sync.WaitGroup 64 65 // a set of interfaces present in the default namespace 66 ifacesMu sync.Mutex 67 ifaces map[string]hostInterface 68 69 // conditional variable to check if the list of interfaces is in-sync with 70 // Linux network stack 71 intfsInSync bool 72 intfsInSyncCond *sync.Cond 73 74 // Linux notifications 75 linkNotifCount uint64 // counts link notifications across all interfaces 76 linkNotifCh chan netlink.LinkUpdate 77 addrNotifCh chan netlink.AddrUpdate 78 delayedLinkNotifCh chan linkNotif 79 doneCh chan struct{} 80 notify func(notification *ifmodel.InterfaceNotification) 81 } 82 83 type hostInterface struct { 84 name string 85 linkRev uint64 86 enabled bool 87 ipAddrs []string 88 vrfName string 89 } 90 91 type linkNotif struct { 92 netlink.LinkUpdate 93 rev uint64 94 delayed bool 95 } 96 97 // NewInterfaceWatcher creates a new instance of the Interface Watcher. 98 func NewInterfaceWatcher(kvscheduler kvs.KVScheduler, ifHandler linuxcalls.NetlinkAPI, notifyInterface func(*ifmodel.InterfaceNotification), log logging.PluginLogger) *InterfaceWatcher { 99 descriptor := &InterfaceWatcher{ 100 log: log.NewLogger("if-watcher"), 101 kvscheduler: kvscheduler, 102 ifHandler: ifHandler, 103 notify: notifyInterface, 104 ifaces: make(map[string]hostInterface), 105 linkNotifCh: make(chan netlink.LinkUpdate), 106 addrNotifCh: make(chan netlink.AddrUpdate), 107 delayedLinkNotifCh: make(chan linkNotif, 100), 108 doneCh: make(chan struct{}), 109 } 110 descriptor.intfsInSyncCond = sync.NewCond(&descriptor.ifacesMu) 111 descriptor.ctx, descriptor.cancel = context.WithCancel(context.Background()) 112 113 return descriptor 114 } 115 116 // GetDescriptor returns descriptor suitable for registration with the KVScheduler. 117 func (w *InterfaceWatcher) GetDescriptor() *kvs.KVDescriptor { 118 return &kvs.KVDescriptor{ 119 Name: InterfaceWatcherName, 120 KeySelector: w.IsLinuxInterfaceNotification, 121 Retrieve: w.Retrieve, 122 } 123 } 124 125 // IsLinuxInterfaceNotification returns <true> for keys representing 126 // notifications about Linux interfaces in the default network namespace. 127 func (w *InterfaceWatcher) IsLinuxInterfaceNotification(key string) bool { 128 return strings.HasPrefix(key, ifmodel.InterfaceHostNameKeyPrefix) 129 } 130 131 // Retrieve returns key with empty value for every currently existing Linux interface 132 // in the default network namespace. 133 func (w *InterfaceWatcher) Retrieve(correlate []kvs.KVWithMetadata) (values []kvs.KVWithMetadata, err error) { 134 // wait until the set of interfaces is in-sync with the Linux network stack 135 w.ifacesMu.Lock() 136 if !w.intfsInSync { 137 w.intfsInSyncCond.Wait() 138 } 139 defer w.ifacesMu.Unlock() 140 141 for _, hostIface := range w.ifaces { 142 if !hostIface.enabled { 143 continue 144 } 145 values = append(values, kvs.KVWithMetadata{ 146 Key: ifmodel.InterfaceHostNameKey(hostIface.name), 147 Value: &emptypb.Empty{}, 148 Origin: kvs.FromSB, 149 }) 150 for _, ipAddr := range hostIface.ipAddrs { 151 values = append(values, kvs.KVWithMetadata{ 152 Key: ifmodel.InterfaceHostNameWithAddrKey(hostIface.name, ipAddr), 153 Value: &emptypb.Empty{}, 154 Origin: kvs.FromSB, 155 }) 156 } 157 if hostIface.vrfName != "" { 158 values = append(values, kvs.KVWithMetadata{ 159 Key: ifmodel.InterfaceHostNameWithVrfKey(hostIface.name, hostIface.vrfName), 160 Value: &emptypb.Empty{}, 161 Origin: kvs.FromSB, 162 }) 163 } 164 } 165 166 return values, nil 167 } 168 169 // StartWatching starts interface watching. 170 func (w *InterfaceWatcher) StartWatching() error { 171 // watch default namespace to be aware of interfaces not created by this plugin 172 err := w.ifHandler.LinkSubscribe(w.linkNotifCh, w.doneCh) 173 if err != nil { 174 err = errors.Errorf("failed to subscribe for link notifications: %v", err) 175 w.log.Error(err) 176 return err 177 } 178 err = w.ifHandler.AddrSubscribe(w.addrNotifCh, w.doneCh) 179 if err != nil { 180 err = errors.Errorf("failed to subscribe for address notifications: %v", err) 181 w.log.Error(err) 182 return err 183 } 184 w.wg.Add(1) 185 go w.watchDefaultNamespace() 186 return nil 187 } 188 189 // StopWatching stops interface watching. 190 func (w *InterfaceWatcher) StopWatching() { 191 w.cancel() 192 w.wg.Wait() 193 } 194 195 // watchDefaultNamespace watches for notification about added/removed interfaces/addresses 196 // to/from the default namespace. 197 func (w *InterfaceWatcher) watchDefaultNamespace() { 198 defer w.wg.Done() 199 200 // get the set of interfaces already available in the default namespace 201 links, err := w.ifHandler.GetLinkList() 202 if err == nil { 203 for _, link := range links { 204 iface := hostInterface{name: link.Attrs().Name} 205 enabled, err := w.ifHandler.IsInterfaceUp(iface.name) 206 if err != nil { 207 w.log.Warnf("IsInterfaceUp failed for interface %s: %v", 208 iface.name, err) 209 continue 210 } 211 iface.enabled = enabled 212 if addrs, err := w.ifHandler.GetAddressList(iface.name); err == nil { 213 for _, addr := range addrs { 214 iface.ipAddrs = append(iface.ipAddrs, addr.IPNet.String()) 215 } 216 } else { 217 w.log.Warnf("GetAddressList failed for interface %s: %v", 218 iface.name, err) 219 } 220 iface.vrfName, err = w.getVrfName(link) 221 if err != nil { 222 w.log.Warn(err) 223 } 224 w.ifaces[iface.name] = iface 225 } 226 } else { 227 w.log.Warnf("failed to list interfaces in the default namespace: %v", err) 228 } 229 230 // mark the state in-sync with the Linux network stack 231 w.ifacesMu.Lock() 232 w.intfsInSync = true 233 w.ifacesMu.Unlock() 234 w.intfsInSyncCond.Broadcast() 235 236 for { 237 select { 238 case notif := <-w.linkNotifCh: 239 w.linkNotifCount++ 240 w.processLinkNotification(linkNotif{ 241 rev: w.linkNotifCount, 242 LinkUpdate: notif, 243 }) 244 case notif := <-w.delayedLinkNotifCh: 245 w.processLinkNotification(notif) 246 case notif := <-w.addrNotifCh: 247 w.processAddrNotification(notif) 248 case <-w.ctx.Done(): 249 close(w.doneCh) 250 return 251 } 252 } 253 } 254 255 // processLinkNotification processes link notification received from Linux. 256 func (w *InterfaceWatcher) processLinkNotification(linkNotif linkNotif) { 257 var err error 258 w.ifacesMu.Lock() 259 defer w.ifacesMu.Unlock() 260 261 ifName := linkNotif.Attrs().Name 262 exists, _ := w.ifHandler.InterfaceExists(ifName) 263 if !linkNotif.delayed && exists && !isLinkUp(linkNotif.LinkUpdate) { 264 // do not react to interface being DOWN immediately, this could be only very temporary 265 linkNotif.delayed = true 266 time.AfterFunc(linkDownDelay, func() { w.delayedLinkNotifCh <- linkNotif }) 267 return 268 } 269 270 // send notification to any interface state watcher (e.g. Configurator) 271 w.sendStateNotification(linkNotif.LinkUpdate) 272 273 // push update to the KV Scheduler 274 prevState := w.ifaces[ifName] 275 if prevState.linkRev > linkNotif.rev { 276 // newer notification received in the meantime 277 return 278 } 279 newState := prevState 280 newState.name = ifName 281 newState.linkRev = linkNotif.rev 282 newState.enabled = isLinkUp(linkNotif.LinkUpdate) 283 newState.vrfName, err = w.getVrfName(linkNotif.Link) 284 if err != nil { 285 w.log.Warn(err) 286 } 287 if prevState.enabled != newState.enabled { 288 w.updateLinkKV(ifName, newState.enabled) 289 // do not advertise IPs and VRF if interface is disabled 290 for _, ipAddr := range newState.ipAddrs { 291 w.updateAddrKV(ifName, ipAddr, !newState.enabled) 292 } 293 if newState.enabled { 294 w.updateVrfKV(ifName, newState.vrfName, false) 295 } else { 296 w.updateVrfKV(ifName, prevState.vrfName, true) 297 } 298 } else if prevState.vrfName != newState.vrfName { 299 w.updateVrfKV(ifName, prevState.vrfName, true) 300 w.updateVrfKV(ifName, newState.vrfName, false) 301 } 302 w.ifaces[ifName] = newState 303 } 304 305 // processAddrNotification processes address notification received from Linux. 306 func (w *InterfaceWatcher) processAddrNotification(addrUpdate netlink.AddrUpdate) { 307 w.ifacesMu.Lock() 308 defer w.ifacesMu.Unlock() 309 310 link, err := w.ifHandler.GetLinkByIndex(addrUpdate.LinkIndex) 311 if err != nil { 312 w.log.Warn(err) 313 return 314 } 315 316 // push update to the KV Scheduler 317 ifName := link.Attrs().Name 318 addr := addrUpdate.LinkAddress.String() 319 removed := !addrUpdate.NewAddr 320 prevState := w.ifaces[ifName] 321 addrIdx := -1 322 for i, ipAddr := range prevState.ipAddrs { 323 if ipAddr == addr { 324 addrIdx = i 325 break 326 } 327 } 328 knownAddr := addrIdx != -1 329 if knownAddr != removed { 330 // removed unknown IP or added already known IP 331 return 332 } 333 if prevState.enabled { 334 w.updateAddrKV(ifName, addr, removed) 335 } 336 337 // update the internal cache 338 newState := prevState 339 newState.name = ifName 340 if removed { 341 lastIdx := len(newState.ipAddrs) - 1 342 newState.ipAddrs[addrIdx] = newState.ipAddrs[lastIdx] 343 newState.ipAddrs[lastIdx] = "" 344 newState.ipAddrs = newState.ipAddrs[:lastIdx] 345 } else { 346 newState.ipAddrs = append(newState.ipAddrs, addr) 347 } 348 w.ifaces[ifName] = newState 349 } 350 351 func linkToInterfaceType(link netlink.Link) ifmodel.Interface_Type { 352 switch link.Type() { 353 case "veth": 354 return ifmodel.Interface_VETH 355 case "tuntap", "tun": 356 return ifmodel.Interface_TAP_TO_VPP 357 case "vrf": 358 return ifmodel.Interface_VRF_DEVICE 359 case "dummy": 360 return ifmodel.Interface_DUMMY 361 default: 362 if link.Attrs().Name == linuxcalls.DefaultLoopbackName { 363 return ifmodel.Interface_LOOPBACK 364 } 365 return ifmodel.Interface_UNDEFINED 366 } 367 } 368 369 // updateLinkKV updates key-value pair representing the interface latest link status. 370 func (w *InterfaceWatcher) updateLinkKV(ifName string, enabled bool) { 371 var value proto.Message 372 if enabled { 373 // empty == enabled, nil == disabled 374 value = &emptypb.Empty{} 375 } 376 if err := w.kvscheduler.PushSBNotification(kvs.KVWithMetadata{ 377 Key: ifmodel.InterfaceHostNameKey(ifName), 378 Value: value, 379 Metadata: nil, 380 }); err != nil { 381 w.log.Warnf("pushing SB notification failed: %v", err) 382 } 383 } 384 385 // updateAddrKV updates key-value pair representing IP address assigned to a host interface. 386 func (w *InterfaceWatcher) updateAddrKV(ifName string, address string, removed bool) { 387 var value proto.Message 388 if !removed { 389 // empty == assigned, nil == not assigned 390 value = &emptypb.Empty{} 391 } 392 if err := w.kvscheduler.PushSBNotification(kvs.KVWithMetadata{ 393 Key: ifmodel.InterfaceHostNameWithAddrKey(ifName, address), 394 Value: value, 395 Metadata: nil, 396 }); err != nil { 397 w.log.Warnf("pushing SB notification failed: %v", err) 398 } 399 } 400 401 // updateAddrKV updates key-value pair representing association between interface and VRF. 402 func (w *InterfaceWatcher) updateVrfKV(ifName string, vrf string, removed bool) { 403 var value proto.Message 404 if vrf == "" { 405 return 406 } 407 if !removed { 408 // empty == assigned, nil == not assigned 409 value = &emptypb.Empty{} 410 } 411 if err := w.kvscheduler.PushSBNotification(kvs.KVWithMetadata{ 412 Key: ifmodel.InterfaceHostNameWithVrfKey(ifName, vrf), 413 Value: value, 414 Metadata: nil, 415 }); err != nil { 416 w.log.Warnf("pushing SB notification failed: %v", err) 417 } 418 } 419 420 func (w *InterfaceWatcher) sendStateNotification(linkUpdate netlink.LinkUpdate) { 421 if w.notify != nil { 422 attrs := linkUpdate.Attrs() 423 adminStatus := ifmodel.InterfaceState_DOWN 424 if isLinkUp(linkUpdate) { 425 adminStatus = ifmodel.InterfaceState_UP 426 } 427 operStatus := ifmodel.InterfaceState_DOWN 428 if attrs.OperState != netlink.OperDown && attrs.OperState != netlink.OperNotPresent { 429 operStatus = ifmodel.InterfaceState_UP 430 } 431 w.notify(&ifmodel.InterfaceNotification{ 432 Type: ifmodel.InterfaceNotification_UPDOWN, 433 State: &ifmodel.InterfaceState{ 434 Name: attrs.Alias, 435 InternalName: attrs.Name, 436 Type: linkToInterfaceType(linkUpdate.Link), 437 IfIndex: int32(attrs.Index), 438 AdminStatus: adminStatus, 439 OperStatus: operStatus, 440 LastChange: time.Now().Unix(), 441 PhysAddress: attrs.HardwareAddr.String(), 442 Speed: 0, 443 Mtu: uint32(attrs.MTU), 444 Statistics: nil, 445 }, 446 }) 447 } 448 } 449 450 func (w *InterfaceWatcher) getVrfName(link netlink.Link) (string, error) { 451 masterIndex := link.Attrs().MasterIndex 452 if masterIndex != 0 { 453 vrfLink, err := w.ifHandler.GetLinkByIndex(masterIndex) 454 if err != nil { 455 err = fmt.Errorf("GetLinkByIndex failed for master interface with index %d: %w", 456 masterIndex, err) 457 return "", err 458 } 459 if vrfDev, isVrf := vrfLink.(*netlink.Vrf); isVrf { 460 return vrfDev.Name, nil 461 } 462 } 463 return "", nil 464 } 465 466 func isLinkUp(update netlink.LinkUpdate) bool { 467 return (update.Attrs().Flags & net.FlagUp) == net.FlagUp 468 }