Skip to content

Commit d1f1d32

Browse files
committed
fix ebpf detach
Change-Id: Ic2cbf3ce0a4f40932268050db7f1d3ff3053429f
1 parent 8302979 commit d1f1d32

File tree

5 files changed

+217
-94
lines changed

5 files changed

+217
-94
lines changed

install.yaml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,7 @@ spec:
129129
cpu: "100m"
130130
memory: "50Mi"
131131
securityContext:
132-
capabilities:
133-
add: ["NET_ADMIN", "SYS_ADMIN"]
132+
privileged: true
134133
readinessProbe:
135134
httpGet:
136135
path: /healthz
@@ -148,6 +147,9 @@ spec:
148147
- name: infiniband
149148
mountPath: /dev/infiniband
150149
mountPropagation: HostToContainer
150+
- name: bpf-programs
151+
mountPath: /sys/fs/bpf
152+
mountPropagation: HostToContainer
151153
volumes:
152154
- name: device-plugin
153155
hostPath:
@@ -167,4 +169,7 @@ spec:
167169
- name: etc
168170
hostPath:
169171
path: /etc
172+
- name: bpf-programs
173+
hostPath:
174+
path: /sys/fs/bpf
170175
---

pkg/driver/dra_hooks.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,17 @@ func (np *NetworkDriver) prepareResourceClaim(ctx context.Context, claim *resour
278278
}
279279
}
280280

