go.ligato.io/vpp-agent/v3@v3.5.0/plugins/vpp/ifplugin/descriptor/dhcp.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 "strings" 20 "sync" 21 22 "github.com/go-errors/errors" 23 "go.ligato.io/cn-infra/v2/logging" 24 "google.golang.org/protobuf/proto" 25 "google.golang.org/protobuf/types/known/emptypb" 26 27 kvs "go.ligato.io/vpp-agent/v3/plugins/kvscheduler/api" 28 "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" 29 "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/vppcalls" 30 "go.ligato.io/vpp-agent/v3/proto/ligato/netalloc" 31 interfaces "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" 32 ) 33 34 const ( 35 // DHCPDescriptorName is the name of the descriptor configuring DHCP for VPP 36 // interfaces. 37 DHCPDescriptorName = "vpp-dhcp" 38 ) 39 40 // DHCPDescriptor enables/disables DHCP for VPP interfaces and notifies about 41 // new DHCP leases. 42 type DHCPDescriptor struct { 43 // provided by the plugin 44 log logging.Logger 45 ifHandler vppcalls.InterfaceVppAPI 46 kvscheduler kvs.KVScheduler 47 ifIndex ifaceidx.IfaceMetadataIndex 48 49 // DHCP notification watching 50 cancel context.CancelFunc 51 wg sync.WaitGroup 52 } 53 54 // NewDHCPDescriptor creates a new instance of DHCPDescriptor. 55 func NewDHCPDescriptor(kvscheduler kvs.KVScheduler, ifHandler vppcalls.InterfaceVppAPI, 56 ifIndex ifaceidx.IfaceMetadataIndex, log logging.PluginLogger, 57 ) (*kvs.KVDescriptor, *DHCPDescriptor) { 58 ctx := &DHCPDescriptor{ 59 kvscheduler: kvscheduler, 60 ifHandler: ifHandler, 61 ifIndex: ifIndex, 62 log: log.NewLogger("dhcp-descriptor"), 63 } 64 descr := &kvs.KVDescriptor{ 65 Name: DHCPDescriptorName, 66 KeySelector: ctx.IsDHCPRelatedKey, 67 KeyLabel: ctx.InterfaceNameFromKey, 68 WithMetadata: true, // DHCP leases 69 Create: ctx.Create, // DHCP client 70 Delete: ctx.Delete, // DHCP client 71 Retrieve: ctx.Retrieve, // DHCP leases 72 DerivedValues: ctx.DerivedValues, // IP address from DHCP lease 73 RetrieveDependencies: []string{InterfaceDescriptorName}, 74 } 75 return descr, ctx 76 } 77 78 // WatchDHCPNotifications starts watching for DHCP notifications. 79 func (d *DHCPDescriptor) WatchDHCPNotifications(ctx context.Context) { 80 // Create child context 81 var childCtx context.Context 82 childCtx, d.cancel = context.WithCancel(ctx) 83 84 d.wg.Add(1) 85 go d.watchDHCPNotifications(childCtx) 86 } 87 88 // Close stops watching of DHCP notifications. 89 func (d *DHCPDescriptor) Close() error { 90 d.cancel() 91 d.wg.Wait() 92 return nil 93 } 94 95 // IsDHCPRelatedKey returns true if the key is identifying DHCP client (derived value) 96 // or DHCP lease (notification). 97 func (d *DHCPDescriptor) IsDHCPRelatedKey(key string) bool { 98 if _, isValid := interfaces.ParseNameFromDHCPClientKey(key); isValid { 99 return true 100 } 101 if _, isValid := interfaces.ParseNameFromDHCPLeaseKey(key); isValid { 102 return true 103 } 104 return false 105 } 106 107 // InterfaceNameFromKey returns interface name from DHCP-related key. 108 func (d *DHCPDescriptor) InterfaceNameFromKey(key string) string { 109 if iface, isValid := interfaces.ParseNameFromDHCPClientKey(key); isValid { 110 return iface 111 } 112 if iface, isValid := interfaces.ParseNameFromDHCPLeaseKey(key); isValid { 113 return iface 114 } 115 return key 116 } 117 118 // Create enables DHCP client. 119 func (d *DHCPDescriptor) Create(key string, emptyVal proto.Message) (metadata kvs.Metadata, err error) { 120 ifName, _ := interfaces.ParseNameFromDHCPClientKey(key) 121 ifMeta, found := d.ifIndex.LookupByName(ifName) 122 if !found { 123 err = errors.Errorf("failed to find DHCP-enabled interface %s", ifName) 124 d.log.Error(err) 125 return nil, err 126 } 127 128 if err := d.ifHandler.SetInterfaceAsDHCPClient(ifMeta.SwIfIndex, ifName); err != nil { 129 err = errors.Errorf("failed to enable DHCP client for interface %s", ifName) 130 d.log.Error(err) 131 return nil, err 132 } 133 134 return nil, err 135 } 136 137 // Delete disables DHCP client. 138 func (d *DHCPDescriptor) Delete(key string, emptyVal proto.Message, metadata kvs.Metadata) error { 139 ifName, _ := interfaces.ParseNameFromDHCPClientKey(key) 140 ifMeta, found := d.ifIndex.LookupByName(ifName) 141 if !found { 142 err := errors.Errorf("failed to find DHCP-enabled interface %s", ifName) 143 d.log.Error(err) 144 return err 145 } 146 147 if err := d.ifHandler.UnsetInterfaceAsDHCPClient(ifMeta.SwIfIndex, ifName); err != nil { 148 err = errors.Errorf("failed to disable DHCP client for interface %s", ifName) 149 d.log.Error(err) 150 return err 151 } 152 153 // notify about the unconfigured client by removing the lease notification 154 return d.kvscheduler.PushSBNotification(kvs.KVWithMetadata{ 155 Key: interfaces.DHCPLeaseKey(ifName), 156 Value: nil, 157 Metadata: nil, 158 }) 159 } 160 161 // Retrieve returns all existing DHCP leases. 162 func (d *DHCPDescriptor) Retrieve(correlate []kvs.KVWithMetadata) ( 163 leases []kvs.KVWithMetadata, err error, 164 ) { 165 // Retrieve VPP configuration. 166 dhcpDump, err := d.ifHandler.DumpDhcpClients() 167 if err != nil { 168 d.log.Error(err) 169 return leases, err 170 } 171 172 for ifIdx, dhcpData := range dhcpDump { 173 ifName, _, found := d.ifIndex.LookupBySwIfIndex(ifIdx) 174 if !found { 175 d.log.Warnf("failed to find interface sw_if_index=%d with DHCP lease", ifIdx) 176 return leases, err 177 } 178 // Store lease under both value (for visibility & to derive interface IP address) 179 // and metadata (for watching). 180 lease := &interfaces.DHCPLease{ 181 InterfaceName: ifName, 182 HostName: dhcpData.Lease.Hostname, 183 HostPhysAddress: dhcpData.Lease.HostMac, 184 IsIpv6: dhcpData.Lease.IsIPv6, 185 HostIpAddress: dhcpData.Lease.HostAddress, 186 RouterIpAddress: dhcpData.Lease.RouterAddress, 187 } 188 leases = append(leases, kvs.KVWithMetadata{ 189 Key: interfaces.DHCPLeaseKey(ifName), 190 Value: lease, 191 Metadata: lease, 192 Origin: kvs.FromSB, 193 }) 194 } 195 196 return leases, nil 197 } 198 199 // DerivedValues derives empty value for leased IP address. 200 func (d *DHCPDescriptor) DerivedValues(key string, dhcpData proto.Message) (derValues []kvs.KeyValuePair) { 201 if strings.HasPrefix(key, interfaces.DHCPLeaseKeyPrefix) { 202 dhcpLease, ok := dhcpData.(*interfaces.DHCPLease) 203 if ok && dhcpLease.HostIpAddress != "" { 204 return []kvs.KeyValuePair{ 205 { 206 Key: interfaces.InterfaceAddressKey(dhcpLease.InterfaceName, dhcpLease.HostIpAddress, 207 netalloc.IPAddressSource_FROM_DHCP), 208 Value: &emptypb.Empty{}, 209 }, 210 } 211 } 212 } 213 return derValues 214 } 215 216 // watchDHCPNotifications watches and processes DHCP notifications. 217 func (d *DHCPDescriptor) watchDHCPNotifications(ctx context.Context) { 218 defer d.wg.Done() 219 d.log.Debug("Started watcher on DHCP notifications") 220 221 dhcpChan := make(chan *vppcalls.Lease, 10) 222 if err := d.ifHandler.WatchDHCPLeases(ctx, dhcpChan); err != nil { 223 d.log.Errorf("watching dhcp leases failed: %v", err) 224 return 225 } 226 227 for { 228 select { 229 case lease := <-dhcpChan: 230 // Get interface logical name 231 ifName, _, found := d.ifIndex.LookupBySwIfIndex(lease.SwIfIndex) 232 if !found { 233 d.log.Warnf("Interface sw_if_index=%d with DHCP lease was not found in the mapping", lease.SwIfIndex) 234 continue 235 } 236 237 d.log.Debugf("DHCP assigned %v to interface %q (router address %v)", lease.HostAddress, ifName, lease.RouterAddress) 238 239 // notify about the new lease 240 dhcpLease := &interfaces.DHCPLease{ 241 InterfaceName: ifName, 242 HostName: lease.Hostname, 243 HostPhysAddress: lease.HostMac, 244 HostIpAddress: lease.HostAddress, 245 RouterIpAddress: lease.RouterAddress, 246 } 247 if err := d.kvscheduler.PushSBNotification(kvs.KVWithMetadata{ 248 Key: interfaces.DHCPLeaseKey(ifName), 249 Value: dhcpLease, 250 Metadata: dhcpLease, 251 }); err != nil { 252 d.log.Error(err) 253 } 254 case <-ctx.Done(): 255 return 256 } 257 } 258 }