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