Skip to content

Commit 98cbcb8

Browse files
committed
libnet/d/overlay: add BPF-powered VNI matcher
Some newer distros such as RHEL 9 have stopped making the xt_u32 kernel module available with the kernels they ship. They do ship the xt_bpf kernel module, which can do everything xt_u32 can and more. Add an alternative implementation of the iptables match rule which uses xt_bpf to implement exactly the same logic as the u32 filter using a BPF program. Try programming the BPF-powered rules as a fallback when programming the u32-powered rules fails. Signed-off-by: Cory Snider <csnider@mirantis.com> (cherry picked from commit 105b983) Signed-off-by: Cory Snider <csnider@mirantis.com>
1 parent 5c5fac2 commit 98cbcb8

5 files changed

Lines changed: 112 additions & 9 deletions

File tree

libnetwork/drivers/overlay/bpf.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package overlay
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"golang.org/x/net/bpf"
8+
)
9+
10+
// vniMatchBPF returns a BPF program suitable for passing to the iptables bpf
11+
// match which matches on the VXAN Network ID of encapsulated packets. The
12+
// program assumes that it will be used in a rule which only matches UDP
13+
// datagrams.
14+
func vniMatchBPF(vni uint32) []bpf.RawInstruction {
15+
asm, err := bpf.Assemble([]bpf.Instruction{
16+
bpf.LoadMemShift{Off: 0}, // ldx 4*([0] & 0xf) ; Load length of IPv4 header into X
17+
bpf.LoadIndirect{Off: 12, Size: 4}, // ld [x + 12] ; Load VXLAN ID (UDP header + 4 bytes) into A
18+
bpf.ALUOpConstant{Op: bpf.ALUOpAnd, Val: 0xffffff00}, // and #0xffffff00 ; VXLAN ID is in top 24 bits
19+
bpf.JumpIf{Cond: bpf.JumpEqual, Val: vni << 8, SkipTrue: 1}, // jeq ($vni << 8), match
20+
bpf.RetConstant{Val: 0}, // ret #0
21+
bpf.RetConstant{Val: ^uint32(0)}, // match: ret #-1
22+
})
23+
// bpf.Assemble() only errors if an instruction is invalid. As the only variable
24+
// part of the program is an instruction value for which the entire range is
25+
// valid, whether the program can be successfully assembled is independent of
26+
// the input. Given that the only recourse is to fix this function and
27+
// recompile, there's little value in bubbling the error up to the caller.
28+
if err != nil {
29+
panic(err)
30+
}
31+
return asm
32+
}
33+
34+
// marshalXTBPF marshals a BPF program into the "decimal" byte code format
35+
// which is suitable for passing to the [iptables bpf match].
36+
//
37+
// iptables -m bpf --bytecode
38+
//
39+
// [iptables bpf match]: https://ipset.netfilter.org/iptables-extensions.man.html#lbAH
40+
func marshalXTBPF(prog []bpf.RawInstruction) string { //nolint:unused
41+
var b strings.Builder
42+
fmt.Fprintf(&b, "%d", len(prog))
43+
for _, ins := range prog {
44+
fmt.Fprintf(&b, ",%d %d %d %d", ins.Op, ins.Jt, ins.Jf, ins.K)
45+
}
46+
return b.String()
47+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package overlay
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func FuzzVNIMatchBPFDoesNotPanic(f *testing.F) {
8+
for _, seed := range []uint32{0, 1, 42, 0xfffffe, 0xffffff, 0xfffffffe, 0xffffffff} {
9+
f.Add(seed)
10+
}
11+
f.Fuzz(func(t *testing.T, vni uint32) {
12+
_ = vniMatchBPF(vni)
13+
})
14+
}

libnetwork/drivers/overlay/encryption.go

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/docker/docker/libnetwork/iptables"
1919
"github.com/docker/docker/libnetwork/ns"
2020
"github.com/docker/docker/libnetwork/types"
21+
"github.com/hashicorp/go-multierror"
2122
"github.com/sirupsen/logrus"
2223
"github.com/vishvananda/netlink"
2324
)
@@ -225,7 +226,31 @@ func removeEncryption(localIP, remoteIP net.IP, em *encrMap) error {
225226
return nil
226227
}
227228

