-
Notifications
You must be signed in to change notification settings - Fork 3
/
macvlan.go
180 lines (160 loc) · 5.74 KB
/
macvlan.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
package macvlan
import (
"bytes"
"errors"
"fmt"
"net"
"strings"
"time"
"github.com/go-logr/logr"
"github.com/vishvananda/netlink"
"golang.org/x/sys/unix"
ctrl "sigs.k8s.io/controller-runtime"
)
const checkInterval = 5 * time.Second
var trackedBridges []int
func checkTrackedInterfaces(logger logr.Logger) {
for _, intfIdx := range trackedBridges {
intf, err := netlink.LinkByIndex(intfIdx)
if err != nil {
logger.Error(err, "couldn't load interface", "index", intfIdx)
}
syncInterface(intf.(*netlink.Bridge), logger)
}
}
func ensureMACDummyIntf(intf *netlink.Bridge, logger logr.Logger) (netlink.Link, error) {
name := fmt.Sprintf("mvd.%s", intf.Attrs().Name)
macDummy, err := netlink.LinkByName(name)
if err != nil {
var linkNotFoundErr *netlink.LinkNotFoundError
if !errors.As(err, &linkNotFoundErr) {
return nil, fmt.Errorf("error getting link by name: %w", err)
}
macDummy = &netlink.Dummy{
LinkAttrs: netlink.NewLinkAttrs(),
}
macDummy.Attrs().Name = name
macDummy.Attrs().MasterIndex = intf.Attrs().Index
macDummy.Attrs().MTU = intf.Attrs().MTU
err = netlink.LinkAdd(macDummy)
if err != nil {
return nil, fmt.Errorf("error adding link %s: %w", macDummy.Attrs().Name, err)
}
err = netlink.LinkSetDown(macDummy)
if err != nil {
return nil, fmt.Errorf("error setting link %s down: %w", macDummy.Attrs().Name, err)
}
}
if macDummy.Attrs().OperState != netlink.OperDown {
logger.Info("Interface not down, setting down - otherwise it would route traffic", "interface", name)
err = netlink.LinkSetDown(macDummy)
if err != nil {
return nil, fmt.Errorf("error setting link %s down: %w", macDummy.Attrs().Name, err)
}
}
return macDummy, nil
}
func createNeighborEntry(mac net.HardwareAddr, intf, master int) *netlink.Neigh {
return &netlink.Neigh{
State: netlink.NUD_NOARP,
Family: unix.AF_BRIDGE,
HardwareAddr: mac,
LinkIndex: intf,
MasterIndex: master,
}
}
func isUnicastMac(mac net.HardwareAddr) bool {
return mac[0]&0x01 == 0
}
func containsMACAddress(list []net.HardwareAddr, mac net.HardwareAddr) bool {
for _, v := range list {
if bytes.Equal(v, mac) {
return true
}
}
return false
}
func syncInterface(intf *netlink.Bridge, logger logr.Logger) {
// First ensure that we have a dummy interface
dummy, err := ensureMACDummyIntf(intf, logger)
if err != nil {
logger.Error(err, "error syncing interface", "interface", intf.Attrs().Name)
return
}
configureNeighbors(intf, dummy, logger)
}
func configureNeighbors(intf *netlink.Bridge, dummy netlink.Link, logger logr.Logger) {
// Get neighbors of bridge
bridgeNeighbors, err := netlink.NeighList(intf.Attrs().Index, unix.AF_BRIDGE)
if err != nil {
logger.Error(err, "error getting neighbors", "interface", intf.Attrs().Name)
return
}
requiredMACAddresses := []net.HardwareAddr{}
for i := range bridgeNeighbors {
neigh := &bridgeNeighbors[i]
// Look for unicast neighbor entries like "02:03:04:05:06:07 dev <bridge> self permanent"
if neigh.MasterIndex == 0 && neigh.Flags == netlink.NTF_SELF && neigh.State == netlink.NUD_PERMANENT && isUnicastMac(neigh.HardwareAddr) {
requiredMACAddresses = append(requiredMACAddresses, neigh.HardwareAddr)
}
}
// Get neighbors of dummy
dummyNeighbors, err := netlink.NeighList(dummy.Attrs().Index, unix.AF_BRIDGE)
if err != nil {
logger.Error(err, "error getting neighbors", "interface", intf.Attrs().Name)
return
}
alreadyExisting := getAlreadyExistingNeighbors(dummyNeighbors, requiredMACAddresses, dummy.Attrs().Name, intf.Attrs().Name, logger)
// Add required MAC addresses when they are not yet existing (aka in alreadyExisting slice)
for _, neigh := range requiredMACAddresses {
if !containsMACAddress(alreadyExisting, neigh) {
logger.Info("adding MAC address on dummy interface of bridge", "address", neigh, "interface", dummy.Attrs().Name, "bridge", intf.Attrs().Name)
err = netlink.NeighSet(createNeighborEntry(neigh, dummy.Attrs().Index, intf.Attrs().Index))
if err != nil {
logger.Error(err, "error adding neighbor to interface", "neighbor", neigh, "interface", dummy.Attrs().Name, "bridge", intf.Attrs().Name)
}
}
}
}
func getAlreadyExistingNeighbors(dummyNeighbors []netlink.Neigh, requiredMACAddresses []net.HardwareAddr, dummyName, intfName string, logger logr.Logger) []net.HardwareAddr {
alreadyExisting := []net.HardwareAddr{}
for i := range dummyNeighbors {
neigh := &dummyNeighbors[i]
// Look for unicast neighbor entries with no flags, no vlan, NUD_NOARP (static) fdb entries
if neigh.Vlan == 0 && neigh.Flags == 0 && neigh.State == netlink.NUD_NOARP && isUnicastMac(neigh.HardwareAddr) {
if !containsMACAddress(requiredMACAddresses, neigh.HardwareAddr) {
// If MAC Address is not in required MAC addresses, delete neighbor
if err := netlink.NeighDel(neigh); err != nil {
logger.Error(err, "error deleting neighbor", "neighbor", neigh)
}
logger.Info("removed MAC address", "address", neigh.HardwareAddr.String(), "interface", dummyName, "bridge", intfName)
} else {
// Add MAC address to alreadyExisting table
alreadyExisting = append(alreadyExisting, neigh.HardwareAddr)
}
}
}
return alreadyExisting
}
func RunMACSync(interfacePrefix string) {
logger := ctrl.Log.WithName("macvlan")
links, err := netlink.LinkList()
if err != nil {
logger.Error(err, "error loading interfaces")
return
}
for _, link := range links {
if strings.HasPrefix(link.Attrs().Name, interfacePrefix) && link.Type() == "bridge" {
logger.Info("tracking interface", "interface", link.Attrs().Name, "prefix", interfacePrefix)
trackedBridges = append(trackedBridges, link.Attrs().Index)
}
}
if len(trackedBridges) > 0 {
go func() {
for {
checkTrackedInterfaces(logger)
time.Sleep(checkInterval)
}
}()
}
}