281+
// Remove the pinned programs before the NRI hooks since it
282+
// has to walk the entire bpf virtual filesystem and is slow
283+
// TODO: check if there is some other way to do this
284+
if podCfg.Network.Interface.DisableEBPFPrograms != nil &&
285+
*podCfg.Network.Interface.DisableEBPFPrograms {
286+
err := unpinBPFPrograms(ifName)
287+
if err != nil {
288+
klog.Infof("error unpinning ebpf programs for %s : %v", ifName, err)
289+
}
290+
}
291+
281292
device := kubeletplugin.Device{
282293
Requests: []string{result.Request},
283294
PoolName: result.Pool,

pkg/driver/ebpf.go

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
/*
2+
Copyright 2025 Google LLC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package driver
18+
19+
import (
20+
"errors"
21+
"fmt"
22+
"os"
23+
"path/filepath"
24+
"runtime"
25+
26+
"github.com/cilium/ebpf"
27+
"github.com/cilium/ebpf/link"
28+
"github.com/vishvananda/netlink"
29+
"github.com/vishvananda/netns"
30+
"k8s.io/klog/v2"
31+
)
32+
33+
// unpinBPFPrograms runs in the host namespace to delete all the pinned bpf programs
34+
func unpinBPFPrograms(ifName string) error {
35+
device, err := netlink.LinkByName(ifName)
36+
if err != nil && !errors.Is(err, netlink.ErrDumpInterrupted) {
37+
return err
38+
}
39+
ifIndex := uint32(device.Attrs().Index)
40+
41+
klog.V(2).Infof("Attempting to unpin eBPF programs from interface %s", ifName)
42+
return filepath.Walk("/sys/fs/bpf", func(pinPath string, info os.FileInfo, err error) error {
43+
if err != nil {
44+
return err
45+
}
46+
47+
if info.IsDir() {
48+
return nil
49+
}
50+
51+
l, err := link.LoadPinnedLink(pinPath, &ebpf.LoadPinOptions{})
52+
if err != nil {
53+
klog.V(4).Infof("error getting link %s: %v", pinPath, err)
54+
return nil
55+
}
56+
57+
linkInfo, err := l.Info()
58+
if err != nil {
59+
klog.Infof("error link info: %v", err)
60+
return nil
61+
}
62+
63+
var linkIfIndex uint32
64+
switch linkInfo.Type {
65+
case link.TCXType:
66+
extra := linkInfo.TCX()
67+
if extra != nil {
68+
linkIfIndex = extra.Ifindex
69+
}
70+
case link.NetkitType:
71+
extra := linkInfo.Netkit()
72+
if extra != nil {
73+
linkIfIndex = extra.Ifindex
74+
}
75+
case link.XDPType:
76+
extra := linkInfo.XDP()
77+
if extra != nil {
78+
linkIfIndex = extra.Ifindex
79+
}
80+
default:
81+
return nil
82+
}
83+
if linkIfIndex != ifIndex {
84+
return nil
85+
}
86+
err = l.Unpin()
87+
if err != nil {
88+
klog.Infof("fail to unpin bpf link %v", err)
89+
} else {
90+
klog.V(2).Infof("succesfully unpin bpf from link %d", linkInfo.ID)
91+
}
92+
return nil
93+
})
94+
95+
}
96+
97+
// detachEBPFPrograms detaches all eBPF programs (TC and TCX) from a given network interface.
98+
// It attempts to remove both classic TC filters and newer TCX programs.
99+
// It runs inside the network namespace to avoid programs on the root namespace
100+
// to cause issues detaching the programs.
101+
func detachEBPFPrograms(containerNsPAth string, ifName string) error {
102+
origns, err := netns.Get()
103+
if err != nil {
104+
return fmt.Errorf("unexpected error trying to get namespace: %v", err)
105+
}
106+
defer origns.Close()
107+
containerNs, err := netns.GetFromPath(containerNsPAth)
108+
if err != nil {
109+
return fmt.Errorf("could not get network namespace from path %s for network device %s : %w", containerNsPAth, ifName, err)
110+
}
111+
defer containerNs.Close()
112+
113+
runtime.LockOSThread()
114+
defer runtime.UnlockOSThread()
115+
err = netns.Set(containerNs)
116+
if err != nil {
117+
return fmt.Errorf("failt to join network namespace %s : %v", containerNsPAth, err)
118+
}
119+
// Switch back to the original namespace
120+
defer netns.Set(origns) // nolint:errcheck
121+
122+
var errs []error
123+
device, err := netlink.LinkByName(ifName)
124+
if err != nil && !errors.Is(err, netlink.ErrDumpInterrupted) {
125+
return err
126+
}
127+
128+
// Detach TC filters (legacy)
129+
klog.V(2).Infof("Attempting to detach TC filters from interface %s", device.Attrs().Name)
130+
for _, parent := range []uint32{netlink.HANDLE_MIN_INGRESS, netlink.HANDLE_MIN_EGRESS} {
131+
filters, err := netlink.FilterList(device, parent)
132+
if err != nil {
133+
klog.V(4).Infof("Could not list TC filters for interface %s (parent %d): %v", device.Attrs().Name, parent, err)
134+
continue
135+
}
136+
for _, f := range filters {
137+
if bpfFilter, ok := f.(*netlink.BpfFilter); ok {
138+
klog.V(4).Infof("Deleting TC filter %s from interface %s (parent %d)", bpfFilter.Name, device.Attrs().Name, parent)
139+
if err := netlink.FilterDel(f); err != nil {
140+
klog.V(2).Infof("failed to delete TC filter %s on %s: %v", bpfFilter.Name, device.Attrs().Name, err)
141+
}
142+
}
143+
}
144+
}
145+
146+
// Detach TCX programs
147+
klog.V(2).Infof("Attempting to detach TCX programs from interface %s", device.Attrs().Name)
148+
for _, attach := range []ebpf.AttachType{ebpf.AttachTCXIngress, ebpf.AttachTCXEgress} {
149+
klog.V(2).Infof("Attempting to detach programs from attachment %s interface %s", attach.String(), device.Attrs().Name)
150+
result, err := link.QueryPrograms(link.QueryOptions{
151+
Target: int(device.Attrs().Index),
152+
Attach: attach,
153+
})
154+
if err != nil {
155+
errs = append(errs, err)
156+
continue
157+
}
158+
for _, p := range result.Programs {
159+
klog.V(2).Infof("Attempting to detach program %d from interface %s", p.ID, device.Attrs().Name)
160+
err = tryDetach(p.ID, device.Attrs().Index, attach)
161+
if err != nil {
162+
klog.V(2).Infof("Failed to detach program %d from interface %s", p.ID, device.Attrs().Name)
163+
errs = append(errs, err)
164+
}
165+
}
166+
}
167+
168+
return errors.Join(errs...)
169+
}
170+
171+
func tryDetach(id ebpf.ProgramID, deviceIdx int, attach ebpf.AttachType) error {
172+
prog, err := ebpf.NewProgramFromID(id)
173+
if err != nil {
174+
klog.V(2).Infof("failed to get eBPF program with ID %d: %v", id, err)
175+
return err
176+
}
177+
178+
if err := prog.Unpin(); err != nil {
179+
klog.Infof("failed to unpin eBPF program %s: %v", prog.String(), err)
180+
return err
181+
}
182+
183+
err = link.RawDetachProgram(link.RawDetachProgramOptions{
184+
Target: deviceIdx,
185+
Program: prog,
186+
Attach: attach,
187+
})
188+
if err != nil {
189+
klog.V(2).Infof("failed to detach eBPF program with ID %d: %v", id, err)
190+
}
191+
192+
err = prog.Close()
193+
if err != nil {
194+
klog.Infof("failed to close eBPF program %s: %v", prog.String(), err)
195+
return err
196+
}
197+
return nil
198+
}

pkg/driver/hostdevice.go

Lines changed: 0 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,7 @@ import (
2020
"errors"
2121
"fmt"
2222
"net"
23-
"runtime"
2423

25-
"github.com/cilium/ebpf"
26-
"github.com/cilium/ebpf/link"
2724
"github.com/google/dranet/pkg/apis"
2825

2926
"github.com/vishvananda/netlink"
@@ -247,91 +244,3 @@ func nsDetachNetdev(containerNsPAth string, devName string, outName string) erro
247244
}
248245
return nil
249246
}
250-
251-
// detachEBPFPrograms detaches all eBPF programs (TC and TCX) from a given network interface.
252-
// It attempts to remove both classic TC filters and newer TCX programs.
253-
// Returns an aggregated error if any detachment fails.
254-
func detachEBPFPrograms(containerNsPAth string, ifName string) error {
255-
origns, err := netns.Get()
256-
if err != nil {
257-
return fmt.Errorf("unexpected error trying to get namespace: %v", err)
258-
}
259-
defer origns.Close()
260-
261-
containerNs, err := netns.GetFromPath(containerNsPAth)
262-
if err != nil {
263-
return fmt.Errorf("could not get network namespace from path %s for network device %s : %w", containerNsPAth, ifName, err)
264-
}
265-
defer containerNs.Close()
266-
267-
runtime.LockOSThread()
268-
defer runtime.UnlockOSThread()
269-
err = netns.Set(containerNs)
270-
if err != nil {
271-
return fmt.Errorf("failt to join network namespace %s : %v", containerNsPAth, err)
272-
}
273-
274-
// Switch back to the original namespace
275-
defer netns.Set(origns) // nolint:errcheck
276-
277-
var errs []error
278-
279-
device, err := netlink.LinkByName(ifName)
280-
if err != nil {
281-
return err
282-
}
283-
284-
// Detach TC filters (legacy)
285-
klog.V(2).Infof("Attempting to detach TC filters from interface %s", device.Attrs().Name)
286-
for _, parent := range []uint32{netlink.HANDLE_MIN_INGRESS, netlink.HANDLE_MIN_EGRESS} {
287-
filters, err := netlink.FilterList(device, parent)
288-
if err != nil {
289-
klog.V(4).Infof("Could not list TC filters for interface %s (parent %d): %v", device.Attrs().Name, parent, err)
290-
continue
291-
}
292-
for _, f := range filters {
293-
if bpfFilter, ok := f.(*netlink.BpfFilter); ok {
294-
klog.V(4).Infof("Deleting TC filter %s from interface %s (parent %d)", bpfFilter.Name, device.Attrs().Name, parent)
295-
if err := netlink.FilterDel(f); err != nil {
296-
klog.V(2).Infof("failed to delete TC filter %s on %s: %v", bpfFilter.Name, device.Attrs().Name, err)
297-
}
298-
}
299-
}
300-
}
301-
// Detach TCX programs
302-
klog.V(2).Infof("Attempting to detach TCX programs from interface %s", device.Attrs().Name)
303-
for _, attach := range []ebpf.AttachType{ebpf.AttachTCXIngress, ebpf.AttachTCXEgress} {
304-
klog.V(2).Infof("Attempting to detach programs from interface %s", device.Attrs().Name)
305-
result, err := link.QueryPrograms(link.QueryOptions{
306-
Target: int(device.Attrs().Index),
307-
Attach: attach,
308-
})
309-
if err != nil {
310-
errs = append(errs, err)
311-
continue
312-
}
313-
for _, p := range result.Programs {
314-
klog.V(2).Infof("Attempting to detach program %d from interface %s", p.ID, device.Attrs().Name)
315-
prog, err := ebpf.NewProgramFromID(p.ID)
316-
if err != nil {
317-
klog.V(2).Infof("failed to get eBPF program with ID %d: %v", p.ID, err)
318-
errs = append(errs, err)
319-
continue
320-
}
321-
322-
err = link.RawDetachProgram(link.RawDetachProgramOptions{
323-
Target: device.Attrs().Index,
324-
Program: prog,
325-
Attach: attach,
326-
})
327-
if err != nil {
328-
klog.V(2).Infof("failed to get eBPF program with ID %d: %v", p.ID, err)
329-
errs = append(errs, err)
330-
continue
331-
}
332-
prog.Close() // nolint:errcheck
333-
}
334-
}
335-
336-
return errors.Join(errs...)
337-
}

pkg/driver/nri_hooks.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ func (np *NetworkDriver) RunPodSandbox(ctx context.Context, pod *api.PodSandbox)
157157
// Check if the ebpf programs should be disabled
158158
if config.Network.Interface.DisableEBPFPrograms != nil &&
159159
*config.Network.Interface.DisableEBPFPrograms {
160-
err = detachEBPFPrograms(ns, ifNameInNs)
160+
err := detachEBPFPrograms(ns, ifNameInNs)
161161
if err != nil {
162162
klog.Infof("error disabling ebpf programs for %s in ns %s: %v", ifNameInNs, ns, err)
163163
return fmt.Errorf("error disabling ebpf programs for %s in ns %s: %v", ifNameInNs, ns, err)

0 commit comments

Comments
 (0)