-
Notifications
You must be signed in to change notification settings - Fork 27
/
Copy pathsriovnet_switchdev.go
515 lines (456 loc) · 16.5 KB
/
sriovnet_switchdev.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
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
/*
Copyright 2023 NVIDIA CORPORATION & 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 sriovnet
import (
"bytes"
"errors"
"fmt"
"net"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
utilfs "github.com/k8snetworkplumbingwg/sriovnet/pkg/utils/filesystem"
"github.com/k8snetworkplumbingwg/sriovnet/pkg/utils/netlinkops"
)
const (
netdevPhysSwitchID = "phys_switch_id"
netdevPhysPortName = "phys_port_name"
)
type PortFlavour uint16
// Keep things consistent with netlink lib constants
// nolint:revive,stylecheck
const (
PORT_FLAVOUR_PHYSICAL = iota
PORT_FLAVOUR_CPU
PORT_FLAVOUR_DSA
PORT_FLAVOUR_PCI_PF
PORT_FLAVOUR_PCI_VF
PORT_FLAVOUR_VIRTUAL
PORT_FLAVOUR_UNUSED
PORT_FLAVOUR_PCI_SF
PORT_FLAVOUR_UNKNOWN = 0xffff
)
// Regex that matches on the physical/upling port name
var physPortRepRegex = regexp.MustCompile(`^p(\d+)$`)
// Regex that matches on PF representor port name. These ports exists on DPUs.
var pfPortRepRegex = regexp.MustCompile(`^(?:c\d+)?pf(\d+)$`)
// Regex that matches on VF representor port name
var vfPortRepRegex = regexp.MustCompile(`^(?:c\d+)?pf(\d+)vf(\d+)$`)
// Regex that matches on SF representor port name
var sfPortRepRegex = regexp.MustCompile(`^(?:c\d+)?pf(\d+)sf(\d+)$`)
func parseIndexFromPhysPortName(portName string, regex *regexp.Regexp) (pfRepIndex, vfRepIndex int, err error) {
pfRepIndex = -1
vfRepIndex = -1
matches := regex.FindStringSubmatch(portName)
//nolint:gomnd
if len(matches) != 3 {
err = fmt.Errorf("failed to parse portName %s", portName)
} else {
pfRepIndex, err = strconv.Atoi(matches[1])
if err == nil {
vfRepIndex, err = strconv.Atoi(matches[2])
}
}
return pfRepIndex, vfRepIndex, err
}
func parsePortName(physPortName string) (pfRepIndex, vfRepIndex int, err error) {
// old kernel syntax of phys_port_name is vf index
physPortName = strings.TrimSpace(physPortName)
physPortNameInt, err := strconv.Atoi(physPortName)
if err == nil {
vfRepIndex = physPortNameInt
} else {
pfRepIndex, vfRepIndex, err = parseIndexFromPhysPortName(physPortName, vfPortRepRegex)
}
return pfRepIndex, vfRepIndex, err
}
func sfIndexFromPortName(physPortName string) (int, error) {
//nolint:gomnd
_, sfRepIndex, err := parseIndexFromPhysPortName(physPortName, sfPortRepRegex)
return sfRepIndex, err
}
func isSwitchdev(netdevice string) bool {
swIDFile := filepath.Join(NetSysDir, netdevice, netdevPhysSwitchID)
physSwitchID, err := utilfs.Fs.ReadFile(swIDFile)
if err != nil {
return false
}
if len(physSwitchID) != 0 {
return true
}
return false
}
// GetUplinkRepresentor gets a VF or PF PCI address (e.g '0000:03:00.4') and
// returns the uplink represntor netdev name for that VF or PF.
func GetUplinkRepresentor(pciAddress string) (string, error) {
devicePath := filepath.Join(PciSysDir, pciAddress, "physfn", "net")
if _, err := utilfs.Fs.Stat(devicePath); errors.Is(err, os.ErrNotExist) {
// If physfn symlink to the parent PF doesn't exist, use the current device's dir
devicePath = filepath.Join(PciSysDir, pciAddress, "net")
}
devices, err := utilfs.Fs.ReadDir(devicePath)
if err != nil {
return "", fmt.Errorf("failed to lookup %s: %v", pciAddress, err)
}
for _, device := range devices {
if isSwitchdev(device.Name()) {
// Try to get the phys port name, if not exists then fallback to check without it
// phys_port_name should be in formant p<port-num> e.g p0,p1,p2 ...etc.
if devicePhysPortName, err := getNetDevPhysPortName(device.Name()); err == nil {
if !physPortRepRegex.MatchString(devicePhysPortName) {
continue
}
}
return device.Name(), nil
}
}
return "", fmt.Errorf("uplink for %s not found", pciAddress)
}
func GetVfRepresentor(uplink string, vfIndex int) (string, error) {
swIDFile := filepath.Join(NetSysDir, uplink, netdevPhysSwitchID)
physSwitchID, err := utilfs.Fs.ReadFile(swIDFile)
if err != nil || len(physSwitchID) == 0 {
return "", fmt.Errorf("cant get uplink %s switch id", uplink)
}
pfSubsystemPath := filepath.Join(NetSysDir, uplink, "subsystem")
devices, err := utilfs.Fs.ReadDir(pfSubsystemPath)
if err != nil {
return "", err
}
for _, device := range devices {
devicePath := filepath.Join(NetSysDir, device.Name())
deviceSwIDFile := filepath.Join(devicePath, netdevPhysSwitchID)
deviceSwID, err := utilfs.Fs.ReadFile(deviceSwIDFile)
if err != nil || !bytes.Equal(deviceSwID, physSwitchID) {
continue
}
physPortNameStr, err := getNetDevPhysPortName(device.Name())
if err != nil {
continue
}
pfRepIndex, vfRepIndex, _ := parsePortName(physPortNameStr)
if pfRepIndex != -1 {
pfPCIAddress, err := getPCIFromDeviceName(uplink)
if err != nil {
continue
}
PCIFuncAddress, err := strconv.Atoi(string((pfPCIAddress[len(pfPCIAddress)-1])))
if pfRepIndex != PCIFuncAddress || err != nil {
continue
}
}
// At this point we're confident we have a representor.
if vfRepIndex == vfIndex {
return device.Name(), nil
}
}
return "", fmt.Errorf("failed to find VF representor for uplink %s", uplink)
}
func GetSfRepresentor(uplink string, sfNum int) (string, error) {
pfNetPath := filepath.Join(NetSysDir, uplink, "device", "net")
devices, err := utilfs.Fs.ReadDir(pfNetPath)
if err != nil {
return "", err
}
for _, device := range devices {
physPortNameStr, err := getNetDevPhysPortName(device.Name())
if err != nil {
continue
}
sfRepIndex, err := sfIndexFromPortName(physPortNameStr)
if err != nil {
continue
}
if sfRepIndex == sfNum {
return device.Name(), nil
}
}
return "", fmt.Errorf("failed to find SF representor for uplink %s", uplink)
}
func getNetDevPhysPortName(netDev string) (string, error) {
devicePortNameFile := filepath.Join(NetSysDir, netDev, netdevPhysPortName)
physPortName, err := utilfs.Fs.ReadFile(devicePortNameFile)
if err != nil {
return "", err
}
return strings.TrimSpace(string(physPortName)), nil
}
// findNetdevWithPortNameCriteria returns representor netdev that matches a criteria function on the
// physical port name
func findNetdevWithPortNameCriteria(criteria func(string) bool) (string, error) {
netdevs, err := utilfs.Fs.ReadDir(NetSysDir)
if err != nil {
return "", err
}
for _, netdev := range netdevs {
// find matching VF representor
netdevName := netdev.Name()
// skip non switchdev netdevs
if !isSwitchdev(netdevName) {
continue
}
portName, err := getNetDevPhysPortName(netdevName)
if err != nil {
continue
}
if criteria(portName) {
return netdevName, nil
}
}
return "", fmt.Errorf("no representor matched criteria")
}
// GetPortIndexFromRepresentor finds the index of a representor from its network device name.
// Supports VF and SF. For multiple port flavors, the same ID could be returned, i.e.
//
// pf0vf10 and pf0sf10
//
// will return the same port ID. To further differentiate the ports, use GetRepresentorPortFlavour
func GetPortIndexFromRepresentor(repNetDev string) (int, error) {
flavor, err := GetRepresentorPortFlavour(repNetDev)
if err != nil {
return 0, err
}
if flavor != PORT_FLAVOUR_PCI_VF && flavor != PORT_FLAVOUR_PCI_SF {
return 0, fmt.Errorf("unsupported port flavor for netdev %s", repNetDev)
}
physPortName, err := getNetDevPhysPortName(repNetDev)
if err != nil {
return 0, fmt.Errorf("failed to get device %s physical port name: %v", repNetDev, err)
}
typeToRegex := map[PortFlavour]*regexp.Regexp{
PORT_FLAVOUR_PCI_VF: vfPortRepRegex,
PORT_FLAVOUR_PCI_SF: sfPortRepRegex,
}
_, repIndex, err := parseIndexFromPhysPortName(physPortName, typeToRegex[flavor])
if err != nil {
return 0, fmt.Errorf("failed to parse the physical port name of device %s: %v", repNetDev, err)
}
return repIndex, nil
}
// GetVfRepresentorDPU returns VF representor on DPU for a host VF identified by pfID and vfIndex
func GetVfRepresentorDPU(pfID, vfIndex string) (string, error) {
// TODO(Adrianc): This method should change to get switchID and vfIndex as input, then common logic can
// be shared with GetVfRepresentor, backward compatibility should be preserved when this happens.
// pfID should be 0 or 1
if pfID != "0" && pfID != "1" {
return "", fmt.Errorf("unexpected pfID(%s). It should be 0 or 1", pfID)
}
// vfIndex should be an unsinged integer provided as a decimal number
if _, err := strconv.ParseUint(vfIndex, 10, 32); err != nil {
return "", fmt.Errorf("unexpected vfIndex(%s). It should be an unsigned decimal number", vfIndex)
}
// map for easy search of expected VF rep port name.
// Note: no support for Multi-Chassis DPUs
expectedPhysPortNames := map[string]interface{}{
fmt.Sprintf("pf%svf%s", pfID, vfIndex): nil,
fmt.Sprintf("c1pf%svf%s", pfID, vfIndex): nil,
}
netdev, err := findNetdevWithPortNameCriteria(func(portName string) bool {
// if phys port name == pf<pfIndex>vf<vfIndex> or c1pf<pfIndex>vf<vfIndex> we have a match
if _, ok := expectedPhysPortNames[portName]; ok {
return true
}
return false
})
if err != nil {
return "", fmt.Errorf("vf representor for pfID:%s, vfIndex:%s not found", pfID, vfIndex)
}
return netdev, nil
}
// GetSfRepresentorDPU returns SF representor on DPU for a host SF identified by pfID and sfIndex
func GetSfRepresentorDPU(pfID, sfIndex string) (string, error) {
// pfID should be 0 or 1
if pfID != "0" && pfID != "1" {
return "", fmt.Errorf("unexpected pfID(%s). It should be 0 or 1", pfID)
}
// sfIndex should be an unsinged integer provided as a decimal number
if _, err := strconv.ParseUint(sfIndex, 10, 32); err != nil {
return "", fmt.Errorf("unexpected sfIndex(%s). It should be an unsigned decimal number", sfIndex)
}
// map for easy search of expected VF rep port name.
// Note: no support for Multi-Chassis DPUs
expectedPhysPortNames := map[string]interface{}{
fmt.Sprintf("pf%ssf%s", pfID, sfIndex): nil,
fmt.Sprintf("c1pf%ssf%s", pfID, sfIndex): nil,
}
netdev, err := findNetdevWithPortNameCriteria(func(portName string) bool {
// if phys port name == pf<pfIndex>sf<sfIndex> or c1pf<pfIndex>sf<sfIndex> we have a match
if _, ok := expectedPhysPortNames[portName]; ok {
return true
}
return false
})
if err != nil {
return "", fmt.Errorf("sf representor for pfID:%s, sfIndex:%s not found", pfID, sfIndex)
}
return netdev, nil
}
// GetRepresentorPortFlavour returns the representor port flavour
// Note: this method does not support old representor names used by old kernels
// e.g <vf_num> and will return PORT_FLAVOUR_UNKNOWN for such cases.
func GetRepresentorPortFlavour(netdev string) (PortFlavour, error) {
if !isSwitchdev(netdev) {
return PORT_FLAVOUR_UNKNOWN, fmt.Errorf("net device %s is does not represent an eswitch port", netdev)
}
// Attempt to get information via devlink (Kernel >= 5.9.0)
port, err := netlinkops.GetNetlinkOps().DevLinkGetPortByNetdevName(netdev)
if err == nil {
return PortFlavour(port.PortFlavour), nil
}
// Fallback to Get PortFlavour by phys_port_name
// read phy_port_name
portName, err := getNetDevPhysPortName(netdev)
if err != nil {
return PORT_FLAVOUR_UNKNOWN, err
}
typeToRegex := map[PortFlavour]*regexp.Regexp{
PORT_FLAVOUR_PHYSICAL: physPortRepRegex,
PORT_FLAVOUR_PCI_PF: pfPortRepRegex,
PORT_FLAVOUR_PCI_VF: vfPortRepRegex,
PORT_FLAVOUR_PCI_SF: sfPortRepRegex,
}
for flavour, regex := range typeToRegex {
if regex.MatchString(portName) {
return flavour, nil
}
}
return PORT_FLAVOUR_UNKNOWN, nil
}
// parseDPUConfigFileOutput parses the config file content of a DPU
// representor port. The format of the file is a set of <key>:<value> pairs as follows:
//
// ```
//
// MAC : 0c:42:a1:c6:cf:7c
// MaxTxRate : 0
// State : Follow
//
// ```
func parseDPUConfigFileOutput(out string) map[string]string {
configMap := make(map[string]string)
for _, line := range strings.Split(strings.TrimSuffix(out, "\n"), "\n") {
entry := strings.SplitN(line, ":", 2)
if len(entry) != 2 {
// unexpected line format
continue
}
configMap[strings.Trim(entry[0], " \t\n")] = strings.Trim(entry[1], " \t\n")
}
return configMap
}
// GetRepresentorPeerMacAddress returns the MAC address of the peer netdev associated with the given
// representor netdev
// Note:
//
// This method functionality is currently supported only on DPUs.
// Currently only netdev representors with PORT_FLAVOUR_PCI_PF are supported
func GetRepresentorPeerMacAddress(netdev string) (net.HardwareAddr, error) {
flavor, err := GetRepresentorPortFlavour(netdev)
if err != nil {
return nil, fmt.Errorf("unknown port flavour for netdev %s. %v", netdev, err)
}
if flavor == PORT_FLAVOUR_UNKNOWN {
return nil, fmt.Errorf("unknown port flavour for netdev %s", netdev)
}
if flavor != PORT_FLAVOUR_PCI_PF {
return nil, fmt.Errorf("unsupported port flavour for netdev %s", netdev)
}
// Attempt to get information via devlink (Kernel >= 5.9.0)
port, err := netlinkops.GetNetlinkOps().DevLinkGetPortByNetdevName(netdev)
if err == nil {
if port.Fn != nil {
return port.Fn.HwAddr, nil
}
}
// Get information via sysfs
// read phy_port_name
portName, err := getNetDevPhysPortName(netdev)
if err != nil {
return nil, err
}
// Extract port num
portNum := pfPortRepRegex.FindStringSubmatch(portName)
if len(portNum) < 2 {
return nil, fmt.Errorf("failed to extract physical port number from port name %s of netdev %s",
portName, netdev)
}
uplinkPhysPortName := "p" + portNum[1]
// Find uplink netdev for that port
// Note(adrianc): As we support only DPUs ATM we do not need to deal with netdevs from different
// eswitch (i.e different switch IDs).
uplinkNetdev, err := findNetdevWithPortNameCriteria(func(pname string) bool { return pname == uplinkPhysPortName })
if err != nil {
return nil, fmt.Errorf("failed to find uplink port for netdev %s. %v", netdev, err)
}
// get MAC address for netdev
configPath := filepath.Join(NetSysDir, uplinkNetdev, "smart_nic", "pf", "config")
out, err := utilfs.Fs.ReadFile(configPath)
if err != nil {
return nil, fmt.Errorf("failed to read DPU config via uplink %s for %s. %v",
uplinkNetdev, netdev, err)
}
config := parseDPUConfigFileOutput(string(out))
macStr, ok := config["MAC"]
if !ok {
return nil, fmt.Errorf("MAC address not found for %s", netdev)
}
mac, err := net.ParseMAC(macStr)
if err != nil {
return nil, fmt.Errorf("failed to parse MAC address \"%s\" for %s. %v", macStr, netdev, err)
}
return mac, nil
}
// SetRepresentorPeerMacAddress sets the given MAC addresss of the peer netdev associated with the given
// representor netdev.
// Note: This method functionality is currently supported only for DPUs.
// Currently only netdev representors with PORT_FLAVOUR_PCI_VF are supported
func SetRepresentorPeerMacAddress(netdev string, mac net.HardwareAddr) error {
flavor, err := GetRepresentorPortFlavour(netdev)
if err != nil {
return fmt.Errorf("unknown port flavour for netdev %s. %v", netdev, err)
}
if flavor == PORT_FLAVOUR_UNKNOWN {
return fmt.Errorf("unknown port flavour for netdev %s", netdev)
}
if flavor != PORT_FLAVOUR_PCI_VF {
return fmt.Errorf("unsupported port flavour for netdev %s", netdev)
}
physPortNameStr, err := getNetDevPhysPortName(netdev)
if err != nil {
return fmt.Errorf("failed to get phys_port_name for netdev %s: %v", netdev, err)
}
pfID, vfIndex, err := parsePortName(physPortNameStr)
if err != nil {
return fmt.Errorf("failed to get the pf and vf index for netdev %s "+
"with phys_port_name %s: %v", netdev, physPortNameStr, err)
}
uplinkPhysPortName := fmt.Sprintf("p%d", pfID)
uplinkNetdev, err := findNetdevWithPortNameCriteria(func(pname string) bool { return pname == uplinkPhysPortName })
if err != nil {
return fmt.Errorf("failed to find netdev for physical port name %s. %v", uplinkPhysPortName, err)
}
vfRepName := fmt.Sprintf("vf%d", vfIndex)
sysfsVfRepMacFile := filepath.Join(NetSysDir, uplinkNetdev, "smart_nic", vfRepName, "mac")
_, err = utilfs.Fs.Stat(sysfsVfRepMacFile)
if err != nil {
return fmt.Errorf("couldn't stat VF representor's sysfs file %s: %v", sysfsVfRepMacFile, err)
}
err = utilfs.Fs.WriteFile(sysfsVfRepMacFile, []byte(mac.String()), 0)
if err != nil {
return fmt.Errorf("failed to write the MAC address %s to VF reprentor %s",
mac.String(), sysfsVfRepMacFile)
}
return nil
}