The filter blocks everything passing through the machine and then allows the machine's own containers back by naming the address ranges they sit on — two ranges fixed in the control plane and the rest typed after a flip had already cut a workstation off. A range describes one machine and goes stale in silence. Read the links carrying a default route instead, from /proc rather than by asking a program, and report them on every apply. A machine with no route off itself reports nothing, and the mesh composes no filter for it rather than writing a rule around a link with no name. novox/hq ADR 0140. The control plane does not read this yet.
121 lines
4.2 KiB
Go
121 lines
4.2 KiB
Go
package outward
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
// A routing table as the kernel writes it: a default route, a route to the zero address that is not
|
|
// one, and a route on the loopback. Only the default route's link faces outside.
|
|
const routeV4 = `Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT
|
|
enp9s0 00000000 01FEA8C0 0003 0 0 100 00000000 0 0 0
|
|
docker0 000011AC 00000000 0001 0 0 0 0000FFFF 0 0 0
|
|
enp9s0 00000000 00000000 0001 0 0 100 00FFFFFF 0 0 0
|
|
lo 00000000 00000000 0003 0 0 0 00000000 0 0 0
|
|
`
|
|
|
|
const routeV6 = `00000000000000000000000000000000 00 00000000000000000000000000000000 00 fe800000000000000000000000000001 00000400 00000001 00000000 00000003 wlan0
|
|
fd0000000000000000000000000000000 40 00000000000000000000000000000000 00 00000000000000000000000000000000 00000100 00000000 00000000 00000001 enp9s0
|
|
`
|
|
|
|
func write(t *testing.T, dir, name, body string) {
|
|
t.Helper()
|
|
if err := os.WriteFile(filepath.Join(dir, name), []byte(body), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestLinksAreTheOnesCarryingADefaultRoute(t *testing.T) {
|
|
dir := t.TempDir()
|
|
write(t, dir, "route", routeV4)
|
|
write(t, dir, "ipv6_route", routeV6)
|
|
|
|
got, err := Links(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// The cable from the v4 table and the radio from the v6 one. Not docker0, whose route is not a
|
|
// default; not the loopback, which faces nothing; and not the v6 route with a real prefix.
|
|
want := []string{"enp9s0", "wlan0"}
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("outward links are %v, want %v", got, want)
|
|
}
|
|
}
|
|
|
|
// A route to the zero address with a real mask is not a default route. Trusting the destination
|
|
// alone would name every link with such a route as facing outside, and a filter that treats an
|
|
// internal bridge as outward constrains this machine's own guests — the fault ADR 0140 removes.
|
|
func TestAZeroDestinationWithAMaskIsNotADefaultRoute(t *testing.T) {
|
|
dir := t.TempDir()
|
|
write(t, dir, "route", `Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT
|
|
br-abc 00000000 00000000 0001 0 0 0 00FFFFFF 0 0 0
|
|
`)
|
|
got, err := Links(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(got) != 0 {
|
|
t.Fatalf("outward links are %v, want none", got)
|
|
}
|
|
}
|
|
|
|
// A machine with no route off itself says so, rather than guessing. The mesh refuses to compose a
|
|
// filter for it; a rule written around a link with no name does not load, and a rule set that does
|
|
// not load is a machine filtering nothing while its unit reports success.
|
|
func TestNoDefaultRouteIsNoLinks(t *testing.T) {
|
|
dir := t.TempDir()
|
|
write(t, dir, "route", "Iface\tDestination\tGateway \tFlags\tRefCnt\tUse\tMetric\tMask\t\tMTU\tWindow\tIRTT\n")
|
|
got, err := Links(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(got) != 0 {
|
|
t.Fatalf("outward links are %v, want none", got)
|
|
}
|
|
}
|
|
|
|
// A machine without the second address family has no file for it. That is not a machine that cannot
|
|
// be filtered, so a missing table is read as no routes rather than as a failure.
|
|
func TestAMissingTableIsNotAFailure(t *testing.T) {
|
|
dir := t.TempDir()
|
|
write(t, dir, "route", routeV4)
|
|
got, err := Links(dir)
|
|
if err != nil {
|
|
t.Fatalf("a missing v6 table should not fail: %v", err)
|
|
}
|
|
if !reflect.DeepEqual(got, []string{"enp9s0"}) {
|
|
t.Fatalf("outward links are %v, want [enp9s0]", got)
|
|
}
|
|
}
|
|
|
|
// The same link carrying a default route in both families is reported once.
|
|
func TestALinkIsReportedOnce(t *testing.T) {
|
|
dir := t.TempDir()
|
|
write(t, dir, "route", routeV4)
|
|
write(t, dir, "ipv6_route",
|
|
"00000000000000000000000000000000 00 00000000000000000000000000000000 00 "+
|
|
"fe800000000000000000000000000001 00000400 00000001 00000000 00000003 enp9s0\n")
|
|
got, err := Links(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !reflect.DeepEqual(got, []string{"enp9s0"}) {
|
|
t.Fatalf("outward links are %v, want [enp9s0]", got)
|
|
}
|
|
}
|
|
|
|
// Against this machine's own routing table, so the parse is held to what the kernel actually writes
|
|
// and not only to a fixture written to agree with it.
|
|
func TestAgainstThisMachinesOwnTable(t *testing.T) {
|
|
got, err := Links("")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(got) == 0 {
|
|
t.Skip("this machine has no default route")
|
|
}
|
|
t.Logf("this machine's outward links: %v", got)
|
|
}
|