/
managed_neighbors.go
117 lines (99 loc) · 3.18 KB
/
managed_neighbors.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
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Cilium
package probes
import (
"errors"
"fmt"
"net"
"runtime"
"sync"
"github.com/vishvananda/netlink"
"github.com/vishvananda/netns"
)
var (
managedNeighborOnce sync.Once
managedNeighborResult error
)
// HaveManagedNeighbors returns nil if the host supports managed neighbor entries (NTF_EXT_MANAGED).
// On unexpected probe results this function will terminate with log.Fatal().
func HaveManagedNeighbors() error {
managedNeighborOnce.Do(func() {
ch := make(chan struct{})
// In order to call haveManagedNeighbors safely, it has to be started
// in a goroutine, so we can make sure the goroutine ends when the function exits.
// This makes sure the underlying OS thread exits if we fail to restore it to the original netns.
go func() {
managedNeighborResult = haveManagedNeighbors()
close(ch)
}()
<-ch // wait for probe to finish
// if we encounter a different error than ErrNotSupported, terminate the agent.
if managedNeighborResult != nil && !errors.Is(managedNeighborResult, ErrNotSupported) {
log.WithError(managedNeighborResult).Fatal("failed to probe managed neighbor support")
}
})
return managedNeighborResult
}
func haveManagedNeighbors() (outer error) {
runtime.LockOSThread()
oldns, err := netns.Get()
if err != nil {
return fmt.Errorf("failed to get current netns: %w", err)
}
defer oldns.Close()
newns, err := netns.New()
if err != nil {
return fmt.Errorf("failed to create new netns: %w", err)
}
defer newns.Close()
defer func() {
// defer closes over named return variable err
if nerr := netns.Set(oldns); nerr != nil {
// The current goroutine is locked to an OS thread and we've failed
// to undo state modifications to the thread. Returning without unlocking
// the goroutine will make sure the underlying OS thread dies.
outer = fmt.Errorf("error setting thread back to its original netns: %w (original error: %s)", nerr, outer)
return
}
// only now that we have successfully changed the thread back to its
// original state (netns) we can safely unlock the goroutine from its OS thread.
runtime.UnlockOSThread()
}()
// Use a veth device instead of a dummy to avoid the kernel having to modprobe
// the dummy kmod, which could potentially be compiled out. veth is currently
// a hard dependency for Cilium, so safe to assume the module is available if
// not already loaded.
veth := &netlink.Veth{
LinkAttrs: netlink.LinkAttrs{Name: "veth0"},
PeerName: "veth1",
}
if err := netlink.LinkAdd(veth); err != nil {
return fmt.Errorf("failed to add dummy veth: %w", err)
}
neigh := netlink.Neigh{
LinkIndex: veth.Index,
IP: net.IPv4(0, 0, 0, 1),
Flags: NTF_EXT_LEARNED,
FlagsExt: NTF_EXT_MANAGED,
}
if err := netlink.NeighAdd(&neigh); err != nil {
return fmt.Errorf("failed to add neighbor: %w", err)
}
nl, err := netlink.NeighList(veth.Index, 0)
if err != nil {
return fmt.Errorf("failed to list neighbors: %w", err)
}
for _, n := range nl {
if !n.IP.Equal(neigh.IP) {
continue
}
if n.Flags != NTF_EXT_LEARNED {
continue
}
if n.FlagsExt != NTF_EXT_MANAGED {
continue
}
return nil
}
return ErrNotSupported
}