228-
func programMangle(vni uint32, add bool) error {
229+
type matchVXLANFunc func(port, vni uint32) []string
230+
231+
// programVXLANRuleFunc returns a function which tries calling programWithMatch
232+
// with the u32 match, falling back to the BPF match if installing u32 variant
233+
// of the rules fails.
234+
func programVXLANRuleFunc(programWithMatch func(matchVXLAN matchVXLANFunc, vni uint32, add bool) error) func(vni uint32, add bool) error {
235+
return func(vni uint32, add bool) error {
236+
if add {
237+
if err := programWithMatch(matchVXLANWithU32, vni, add); err != nil {
238+
// That didn't work. Maybe the xt_u32 module isn't available? Try again with xt_bpf.
239+
err2 := programWithMatch(matchVXLANWithBPF, vni, add)
240+
if err2 != nil {
241+
return multierror.Append(err, err2)
242+
}
243+
}
244+
return nil
245+
} else {
246+
// Delete both flavours.
247+
err := programWithMatch(matchVXLANWithU32, vni, add)
248+
return multierror.Append(err, programWithMatch(matchVXLANWithBPF, vni, add)).ErrorOrNil()
249+
}
250+
}
251+
}
252+
253+
var programMangle = programVXLANRuleFunc(func(matchVXLAN matchVXLANFunc, vni uint32, add bool) error {
229254
var (
230255
m = strconv.FormatUint(mark, 10)
231256
chain = "OUTPUT"
@@ -247,9 +272,9 @@ func programMangle(vni uint32, add bool) error {
247272
}
248273

249274
return nil
250-
}
275+
})
251276

252-
func programInput(vni uint32, add bool) error {
277+
var programInput = programVXLANRuleFunc(func(matchVXLAN matchVXLANFunc, vni uint32, add bool) error {
253278
var (
254279
plainVxlan = matchVXLAN(overlayutils.VXLANUDPPort(), vni)
255280
ipsecVxlan = append([]string{"-m", "policy", "--dir", "in", "--pol", "ipsec"}, plainVxlan...)
@@ -279,7 +304,7 @@ func programInput(vni uint32, add bool) error {
279304
}
280305

281306
return nil
282-
}
307+
})
283308

284309
func programSA(localIP, remoteIP net.IP, spi *spi, k *key, dir int, add bool) (fSA *netlink.XfrmState, rSA *netlink.XfrmState, err error) {
285310
var (
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package overlay
2+
3+
import (
4+
"strconv"
5+
)
6+
7+
// matchVXLANWithBPF returns an iptables rule fragment which matches VXLAN
8+
// datagrams with the given destination port and VXLAN Network ID utilizing the
9+
// xt_bpf netfilter kernel module. The returned slice's backing array is
10+
// guaranteed not to alias any other slice's.
11+
func matchVXLANWithBPF(port, vni uint32) []string {
12+
dport := strconv.FormatUint(uint64(port), 10)
13+
vniMatch := marshalXTBPF(vniMatchBPF(vni))
14+
15+
// https://ipset.netfilter.org/iptables-extensions.man.html#lbAH
16+
return []string{"-p", "udp", "--dport", dport, "-m", "bpf", "--bytecode", vniMatch}
17+
}

libnetwork/drivers/overlay/encryption_u32.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ import (
55
"strconv"
66
)
77

8-
// matchVXLAN returns an iptables rule fragment which matches VXLAN datagrams
9-
// with the given destination port and VXLAN Network ID utilizing the xt_u32
10-
// netfilter kernel module. The returned slice's backing array is guaranteed not
11-
// to alias any other slice's.
12-
func matchVXLAN(port, vni uint32) []string {
8+
// matchVXLANWithU32 returns an iptables rule fragment which matches VXLAN
9+
// datagrams with the given destination port and VXLAN Network ID utilizing the
10+
// xt_u32 netfilter kernel module. The returned slice's backing array is
11+
// guaranteed not to alias any other slice's.
12+
func matchVXLANWithU32(port, vni uint32) []string {
1313
dport := strconv.FormatUint(uint64(port), 10)
1414

1515
// The u32 expression language is documented in iptables-extensions(8).

0 commit comments

Comments
 (0)