forked from ligato/vpp-agent
-
Notifications
You must be signed in to change notification settings - Fork 0
/
dump_interface_linuxcalls.go
367 lines (331 loc) · 12.1 KB
/
dump_interface_linuxcalls.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
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
// 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.
// +build !windows,!darwin
package linuxcalls
import (
"strconv"
"strings"
"github.com/gogo/protobuf/proto"
"github.com/ligato/cn-infra/logging"
"github.com/vishvananda/netlink"
"golang.org/x/sys/unix"
interfaces "github.com/ligato/vpp-agent/api/models/linux/interfaces"
namespaces "github.com/ligato/vpp-agent/api/models/linux/namespace"
"github.com/ligato/vpp-agent/plugins/linux/nsplugin/linuxcalls"
)
const (
// defaultLoopbackName is the name used to access loopback interface in linux
// host_if_name field in config is effectively ignored
DefaultLoopbackName = "lo"
// minimum number of namespaces to be given to a single Go routine for processing
// in the Retrieve operation
minWorkForGoRoutine = 3
)
// retrievedIfaces is used as the return value sent via channel by retrieveInterfaces().
type retrievedInterfaces struct {
interfaces []*InterfaceDetails
stats []*InterfaceStatistics
err error
}
// DumpInterfaces retrieves all linux interfaces from default namespace and from all
// the other namespaces based on known linux interfaces from the index map.
func (h *NetLinkHandler) DumpInterfaces() ([]*InterfaceDetails, error) {
return h.DumpInterfacesFromNamespaces(h.getKnownNamespaces())
}
// DumpInterfaceStats retrieves statistics for all linux interfaces from default namespace
// and from all the other namespaces based on known linux interfaces from the index map.
func (h *NetLinkHandler) DumpInterfaceStats() ([]*InterfaceStatistics, error) {
return h.DumpInterfaceStatsFromNamespaces(h.getKnownNamespaces())
}
// DumpInterfacesFromNamespaces requires context in form of the namespace list of which linux interfaces
// will be retrieved. If no context is provided, interfaces only from the default namespace are retrieved.
func (h *NetLinkHandler) DumpInterfacesFromNamespaces(nsList []*namespaces.NetNamespace) ([]*InterfaceDetails, error) {
// Always retrieve from the default namespace
if len(nsList) == 0 {
nsList = []*namespaces.NetNamespace{nil}
}
// Determine the number of go routines to invoke
goRoutinesCnt := len(nsList) / minWorkForGoRoutine
if goRoutinesCnt == 0 {
goRoutinesCnt = 1
}
if goRoutinesCnt > h.goRoutineCount {
goRoutinesCnt = h.goRoutineCount
}
ch := make(chan retrievedInterfaces, goRoutinesCnt)
// Invoke multiple go routines for more efficient parallel interface retrieval
for idx := 0; idx < goRoutinesCnt; idx++ {
if goRoutinesCnt > 1 {
go h.retrieveInterfaces(nsList, idx, goRoutinesCnt, ch)
} else {
h.retrieveInterfaces(nsList, idx, goRoutinesCnt, ch)
}
}
// receive results from the go routines
var linuxIfs []*InterfaceDetails
for idx := 0; idx < goRoutinesCnt; idx++ {
retrieved := <-ch
if retrieved.err != nil {
return nil, retrieved.err
}
linuxIfs = append(linuxIfs, retrieved.interfaces...)
}
return linuxIfs, nil
}
// DumpInterfaceStatsFromNamespaces requires context in form of the namespace list of which linux interface stats
// will be retrieved. If no context is provided, interface stats only from the default namespace interfaces
// are retrieved.
func (h *NetLinkHandler) DumpInterfaceStatsFromNamespaces(nsList []*namespaces.NetNamespace) ([]*InterfaceStatistics, error) {
// Always retrieve from the default namespace
if len(nsList) == 0 {
nsList = []*namespaces.NetNamespace{nil}
}
// Determine the number of go routines to invoke
goRoutinesCnt := len(nsList) / minWorkForGoRoutine
if goRoutinesCnt == 0 {
goRoutinesCnt = 1
}
if goRoutinesCnt > h.goRoutineCount {
goRoutinesCnt = h.goRoutineCount
}
ch := make(chan retrievedInterfaces, goRoutinesCnt)
// Invoke multiple go routines for more efficient parallel interface retrieval
for idx := 0; idx < goRoutinesCnt; idx++ {
if goRoutinesCnt > 1 {
go h.retrieveInterfaces(nsList, idx, goRoutinesCnt, ch)
} else {
h.retrieveInterfaces(nsList, idx, goRoutinesCnt, ch)
}
}
// receive results from the go routines
var linuxStats []*InterfaceStatistics
for idx := 0; idx < goRoutinesCnt; idx++ {
retrieved := <-ch
if retrieved.err != nil {
return nil, retrieved.err
}
linuxStats = append(linuxStats, retrieved.stats...)
}
return linuxStats, nil
}
// Obtain all linux namespaces known to the Linux plugin
func (h *NetLinkHandler) getKnownNamespaces() []*namespaces.NetNamespace {
// Add default namespace
nsList := []*namespaces.NetNamespace{nil}
for _, ifName := range h.ifIndexes.ListAllInterfaces() {
if metadata, exists := h.ifIndexes.LookupByName(ifName); exists {
if metadata == nil {
h.log.Warnf("metadata for %s are nil", ifName)
continue
}
nsListed := false
for _, ns := range nsList {
if proto.Equal(ns, metadata.Namespace) {
nsListed = true
break
}
}
if !nsListed {
nsList = append(nsList, metadata.Namespace)
}
}
}
return nsList
}
// GetVethAlias returns alias for Linux VETH interface managed by the agent.
// The alias stores the VETH logical name together with the peer (logical) name.
func GetVethAlias(vethName, peerName string) string {
return vethName + "/" + peerName
}
// ParseVethAlias parses out VETH logical name together with the peer name from the alias.
func ParseVethAlias(alias string) (vethName, peerName string) {
aliasParts := strings.Split(alias, "/")
vethName = aliasParts[0]
if len(aliasParts) > 1 {
peerName = aliasParts[1]
}
return
}
// GetTapAlias returns alias for Linux TAP interface managed by the agent.
// The alias stores the TAP_TO_VPP logical name together with VPP-TAP logical name
// and the host interface name as originally set by VPP side.
func GetTapAlias(linuxIf *interfaces.Interface, origHostIfName string) string {
return linuxIf.Name + "/" + linuxIf.GetTap().GetVppTapIfName() + "/" + origHostIfName
}
// ParseTapAlias parses out TAP_TO_VPP logical name together with the name of the
// linked VPP-TAP and the original TAP host interface name.
func ParseTapAlias(alias string) (linuxTapName, vppTapName, origHostIfName string) {
aliasParts := strings.Split(alias, "/")
linuxTapName = aliasParts[0]
if len(aliasParts) > 1 {
vppTapName = aliasParts[1]
}
if len(aliasParts) > 2 {
origHostIfName = aliasParts[2]
}
return
}
// retrieveInterfaces is run by a separate go routine to retrieve all interfaces
// present in every <goRoutineIdx>-th network namespace from the list.
func (h *NetLinkHandler) retrieveInterfaces(nsList []*namespaces.NetNamespace, goRoutineIdx, goRoutinesCnt int, ch chan<- retrievedInterfaces) {
var retrieved retrievedInterfaces
nsCtx := linuxcalls.NewNamespaceMgmtCtx()
for i := goRoutineIdx; i < len(nsList); i += goRoutinesCnt {
nsRef := nsList[i]
// switch to the namespace
revert, err := h.nsPlugin.SwitchToNamespace(nsCtx, nsRef)
if err != nil {
h.log.WithField("namespace", nsRef).Warn("Failed to switch namespace:", err)
continue // continue with the next namespace
}
// get all links in the namespace
links, err := h.GetLinkList()
if err != nil {
h.log.Error("Failed to get link list:", err)
// switch back to the default namespace before returning error
revert()
retrieved.err = err
break
}
// retrieve every interface managed by this agent
for _, link := range links {
iface := &interfaces.Interface{
Namespace: nsRef,
HostIfName: link.Attrs().Name,
PhysAddress: link.Attrs().HardwareAddr.String(),
Mtu: uint32(link.Attrs().MTU),
}
alias := link.Attrs().Alias
if !strings.HasPrefix(alias, h.agentPrefix) {
// skip interface not configured by this agent
continue
}
alias = strings.TrimPrefix(alias, h.agentPrefix)
// parse alias to obtain logical references
if link.Type() == "veth" {
iface.Type = interfaces.Interface_VETH
var vethPeerIfName string
iface.Name, vethPeerIfName = ParseVethAlias(alias)
iface.Link = &interfaces.Interface_Veth{
Veth: &interfaces.VethLink{
PeerIfName: vethPeerIfName,
},
}
} else if link.Type() == "tuntap" || link.Type() == "tun" /* not defined in vishvananda */ {
iface.Type = interfaces.Interface_TAP_TO_VPP
var vppTapIfName string
iface.Name, vppTapIfName, _ = ParseTapAlias(alias)
iface.Link = &interfaces.Interface_Tap{
Tap: &interfaces.TapLink{
VppTapIfName: vppTapIfName,
},
}
} else if link.Attrs().Name == DefaultLoopbackName {
iface.Type = interfaces.Interface_LOOPBACK
iface.Name = alias
} else {
// unsupported interface type supposedly configured by agent => print warning
h.log.WithFields(logging.Fields{
"if-host-name": link.Attrs().Name,
"namespace": nsRef,
}).Warnf("Managed interface of unsupported type: %s", link.Type())
continue
}
// skip interfaces with invalid aliases
if iface.Name == "" {
continue
}
// retrieve addresses, MTU, etc.
h.retrieveLinkDetails(link, iface, nsRef)
// build interface details
retrieved.interfaces = append(retrieved.interfaces, &InterfaceDetails{
Interface: iface,
Meta: &InterfaceMeta{
LinuxIfIndex: link.Attrs().Index,
ParentIndex: link.Attrs().ParentIndex,
MasterIndex: link.Attrs().MasterIndex,
OperState: uint8(link.Attrs().OperState),
Flags: link.Attrs().RawFlags,
Encapsulation: link.Attrs().EncapType,
NumRxQueues: link.Attrs().NumRxQueues,
NumTxQueues: link.Attrs().NumTxQueues,
TxQueueLen: link.Attrs().TxQLen,
},
})
// build interface statistics
retrieved.stats = append(retrieved.stats, &InterfaceStatistics{
Name: iface.Name,
Type: iface.Type,
LinuxIfIndex: link.Attrs().Index,
RxPackets: link.Attrs().Statistics.RxPackets,
TxPackets: link.Attrs().Statistics.TxPackets,
RxBytes: link.Attrs().Statistics.RxBytes,
TxBytes: link.Attrs().Statistics.TxBytes,
RxErrors: link.Attrs().Statistics.RxErrors,
TxErrors: link.Attrs().Statistics.TxErrors,
RxDropped: link.Attrs().Statistics.TxDropped,
TxDropped: link.Attrs().Statistics.RxDropped,
})
}
// switch back to the default namespace
revert()
}
ch <- retrieved
}
// retrieveLinkDetails retrieves link details common to all interface types (e.g. addresses).
func (h *NetLinkHandler) retrieveLinkDetails(link netlink.Link, iface *interfaces.Interface, nsRef *namespaces.NetNamespace) {
var err error
// read interface status
iface.Enabled, err = h.IsInterfaceUp(link.Attrs().Name)
if err != nil {
h.log.WithFields(logging.Fields{
"if-host-name": link.Attrs().Name,
"namespace": nsRef,
}).Warn("Failed to read interface status:", err)
}
// read assigned IP addresses
addressList, err := h.GetAddressList(link.Attrs().Name)
if err != nil {
h.log.WithFields(logging.Fields{
"if-host-name": link.Attrs().Name,
"namespace": nsRef,
}).Warn("Failed to read address list:", err)
}
for _, address := range addressList {
if address.Scope == unix.RT_SCOPE_LINK {
// ignore link-local IPv6 addresses
continue
}
mask, _ := address.Mask.Size()
addrStr := address.IP.String() + "/" + strconv.Itoa(mask)
iface.IpAddresses = append(iface.IpAddresses, addrStr)
}
// read checksum offloading
if iface.Type == interfaces.Interface_VETH {
rxOn, txOn, err := h.GetChecksumOffloading(link.Attrs().Name)
if err != nil {
h.log.WithFields(logging.Fields{
"if-host-name": link.Attrs().Name,
"namespace": nsRef,
}).Warn("Failed to read checksum offloading:", err)
} else {
if !rxOn {
iface.GetVeth().RxChecksumOffloading = interfaces.VethLink_CHKSM_OFFLOAD_DISABLED
}
if !txOn {
iface.GetVeth().TxChecksumOffloading = interfaces.VethLink_CHKSM_OFFLOAD_DISABLED
}
}
}
}