Skip to content

Commit 223358e

Browse files
authored
Merge pull request #146 from winsopc/vfio-support-upstream
VF VFIO device support
2 parents 1adb133 + fcb3156 commit 223358e

File tree

10 files changed

+213
-59
lines changed

10 files changed

+213
-59
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ echo 8 > /sys/class/net/ib0/device/sriov_numvfs
207207
* `rdmaIsolation` (boolean, optional): Enable RDMA network namespace isolation for RDMA workloads. More information
208208
about the system requirements to support this mode of operation can be found [here](https://github.com/Mellanox/rdma-cni)
209209
* `ibKubernetesEnabled` (bool, optional): Enforces ib-sriov-cni to work with [ib-kubernetes](https://www.github.com/Mellanox/ib-kubernetes).
210+
* `vfioPciMode` (boolean, optional): Enable VFIO mode for VF devices bound to vfio-pci driver. When enabled, the CNI skips network interface configuration as VFIO devices are used for direct device assignment (e.g., for kubevirt/VM workloads). Defaults to false. If not explicitly set, the mode is auto-detected based on the VF's driver binding.
210211

211212
> *__Note__*: If `rdmaIsolation` is set to _true_, [`rdma-cni`](https://github.com/Mellanox/rdma-cni) should not be used.
212213

cmd/ib-sriov-cni/main.go

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,30 @@ func unlockCNIExecution(lock *flock.Flock) {
9595
_ = lock.Unlock()
9696
}
9797

98+
func handleVfioPciDetection(netConf *localtypes.NetConf) error {
99+
if netConf.DeviceID == "" {
100+
return fmt.Errorf("device ID is required for VFIO PCI detection")
101+
}
102+
103+
isVfioPci, err := utils.IsVfioPciDevice(netConf.DeviceID)
104+
if err != nil {
105+
return fmt.Errorf("failed to check vfio-pci driver binding for device %s: %v", netConf.DeviceID, err)
106+
}
107+
108+
// If vfioPciMode is explicitly set to true, validate the device is actually bound to vfio-pci
109+
if netConf.VfioPciMode {
110+
if !isVfioPci {
111+
return fmt.Errorf("vfioPciMode is enabled but device %s is not bound to vfio-pci driver", netConf.DeviceID)
112+
}
113+
} else {
114+
// Auto-detect: if device is bound to vfio-pci, enable vfioPciMode
115+
if isVfioPci {
116+
netConf.VfioPciMode = true
117+
}
118+
}
119+
return nil
120+
}
121+
98122
// Get network config, updated with GUID, device info and network namespace.
99123
func getNetConfNetns(args *skel.CmdArgs) (*localtypes.NetConf, ns.NetNS, error) {
100124
netConf, err := config.LoadConf(args.StdinData)
@@ -116,13 +140,18 @@ func getNetConfNetns(args *skel.CmdArgs) (*localtypes.NetConf, ns.NetNS, error)
116140
"infiniband SRIOV-CNI failed, Unexpected error. GUID must be provided by ib-kubernetes")
117141
}
118142

119-
if netConf.RdmaIso {
143+
if netConf.RdmaIsolation {
120144
err = utils.EnsureRdmaSystemMode()
121145
if err != nil {
122146
return nil, nil, err
123147
}
124148
}
125149

150+
// Handle vfio-pci detection
151+
if err := handleVfioPciDetection(netConf); err != nil {
152+
return nil, nil, err
153+
}
154+
126155
err = config.LoadDeviceInfo(netConf)
127156
if err != nil {
128157
return nil, nil, fmt.Errorf("failed to get device specific information. %v", err)
@@ -135,18 +164,23 @@ func getNetConfNetns(args *skel.CmdArgs) (*localtypes.NetConf, ns.NetNS, error)
135164
return netConf, netns, nil
136165
}
137166

138-
// Applies VF config and performs VF setup. if RdmaIso is configured, moves RDMA device into namespace
167+
// Applies VF config and performs VF setup. if RdmaIsolation is configured, moves RDMA device into namespace
139168
func doVFConfig(sm localtypes.Manager, netConf *localtypes.NetConf, netns ns.NetNS, args *skel.CmdArgs) (retErr error) {
140169
err := sm.ApplyVFConfig(netConf)
141170
if err != nil {
142171
return fmt.Errorf("infiniBand SRI-OV CNI failed to configure VF %q", err)
143172
}
144173

174+
// VFIO devices don't have network interfaces, skip SetupVF
175+
if netConf.VfioPciMode {
176+
return nil
177+
}
178+
145179
// Note(adrianc): We do this here as ApplyVFCOnfig is rebinding the VF, causing the RDMA device to be recreated.
146180
// We do this here due to some un-intuitive kernel behavior (which i hope will change), moving an RDMA device
147181
// to namespace causes all of its associated ULP devices (IPoIB) to be recreated in the default namespace,
148182
// hence SetupVF needs to occur after moving RDMA device to namespace
149-
if netConf.RdmaIso {
183+
if netConf.RdmaIsolation {
150184
var rdmaDev string
151185
rdmaDev, err = utils.MoveRdmaDevToNsPci(netConf.DeviceID, netns)
152186
if err != nil {
@@ -244,7 +278,7 @@ func cmdAdd(args *skel.CmdArgs) (retErr error) {
244278
if nsErr == nil {
245279
_ = sm.ReleaseVF(netConf, args.IfName, args.ContainerID, netns)
246280
}
247-
if netConf.RdmaIso {
281+
if netConf.RdmaIsolation {
248282
_ = utils.MoveRdmaDevFromNs(netConf.RdmaNetState.ContainerRdmaDevName, netns)
249283
}
250284
}
@@ -256,7 +290,8 @@ func cmdAdd(args *skel.CmdArgs) (retErr error) {
256290
Sandbox: netns.Path(),
257291
}}
258292

259-
if netConf.IPAM.Type != "" {
293+
// VFIO devices don't have network interfaces, skip IPAM configuration
294+
if netConf.IPAM.Type != "" && !netConf.VfioPciMode {
260295
var newResult *current.Result
261296
newResult, err = runIPAMPlugin(args.StdinData, netConf)
262297
if err != nil {
@@ -294,6 +329,17 @@ func cmdAdd(args *skel.CmdArgs) (retErr error) {
294329
return types.PrintResult(result, netConf.CNIVersion)
295330
}
296331

332+
func handleIPAMCleanup(netConf *localtypes.NetConf, stdinData []byte) error {
333+
// VFIO devices don't use IPAM
334+
if netConf.VfioPciMode {
335+
return nil
336+
}
337+
if netConf.IPAM.Type == ipamDHCP {
338+
return fmt.Errorf("ipam type dhcp is not supported")
339+
}
340+
return ipam.ExecDel(netConf.IPAM.Type, stdinData)
341+
}
342+
297343
func cmdDel(args *skel.CmdArgs) (retErr error) {
298344
// https://github.com/kubernetes/kubernetes/pull/35240
299345
if args.Netns == "" {
@@ -320,10 +366,7 @@ func cmdDel(args *skel.CmdArgs) (retErr error) {
320366
sm := sriov.NewSriovManager()
321367

322368
if netConf.IPAM.Type != "" {
323-
if netConf.IPAM.Type == ipamDHCP {
324-
return fmt.Errorf("ipam type dhcp is not supported")
325-
}
326-
err = ipam.ExecDel(netConf.IPAM.Type, args.StdinData)
369+
err = handleIPAMCleanup(netConf, args.StdinData)
327370
if err != nil {
328371
return err
329372
}
@@ -352,9 +395,12 @@ func cmdDel(args *skel.CmdArgs) (retErr error) {
352395
}
353396
defer unlockCNIExecution(lock)
354397

355-
err = sm.ReleaseVF(netConf, args.IfName, args.ContainerID, netns)
356-
if err != nil {
357-
return err
398+
// VFIO devices don't have network interfaces to release
399+
if !netConf.VfioPciMode {
400+
err = sm.ReleaseVF(netConf, args.IfName, args.ContainerID, netns)
401+
if err != nil {
402+
return err
403+
}
358404
}
359405

360406
// Move RDMA device to default namespace
@@ -364,7 +410,7 @@ func cmdDel(args *skel.CmdArgs) (retErr error) {
364410
// 1. netedv cleanup during ReleaseVF.
365411
// 2. rdma dev netns cleanup as ResetVFConfig will rebind the VF.
366412
// Doing anything would have yielded the same results however ResetVFConfig will eventually not trigger VF rebind.
367-
if netConf.RdmaIso {
413+
if netConf.RdmaIsolation {
368414
err = utils.MoveRdmaDevFromNs(netConf.RdmaNetState.ContainerRdmaDevName, netns)
369415
if err != nil {
370416
return fmt.Errorf(

pkg/config/config.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ func LoadDeviceInfo(netConf *types.NetConf) error {
5050
return fmt.Errorf("load config: vf pci addr is required")
5151
}
5252

53+
// VFIO devices don't have network interfaces, skip getting interface name
54+
if netConf.VfioPciMode {
55+
netConf.HostIFNames = ""
56+
return nil
57+
}
58+
5359
// Get interface name
5460
hostIFNames, err := utils.GetVFLinkNames(netConf.DeviceID)
5561
if err != nil || hostIFNames == "" {

pkg/sriov/sriov.go

Lines changed: 54 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,45 @@ func (s *sriovManager) ReleaseVF(conf *types.NetConf, podifName, cid string, net
222222
})
223223
}
224224

225+
// applyVFGuid handles VF GUID configuration and validation for both VFIO and regular VFs
226+
func (s *sriovManager) applyVFGuid(conf *types.NetConf, pfLink netlink.Link) error {
227+
if conf.GUID != "" {
228+
if !utils.IsValidGUID(conf.GUID) {
229+
return fmt.Errorf("invalid guid %s", conf.GUID)
230+
}
231+
232+
// For VFIO VF devices, we can't read current GUID from VF interface
233+
if conf.VfioPciMode {
234+
// Save all-F GUID to reset to during deletion
235+
conf.HostIFGUID = utils.DefaultGUID
236+
} else {
237+
// Regular VF: save current GUID from VF link
238+
vfLink, err := s.nLink.LinkByName(conf.HostIFNames)
239+
if err != nil {
240+
return fmt.Errorf("failed to lookup vf %q: %v", conf.HostIFNames, err)
241+
}
242+
conf.HostIFGUID = vfLink.Attrs().HardwareAddr.String()[36:]
243+
}
244+
245+
// Set link guid
246+
if err := s.setVfGUID(conf, pfLink, conf.GUID); err != nil {
247+
return err
248+
}
249+
} else if !conf.VfioPciMode {
250+
// Verify VF have valid GUID (skip for VFIO as we can't access VF interface)
251+
vfLink, err := s.nLink.LinkByName(conf.HostIFNames)
252+
if err != nil {
253+
return fmt.Errorf("failed to lookup vf %q: %v", conf.HostIFNames, err)
254+
}
255+
256+
guid := utils.GetGUIDFromHwAddr(vfLink.Attrs().HardwareAddr)
257+
if guid == "" || utils.IsAllZeroGUID(guid) || utils.IsAllOnesGUID(guid) {
258+
return fmt.Errorf("VF %s GUID is not valid", conf.HostIFNames)
259+
}
260+
}
261+
return nil
262+
}
263+
225264
// ApplyVFConfig configure a VF with parameters given in NetConf
226265
func (s *sriovManager) ApplyVFConfig(conf *types.NetConf) error {
227266
pfLink, err := s.nLink.LinkByName(conf.Master)
@@ -248,37 +287,8 @@ func (s *sriovManager) ApplyVFConfig(conf *types.NetConf) error {
248287
}
249288
}
250289

251-
// Set link guid
252-
if conf.GUID != "" {
253-
if !utils.IsValidGUID(conf.GUID) {
254-
return fmt.Errorf("invalid guid %s", conf.GUID)
255-
}
256-
// save link guid
257-
vfLink, err := s.nLink.LinkByName(conf.HostIFNames)
258-
if err != nil {
259-
return fmt.Errorf("failed to lookup vf %q: %v", conf.HostIFNames, err)
260-
}
261-
262-
conf.HostIFGUID = vfLink.Attrs().HardwareAddr.String()[36:]
263-
264-
// Set link guid
265-
if err := s.setVfGUID(conf, pfLink, conf.GUID); err != nil {
266-
return err
267-
}
268-
} else {
269-
// Verify VF have valid GUID.
270-
vfLink, err := s.nLink.LinkByName(conf.HostIFNames)
271-
if err != nil {
272-
return fmt.Errorf("failed to lookup vf %q: %v", conf.HostIFNames, err)
273-
}
274-
275-
guid := utils.GetGUIDFromHwAddr(vfLink.Attrs().HardwareAddr)
276-
if guid == "" || utils.IsAllZeroGUID(guid) || utils.IsAllOnesGUID(guid) {
277-
return fmt.Errorf("VF %s GUID is not valid", conf.HostIFNames)
278-
}
279-
}
280-
281-
return nil
290+
// Handle VF GUID configuration
291+
return s.applyVFGuid(conf, pfLink)
282292
}
283293

284294
// restoreVFName restores VF name from conf
@@ -329,15 +339,18 @@ func (s *sriovManager) ResetVFConfig(conf *types.NetConf) error {
329339
// This happen when create a VF it guid is all zeros
330340
if conf.HostIFGUID != "" {
331341
if utils.IsAllZeroGUID(conf.HostIFGUID) {
332-
conf.HostIFGUID = "FF:FF:FF:FF:FF:FF:FF:FF"
342+
conf.HostIFGUID = utils.DefaultGUID
333343
}
334344

335345
if err := s.setVfGUID(conf, pfLink, conf.HostIFGUID); err != nil {
336346
return err
337347
}
338348
// setVfGUID cause VF to rebind, which change its name. Lets restore it.
349+
// For VFIO devices, skip VF name restoration since no rebind occurs
339350
// Once setVfGUID wouldn't do rebind to apply GUID this function should be removed
340-
return s.restoreVFName(conf)
351+
if !conf.VfioPciMode {
352+
return s.restoreVFName(conf)
353+
}
341354
}
342355

343356
return nil
@@ -356,10 +369,14 @@ func (s *sriovManager) setVfGUID(conf *types.NetConf, pfLink netlink.Link, guidA
356369
if err != nil {
357370
return fmt.Errorf("failed to add port guid %s: %v", guid, err)
358371
}
359-
// unbind vf then bind it to apply the guid
360-
err = s.utils.RebindVf(conf.Master, conf.DeviceID)
361-
if err != nil {
362-
return err
372+
// For VFIO devices, skip rebind as the device is bound to vfio-pci driver
373+
// and doesn't have a network interface that can be unbound/rebound
374+
if !conf.VfioPciMode {
375+
// unbind vf then bind it to apply the guid
376+
err = s.utils.RebindVf(conf.Master, conf.DeviceID)
377+
if err != nil {
378+
return err
379+
}
363380
}
364381
return nil
365382
}

pkg/sriov/sriov_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,28 @@ var _ = Describe("Sriov", func() {
293293
Expect(err.Error()).To(Equal("mocked failed"))
294294
Expect(netconf.HostIFGUID).To(Equal(hostGUID))
295295
})
296+
It("ApplyVFConfig with valid GUID and VfioPciMode VF (no network interface) - should return success after setting GUID", func() {
297+
mockedNetLinkManger := &mocks.NetlinkManager{}
298+
mockedPciUtils := &mocks.PciUtils{}
299+
300+
fakeLink := &FakeLink{netlink.LinkAttrs{}}
301+
netconf.GUID = "01:23:45:67:89:ab:cd:ef"
302+
netconf.VfioPciMode = true
303+
netconf.HostIFNames = "" // VFIO VF has no network interface
304+
305+
// Only PF link is needed for VFIO VF
306+
mockedNetLinkManger.On("LinkByName", netconf.Master).Return(fakeLink, nil)
307+
mockedNetLinkManger.On("LinkSetVfNodeGUID", fakeLink, mock.AnythingOfType("int"), mock.Anything).Return(nil)
308+
mockedNetLinkManger.On("LinkSetVfPortGUID", fakeLink, mock.AnythingOfType("int"), mock.Anything).Return(nil)
309+
310+
mockedPciUtils.On("RebindVf", mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(nil)
311+
312+
sm := sriovManager{nLink: mockedNetLinkManger, utils: mockedPciUtils}
313+
err := sm.ApplyVFConfig(netconf)
314+
Expect(err).NotTo(HaveOccurred())
315+
// For VFIO VF, HostIFGUID should be set to all-F for reset during deletion
316+
Expect(netconf.HostIFGUID).To(Equal("FF:FF:FF:FF:FF:FF:FF:FF"))
317+
})
296318
})
297319
Context("Checking SetupVF function", func() {
298320
var (

pkg/types/types.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ type IbSriovNetConf struct {
2828
GUID string `json:"-"` // Taken from either CNI_ARGS "guid" attribute or from RuntimeConfig
2929
PKey string `json:"pkey"`
3030
LinkState string `json:"link_state,omitempty"` // auto|enable|disable
31-
RdmaIso bool `json:"rdmaIsolation,omitempty"`
31+
RdmaIsolation bool `json:"rdmaIsolation,omitempty"`
3232
IBKubernetesEnabled bool `json:"ibKubernetesEnabled,omitempty"`
33+
VfioPciMode bool `json:"vfioPciMode,omitempty"` // Skip SR-IOV network setup, default false
3334
RdmaNetState rdmatypes.RdmaNetState
3435
RuntimeConfig RuntimeConf `json:"runtimeConfig,omitempty"`
3536
Args struct {

pkg/types/types_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ var _ = Describe("Types", func() {
5050
ContIFNames: "net1",
5151
PKey: "0x8001",
5252
LinkState: "enable",
53-
RdmaIso: true,
53+
RdmaIsolation: true,
5454
IBKubernetesEnabled: false,
5555
RdmaNetState: rdmatypes.RdmaNetState{
5656
Version: "1.0",

0 commit comments

Comments
 (0)