mirror of
https://github.com/moby/moby.git
synced 2026-01-11 18:51:37 +00:00
Move internal/testutils/networking to integration/internal/testutils/networking
Signed-off-by: Derek McGowan <derek@mcg.dev>
This commit is contained in:
@@ -1,127 +0,0 @@
|
||||
package networking
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/testutil/daemon"
|
||||
"gotest.tools/v3/assert"
|
||||
"gotest.tools/v3/icmd"
|
||||
"gotest.tools/v3/poll"
|
||||
)
|
||||
|
||||
const (
|
||||
// The name of the bridge driver's nftables tables.
|
||||
nftTable = "docker-bridges"
|
||||
// The name of the filter-FORWARD chain in nftTable.
|
||||
nftFFChain = "filter-FORWARD"
|
||||
)
|
||||
|
||||
// Find the policy in, for example "Chain FORWARD (policy ACCEPT)".
|
||||
var rePolicy = regexp.MustCompile("policy ([A-Za-z]+)")
|
||||
|
||||
// SetFilterForwardPolicies sets the default policy for the FORWARD chain in
|
||||
// the filter tables for both IPv4 and IPv6. The original policy is restored
|
||||
// using t.Cleanup().
|
||||
//
|
||||
// There's only one filter-FORWARD policy, so this won't behave well if used by
|
||||
// tests running in parallel in a single network namespace that expect different
|
||||
// behaviour.
|
||||
func SetFilterForwardPolicies(t *testing.T, firewallBackend string, policy string) {
|
||||
t.Helper()
|
||||
if strings.HasPrefix(firewallBackend, "iptables") {
|
||||
setIptablesFFP(t, policy)
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(firewallBackend, "nftables") {
|
||||
setNftablesFFP(t, policy)
|
||||
return
|
||||
}
|
||||
t.Fatalf("unknown firewall backend %s", firewallBackend)
|
||||
}
|
||||
|
||||
func setIptablesFFP(t *testing.T, policy string) {
|
||||
t.Helper()
|
||||
for _, iptablesCmd := range []string{"iptables", "ip6tables"} {
|
||||
origPolicy, err := getChainPolicy(t, exec.Command(iptablesCmd, "-L", "FORWARD"))
|
||||
assert.NilError(t, err, "failed to get iptables policy")
|
||||
if origPolicy == policy {
|
||||
continue
|
||||
}
|
||||
if err := exec.Command(iptablesCmd, "-P", "FORWARD", policy).Run(); err != nil {
|
||||
t.Fatalf("Failed to set %s FORWARD policy: %v", iptablesCmd, err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if err := exec.Command(iptablesCmd, "-P", "FORWARD", origPolicy).Run(); err != nil {
|
||||
t.Logf("Failed to restore %s FORWARD policy: %v", iptablesCmd, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func setNftablesFFP(t *testing.T, policy string) {
|
||||
t.Helper()
|
||||
policy = strings.ToLower(policy)
|
||||
for _, family := range []string{"ip", "ip6"} {
|
||||
origPolicy, err := getChainPolicy(t, exec.Command("nft", "list", "chain", family, nftTable, nftFFChain))
|
||||
assert.NilError(t, err, "failed to get nftables policy")
|
||||
if origPolicy == policy {
|
||||
continue
|
||||
}
|
||||
cmd := func(p string) *exec.Cmd {
|
||||
return exec.Command("nft", "add", "chain", family, nftTable, nftFFChain, "{", "policy", p, ";", "}")
|
||||
}
|
||||
if err := cmd(policy).Run(); err != nil {
|
||||
t.Fatalf("Failed to set %s filter-FORWARD policy: %v", family, err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if err := cmd(origPolicy).Run(); err != nil {
|
||||
t.Logf("Failed to restore %s filter-FORWARD policy: %v", family, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func getChainPolicy(t *testing.T, cmd *exec.Cmd) (string, error) {
|
||||
t.Helper()
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("getting policy: %w", err)
|
||||
}
|
||||
opMatch := rePolicy.FindSubmatch(out)
|
||||
if len(opMatch) != 2 {
|
||||
return "", fmt.Errorf("searching for policy: %w", err)
|
||||
}
|
||||
return string(opMatch[1]), nil
|
||||
}
|
||||
|
||||
// FirewalldRunning returns true if "firewall-cmd --state" reports "running".
|
||||
func FirewalldRunning() bool {
|
||||
state, err := exec.Command("firewall-cmd", "--state").CombinedOutput()
|
||||
return err == nil && strings.TrimSpace(string(state)) == "running"
|
||||
}
|
||||
|
||||
// FirewalldReload reloads firewalld and waits for the daemon to re-create its rules.
|
||||
// It's a no-op if firewalld is not running, and the test fails if the reload does
|
||||
// not complete.
|
||||
func FirewalldReload(t *testing.T, d *daemon.Daemon) {
|
||||
t.Helper()
|
||||
if !FirewalldRunning() {
|
||||
return
|
||||
}
|
||||
lastReload := d.FirewallReloadedAt(t)
|
||||
res := icmd.RunCommand("firewall-cmd", "--reload")
|
||||
assert.NilError(t, res.Error)
|
||||
|
||||
poll.WaitOn(t, func(_ poll.LogT) poll.Result {
|
||||
latestReload := d.FirewallReloadedAt(t)
|
||||
if latestReload != "" && latestReload != lastReload {
|
||||
t.Log("Firewalld reload completed at", latestReload)
|
||||
return poll.Success()
|
||||
}
|
||||
return poll.Continue("firewalld reload not complete")
|
||||
})
|
||||
}
|
||||
@@ -1,197 +0,0 @@
|
||||
package networking
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/netip"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"github.com/vishvananda/netns"
|
||||
)
|
||||
|
||||
// CurrentNetns can be passed to L3Segment.AddHost to indicate that the
|
||||
// host lives in the current network namespace (eg. where dockerd runs).
|
||||
const CurrentNetns = ""
|
||||
|
||||
func runCommand(t *testing.T, cmd string, args ...string) (string, error) {
|
||||
t.Helper()
|
||||
t.Log(strings.Join(append([]string{cmd}, args...), " "))
|
||||
|
||||
var b bytes.Buffer
|
||||
c := exec.Command(cmd, args...)
|
||||
c.Stdout = &b
|
||||
c.Stderr = &b
|
||||
err := c.Run()
|
||||
return b.String(), err
|
||||
}
|
||||
|
||||
// L3Segment simulates a switched, dual-stack capable network that
|
||||
// interconnects multiple hosts running in their own network namespace.
|
||||
type L3Segment struct {
|
||||
Hosts map[string]Host
|
||||
bridge Host
|
||||
}
|
||||
|
||||
// NewL3Segment creates a new L3Segment. The bridge interface interconnecting
|
||||
// all the hosts is created in a new network namespace named nsName and it's
|
||||
// assigned one or more IP addresses. Those need to be unmasked netip.Prefix.
|
||||
func NewL3Segment(t *testing.T, nsName string, addrs ...netip.Prefix) *L3Segment {
|
||||
t.Helper()
|
||||
|
||||
l3 := &L3Segment{
|
||||
Hosts: map[string]Host{},
|
||||
}
|
||||
|
||||
l3.bridge = newHost(t, "bridge", nsName, "br0")
|
||||
defer func() {
|
||||
if t.Failed() {
|
||||
l3.Destroy(t)
|
||||
}
|
||||
}()
|
||||
|
||||
l3.bridge.MustRun(t, "ip", "link", "add", l3.bridge.Iface, "type", "bridge")
|
||||
for _, addr := range addrs {
|
||||
l3.bridge.MustRun(t, "ip", "addr", "add", addr.String(), "dev", l3.bridge.Iface, "nodad")
|
||||
l3.bridge.MustRun(t, "ip", "link", "set", l3.bridge.Iface, "up")
|
||||
}
|
||||
|
||||
return l3
|
||||
}
|
||||
|
||||
func (l3 *L3Segment) AddHost(t *testing.T, hostname, nsName, ifname string, addrs ...netip.Prefix) {
|
||||
t.Helper()
|
||||
|
||||
if len(hostname) >= syscall.IFNAMSIZ {
|
||||
// hostname is reused as the name for the veth interface added to the
|
||||
// bridge. Hence, it needs to be shorter than ifnamsiz.
|
||||
t.Fatalf("hostname too long")
|
||||
}
|
||||
|
||||
host := newHost(t, hostname, nsName, ifname)
|
||||
l3.Hosts[hostname] = host
|
||||
|
||||
host.MustRun(t, "ip", "link", "add", hostname, "netns", l3.bridge.ns, "type", "veth", "peer", "name", host.Iface)
|
||||
l3.bridge.MustRun(t, "ip", "link", "set", hostname, "up", "master", l3.bridge.Iface)
|
||||
host.MustRun(t, "ip", "link", "set", host.Iface, "up")
|
||||
host.MustRun(t, "ip", "link", "set", "lo", "up")
|
||||
|
||||
for _, addr := range addrs {
|
||||
host.MustRun(t, "ip", "addr", "add", addr.String(), "dev", host.Iface, "nodad")
|
||||
}
|
||||
}
|
||||
|
||||
func (l3 *L3Segment) Destroy(t *testing.T) {
|
||||
t.Helper()
|
||||
for _, host := range l3.Hosts {
|
||||
host.Destroy(t)
|
||||
}
|
||||
l3.bridge.Destroy(t)
|
||||
}
|
||||
|
||||
type Host struct {
|
||||
Name string
|
||||
Iface string // Iface is the interface name in the host network namespace.
|
||||
ns string // ns is the network namespace name.
|
||||
}
|
||||
|
||||
func newHost(t *testing.T, hostname, nsName, ifname string) Host {
|
||||
t.Helper()
|
||||
|
||||
if len(ifname) >= syscall.IFNAMSIZ {
|
||||
t.Fatalf("ifname too long")
|
||||
}
|
||||
|
||||
if nsName != CurrentNetns {
|
||||
if out, err := runCommand(t, "ip", "netns", "add", nsName); err != nil {
|
||||
t.Log(out)
|
||||
t.Fatalf("Error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return Host{
|
||||
Name: hostname,
|
||||
Iface: ifname,
|
||||
ns: nsName,
|
||||
}
|
||||
}
|
||||
|
||||
// Run executes the provided command in the host's network namespace,
|
||||
// returns its combined stdout/stderr, and error.
|
||||
func (h Host) Run(t *testing.T, cmd string, args ...string) (string, error) {
|
||||
t.Helper()
|
||||
if h.ns != CurrentNetns {
|
||||
args = append([]string{"netns", "exec", h.ns, cmd}, args...)
|
||||
cmd = "ip"
|
||||
}
|
||||
return runCommand(t, cmd, args...)
|
||||
}
|
||||
|
||||
// MustRun executes the provided command in the host's network namespace
|
||||
// and returns its combined stdout/stderr, failing the test if the
|
||||
// command returns an error.
|
||||
func (h Host) MustRun(t *testing.T, cmd string, args ...string) string {
|
||||
t.Helper()
|
||||
out, err := h.Run(t, cmd, args...)
|
||||
if err != nil {
|
||||
t.Log(out)
|
||||
t.Fatalf("Error: %v", err)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Do run the provided function in the host's network namespace.
|
||||
func (h Host) Do(t *testing.T, fn func()) {
|
||||
t.Helper()
|
||||
|
||||
if h.ns != CurrentNetns {
|
||||
targetNs, err := netns.GetFromName(h.ns)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get netns handle: %v", err)
|
||||
}
|
||||
defer targetNs.Close()
|
||||
|
||||
origNs, err := netns.Get()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get current netns: %v", err)
|
||||
}
|
||||
defer origNs.Close()
|
||||
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
if err := netns.Set(targetNs); err != nil {
|
||||
t.Fatalf("failed to enter netns: %v", err)
|
||||
}
|
||||
defer netns.Set(origNs)
|
||||
}
|
||||
|
||||
fn()
|
||||
}
|
||||
|
||||
func (h Host) Destroy(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
// When a netns is deleted while there's still veth interfaces in it, the
|
||||
// kernel delete both ends of the veth pairs. The veth interface living in
|
||||
// that netns will be deleted instantaneously, but the other end will be
|
||||
// reclaimed after a short delay.
|
||||
//
|
||||
// If, in the meantime, a new test is spun up, and tries to create a new
|
||||
// veth pair with the same peer name, the kernel will return -EEXIST.
|
||||
//
|
||||
// But, if the veth pair is explicitly deleted _before_ the netns, then
|
||||
// both veth ends will be deleted instantaneously.
|
||||
//
|
||||
// Hence, we need to do just that here.
|
||||
h.MustRun(t, "ip", "link", "delete", h.Iface)
|
||||
|
||||
if h.ns != CurrentNetns {
|
||||
if out, err := runCommand(t, "ip", "netns", "delete", h.ns); err != nil {
|
||||
t.Log(out)
|
||||
t.Fatalf("Error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user