diff --git a/internal/dhcp/dhcp_test.go b/internal/dhcp/dhcp_test.go index a13d1a2..94c7d7b 100644 --- a/internal/dhcp/dhcp_test.go +++ b/internal/dhcp/dhcp_test.go @@ -55,7 +55,7 @@ func newConf(t *testing.T, cidr string) Config { gw := net.ParseIP("192.168.1.1").To4() return Config{ Network: network, - InterfaceIP: net.ParseIP("192.168.1.254").To4(), + VPCGateway: gw, VPCRoute: vpcNet, DefaultGateway: gw, Name: "test", @@ -136,29 +136,32 @@ func TestGenerateConfig_VxlanEmitsNoRouterOption(t *testing.T) { } func TestGenerateConfig_ContainsVPCRoute(t *testing.T) { - routes := route121(t, confLines(t, newConf(t, "192.168.1.0/29"))) - if !strings.Contains(routes, "10.0.0.0/16,192.168.1.254") { - t.Errorf("route VPC absente, ou next-hop autre que l'interface_ip du subnet :\n%s", routes) + conf := newConf(t, "192.168.1.0/29") + path, _, _ := GenerateConfig(conf) + content, _ := os.ReadFile(path) + + if !strings.Contains(string(content), "dhcp-option=121,10.0.0.0/16,192.168.1.1") { + t.Errorf("dhcp-option=121 absente ou incorrecte :\n%s", content) } } func TestGenerateConfig_NoVPCRoute(t *testing.T) { conf := newConf(t, "192.168.1.0/29") conf.VPCRoute = nil + path, _, _ := GenerateConfig(conf) + content, _ := os.ReadFile(path) - routes := route121(t, confLines(t, conf)) - if strings.Contains(routes, "10.0.0.0/16") { - t.Errorf("route VPC présente alors que VPCRoute=nil :\n%s", routes) + if strings.Contains(string(content), "dhcp-option=121,") { + t.Errorf("dhcp-option=121 présente alors que VPCRoute=nil :\n%s", content) } } func TestGenerateConfig_ContainsDhcpRange(t *testing.T) { _, network, _ := net.ParseCIDR("10.10.0.0/24") conf := Config{ - Network: network, - InterfaceIP: net.ParseIP("10.10.0.1").To4(), - Name: "vpc1", - ConfDir: t.TempDir(), + Network: network, + Name: "vpc1", + ConfDir: t.TempDir(), } path, _, _ := GenerateConfig(conf) content, _ := os.ReadFile(path) @@ -201,10 +204,9 @@ func TestGenerateConfig_CreatesConfDir(t *testing.T) { dir := filepath.Join(t.TempDir(), "sous", "dossier") _, network, _ := net.ParseCIDR("10.0.0.0/30") conf := Config{ - Network: network, - InterfaceIP: net.ParseIP("10.0.0.1").To4(), - Name: "net", - ConfDir: dir, + Network: network, + Name: "net", + ConfDir: dir, } if _, _, err := GenerateConfig(conf); err != nil { t.Fatalf("GenerateConfig devrait créer les répertoires manquants : %v", err) @@ -213,79 +215,3 @@ func TestGenerateConfig_CreatesConfDir(t *testing.T) { t.Errorf("répertoire %q non créé", dir) } } - -// --- option 121 : routes classless --- - -func confLines(t *testing.T, c Config) string { - t.Helper() - path, _, err := GenerateConfig(c) - if err != nil { - t.Fatalf("GenerateConfig : %v", err) - } - content, err := os.ReadFile(path) - if err != nil { - t.Fatalf("lecture : %v", err) - } - return string(content) -} - -func route121(t *testing.T, content string) string { - t.Helper() - for _, line := range strings.Split(content, "\n") { - if strings.HasPrefix(line, "dhcp-option=121,") { - return strings.TrimPrefix(line, "dhcp-option=121,") - } - } - t.Fatalf("aucune dhcp-option=121 dans :\n%s", content) - return "" -} - -func TestGenerateConfig_AlwaysRoutesToMetadata(t *testing.T) { - conf := newConf(t, "192.168.1.0/29") - conf.VPCRoute = nil - conf.DefaultGateway = nil - - routes := route121(t, confLines(t, conf)) - if !strings.Contains(routes, "169.254.169.254/32,192.168.1.254") { - t.Errorf("sans route vers le serveur de métadonnées, cloud-init échoue et la VM n'est pas provisionnée :\n%s", routes) - } -} - -func TestGenerateConfig_MetadataRouteEvenInBridgeMode(t *testing.T) { - conf := newConf(t, "192.168.1.0/29") - conf.VPCRoute = nil - - routes := route121(t, confLines(t, conf)) - if !strings.Contains(routes, "169.254.169.254/32") { - t.Errorf("le mode bridge a besoin de la même route :\n%s", routes) - } -} - -func TestGenerateConfig_DefaultRouteAlsoInOption121(t *testing.T) { - conf := newConf(t, "192.168.1.0/29") - conf.VPCRoute = nil - - routes := route121(t, confLines(t, conf)) - if !strings.Contains(routes, "0.0.0.0/0,192.168.1.1") { - t.Errorf("RFC 3442 : un client qui lit l'option 121 ignore l'option 3, la route par défaut doit donc figurer dans la 121 :\n%s", routes) - } -} - -func TestGenerateConfig_NoDefaultRouteMeansNoCatchAllInOption121(t *testing.T) { - conf := newConf(t, "192.168.1.0/29") - conf.DefaultGateway = nil - - routes := route121(t, confLines(t, conf)) - if strings.Contains(routes, "0.0.0.0/0") { - t.Errorf("aucune route par défaut demandée, la 121 ne doit pas en contenir :\n%s", routes) - } -} - -func TestGenerateConfig_MissingInterfaceIPIsAnError(t *testing.T) { - conf := newConf(t, "192.168.1.0/29") - conf.InterfaceIP = nil - - if _, _, err := GenerateConfig(conf); err == nil { - t.Error("sans interface_ip aucune route metadata n'est possible : il faut échouer, pas écrire une conf muette") - } -} diff --git a/internal/dhcp/generate.go b/internal/dhcp/generate.go index ebcb914..40250f6 100644 --- a/internal/dhcp/generate.go +++ b/internal/dhcp/generate.go @@ -6,20 +6,17 @@ import ( "os" "path/filepath" "strings" - - "git.g3e.fr/syonad/two/internal/metadata" ) func GenerateConfig(c Config) (string, map[string]string, error) { - if c.InterfaceIP == nil { - return "", nil, fmt.Errorf("interface ip is required: guests would have no route to the metadata server") - } mask := fmt.Sprintf("%d.%d.%d.%d", c.Network.Mask[0], c.Network.Mask[1], c.Network.Mask[2], c.Network.Mask[3]) var sb strings.Builder fmt.Fprintf(&sb, "no-resolv\n") fmt.Fprintf(&sb, "dhcp-range=%s,static,%s,12h\n", c.Network.IP.String(), mask) - fmt.Fprintf(&sb, "dhcp-option=121,%s\n", strings.Join(classlessRoutes(c), ",")) + if c.VPCRoute != nil { + fmt.Fprintf(&sb, "dhcp-option=121,%s,%s\n", c.VPCRoute.String(), c.VPCGateway.String()) + } if c.DefaultGateway != nil { fmt.Fprintf(&sb, "dhcp-option=3,%s\n", c.DefaultGateway.String()) } else { @@ -43,19 +40,6 @@ func GenerateConfig(c Config) (string, map[string]string, error) { return outPath, entries, os.WriteFile(outPath, []byte(sb.String()), 0644) } -func classlessRoutes(c Config) []string { - nextHop := c.InterfaceIP.String() - - routes := []string{metadata.ServiceIP + "/32," + nextHop} - if c.VPCRoute != nil { - routes = append(routes, c.VPCRoute.String()+","+nextHop) - } - if c.DefaultGateway != nil { - routes = append(routes, "0.0.0.0/0,"+c.DefaultGateway.String()) - } - return routes -} - func incrementIP(ip net.IP) { for j := len(ip) - 1; j >= 0; j-- { ip[j]++ diff --git a/internal/dhcp/struct.go b/internal/dhcp/struct.go index 9df7c1f..bacb1e0 100644 --- a/internal/dhcp/struct.go +++ b/internal/dhcp/struct.go @@ -8,9 +8,9 @@ const DefaultConfDir = "/etc/dnsmasq.d" type Config struct { Network *net.IPNet - InterfaceIP net.IP // subnet gateway; next-hop for the metadata and VPC routes - VPCRoute *net.IPNet // if non-nil, routed via InterfaceIP in option 121 - DefaultGateway net.IP // if non-nil, default route via option 3 and 0.0.0.0/0 in option 121 + VPCGateway net.IP // next-hop for VPCRoute (option 121) + VPCRoute *net.IPNet // if non-nil, emit dhcp-option=121,VPCRoute,VPCGateway + DefaultGateway net.IP // if non-nil, emit dhcp-option=3,DefaultGateway; if nil, emit a bare dhcp-option=3 to suppress the dnsmasq default Name string ConfDir string } diff --git a/internal/iptables/iptables.go b/internal/iptables/iptables.go index 5e39e3e..1d156a7 100644 --- a/internal/iptables/iptables.go +++ b/internal/iptables/iptables.go @@ -3,8 +3,6 @@ package iptables import ( "fmt" "os/exec" - - "git.g3e.fr/syonad/two/internal/metadata" ) func addRule(args ...string) error { @@ -18,7 +16,7 @@ func deleteRule(args ...string) error { func AddMetadataRedirect(vmIP, gatewayIP, metadataPort string) error { if err := addRule("PREROUTING", "-s", vmIP+"/32", - "-d", metadata.ServiceIP+"/32", + "-d", "169.254.169.254/32", "-p", "tcp", "-m", "tcp", "--dport", "80", "-j", "DNAT", @@ -32,7 +30,7 @@ func AddMetadataRedirect(vmIP, gatewayIP, metadataPort string) error { func DeleteMetadataRedirect(vmIP, gatewayIP, metadataPort string) error { if err := deleteRule("PREROUTING", "-s", vmIP+"/32", - "-d", metadata.ServiceIP+"/32", + "-d", "169.254.169.254/32", "-p", "tcp", "-m", "tcp", "--dport", "80", "-j", "DNAT", diff --git a/internal/metadata/struct.go b/internal/metadata/struct.go index 39592ed..2e94cfc 100644 --- a/internal/metadata/struct.go +++ b/internal/metadata/struct.go @@ -1,10 +1,5 @@ package metadata -// ServiceIP is the well-known address guests use to reach the metadata server. -// Traffic to it is DNATed to the subnet gateway by internal/iptables, and the -// route to it is advertised by internal/dhcp. -const ServiceIP = "169.254.169.254" - type NoCloudData struct { MetaData string UserData string diff --git a/internal/subnet/create.go b/internal/subnet/create.go index a42a1a9..e7b0c70 100644 --- a/internal/subnet/create.go +++ b/internal/subnet/create.go @@ -136,13 +136,13 @@ func setupVxlanHost(d subnetData, vethE string) error { func startDHCP(db *badger.DB, subnetName string, d subnetData) error { conf := dhcp.Config{ - Network: d.cidr, - Name: d.vpc + "_" + d.bridge, - ConfDir: dhcp.DefaultConfDir, - InterfaceIP: d.interfaceIP, + Network: d.cidr, + Name: d.vpc + "_" + d.bridge, + ConfDir: dhcp.DefaultConfDir, } switch d.mode { case "vxlan": + conf.VPCGateway = d.interfaceIP conf.VPCRoute = d.vpcCIDR case "bridge": if d.defaultRoute {