f-34: route: add default gateway full gestion
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
This commit is contained in:
parent
d15454d35b
commit
46596f8142
13 changed files with 396 additions and 51 deletions
|
|
@ -43,11 +43,13 @@ func createSubnet(db *badger.DB, subnetName string, d subnetData) error {
|
|||
}
|
||||
|
||||
switch d.mode {
|
||||
case "vxlan":
|
||||
case ModeVxlan:
|
||||
if err := setupVxlanHost(d, vethE); err != nil {
|
||||
return err
|
||||
}
|
||||
case "bridge":
|
||||
case ModePublicIP:
|
||||
return fmt.Errorf("subnet mode %q: host network setup is not implemented yet", d.mode)
|
||||
case ModeBridge:
|
||||
if err := netif.BridgeSetMaster(vethE, d.localIface); err != nil {
|
||||
return fmt.Errorf("add veth-e to bridge: %w", err)
|
||||
}
|
||||
|
|
@ -141,18 +143,12 @@ func startDHCP(db *badger.DB, subnetName string, d subnetData) error {
|
|||
ConfDir: dhcp.DefaultConfDir,
|
||||
InterfaceIP: d.interfaceIP,
|
||||
}
|
||||
switch d.mode {
|
||||
case "vxlan":
|
||||
conf.VPCRoute = d.vpcCIDR
|
||||
case "bridge":
|
||||
if d.defaultRoute {
|
||||
gw, err := netif.GetDefaultGateway()
|
||||
if err != nil {
|
||||
return fmt.Errorf("get default gateway: %w", err)
|
||||
}
|
||||
conf.DefaultGateway = gw
|
||||
}
|
||||
defaultGateway, vpcRoute, err := dhcpRouting(d, netif.GetDefaultGateway)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conf.DefaultGateway = defaultGateway
|
||||
conf.VPCRoute = vpcRoute
|
||||
_, entries, err := dhcp.GenerateConfig(conf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("generate dhcp config: %w", err)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ type subnetData struct {
|
|||
cidr *net.IPNet
|
||||
vpcCIDR *net.IPNet
|
||||
defaultRoute bool
|
||||
gateway net.IP
|
||||
}
|
||||
|
||||
func loadSubnet(db *badger.DB, name string) (subnetData, error) {
|
||||
|
|
@ -85,6 +86,14 @@ func loadSubnet(db *badger.DB, name string) (subnetData, error) {
|
|||
}
|
||||
d.defaultRoute = defaultRouteStr == "true"
|
||||
|
||||
if gatewayStr, err := kv.GetFromDB(db, "subnet/"+name+"/gateway"); err == nil && gatewayStr != "" {
|
||||
gateway := net.ParseIP(gatewayStr)
|
||||
if gateway == nil {
|
||||
return d, fmt.Errorf("invalid gateway: %s", gatewayStr)
|
||||
}
|
||||
d.gateway = gateway
|
||||
}
|
||||
|
||||
vpcCIDRStr, err := kv.GetFromDB(db, "vpc/"+d.vpc+"/cidr")
|
||||
if err != nil {
|
||||
return d, fmt.Errorf("get vpc cidr: %w", err)
|
||||
|
|
|
|||
15
internal/subnet/mode.go
Normal file
15
internal/subnet/mode.go
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
package subnet
|
||||
|
||||
const (
|
||||
ModeVxlan = "vxlan"
|
||||
ModeBridge = "bridge"
|
||||
ModePublicIP = "public_ip"
|
||||
)
|
||||
|
||||
func ValidMode(mode string) bool {
|
||||
switch mode {
|
||||
case ModeVxlan, ModeBridge, ModePublicIP:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
34
internal/subnet/routing.go
Normal file
34
internal/subnet/routing.go
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
package subnet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
// dhcpRouting resolves what the DHCP server advertises to the guests of a subnet.
|
||||
//
|
||||
// The default route always points at the subnet gateway (interface_ip); default_route
|
||||
// swaps that next-hop for the supplied gateway, or for the deduced one when none was
|
||||
// supplied. The VPC route keeps interface_ip as its next-hop in every mode but bridge,
|
||||
// so that traffic to the VPC ranges never leaves through a public gateway.
|
||||
func dhcpRouting(d subnetData, deduceGateway func() (net.IP, error)) (net.IP, *net.IPNet, error) {
|
||||
defaultGateway := d.interfaceIP
|
||||
if d.defaultRoute {
|
||||
if d.gateway != nil {
|
||||
defaultGateway = d.gateway
|
||||
} else {
|
||||
deduced, err := deduceGateway()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("get default gateway: %w", err)
|
||||
}
|
||||
defaultGateway = deduced
|
||||
}
|
||||
}
|
||||
|
||||
var vpcRoute *net.IPNet
|
||||
if d.mode != ModeBridge {
|
||||
vpcRoute = d.vpcCIDR
|
||||
}
|
||||
|
||||
return defaultGateway, vpcRoute, nil
|
||||
}
|
||||
158
internal/subnet/routing_test.go
Normal file
158
internal/subnet/routing_test.go
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
package subnet
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func cidr(t *testing.T, s string) *net.IPNet {
|
||||
t.Helper()
|
||||
_, n, err := net.ParseCIDR(s)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseCIDR(%q) : %v", s, err)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func baseSubnet(t *testing.T, mode string) subnetData {
|
||||
t.Helper()
|
||||
return subnetData{
|
||||
mode: mode,
|
||||
interfaceIP: net.ParseIP("10.1.1.1").To4(),
|
||||
cidr: cidr(t, "10.1.0.0/23"),
|
||||
vpcCIDR: cidr(t, "192.168.0.0/16"),
|
||||
}
|
||||
}
|
||||
|
||||
func deduced(ip string) func() (net.IP, error) {
|
||||
return func() (net.IP, error) { return net.ParseIP(ip).To4(), nil }
|
||||
}
|
||||
|
||||
func neverDeduced(t *testing.T) func() (net.IP, error) {
|
||||
t.Helper()
|
||||
return func() (net.IP, error) {
|
||||
t.Error("la gateway de l'host ne doit pas être interrogée dans ce cas")
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
// --- next-hop de la route par défaut ---
|
||||
|
||||
func TestDhcpRouting_DefaultRouteUsesInterfaceIP(t *testing.T) {
|
||||
d := baseSubnet(t, ModeVxlan)
|
||||
|
||||
gw, _, err := dhcpRouting(d, neverDeduced(t))
|
||||
if err != nil {
|
||||
t.Fatalf("dhcpRouting : %v", err)
|
||||
}
|
||||
if gw.String() != "10.1.1.1" {
|
||||
t.Errorf("sans default_route le next-hop doit être l'interface_ip, obtenu %s", gw)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDhcpRouting_DefaultRouteUsesSuppliedGateway(t *testing.T) {
|
||||
d := baseSubnet(t, ModeVxlan)
|
||||
d.defaultRoute = true
|
||||
d.gateway = net.ParseIP("10.1.1.254").To4()
|
||||
|
||||
gw, _, err := dhcpRouting(d, neverDeduced(t))
|
||||
if err != nil {
|
||||
t.Fatalf("dhcpRouting : %v", err)
|
||||
}
|
||||
if gw.String() != "10.1.1.254" {
|
||||
t.Errorf("gateway fournie attendue, obtenu %s", gw)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDhcpRouting_DefaultRouteFallsBackToDeducedGateway(t *testing.T) {
|
||||
d := baseSubnet(t, ModeBridge)
|
||||
d.defaultRoute = true
|
||||
|
||||
gw, _, err := dhcpRouting(d, deduced("192.0.2.1"))
|
||||
if err != nil {
|
||||
t.Fatalf("dhcpRouting : %v", err)
|
||||
}
|
||||
if gw.String() != "192.0.2.1" {
|
||||
t.Errorf("gateway déduite attendue, obtenu %s", gw)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDhcpRouting_SuppliedGatewayIgnoredWithoutDefaultRoute(t *testing.T) {
|
||||
d := baseSubnet(t, ModeVxlan)
|
||||
d.gateway = net.ParseIP("10.1.1.254").To4()
|
||||
|
||||
gw, _, err := dhcpRouting(d, neverDeduced(t))
|
||||
if err != nil {
|
||||
t.Fatalf("dhcpRouting : %v", err)
|
||||
}
|
||||
if gw.String() != "10.1.1.1" {
|
||||
t.Errorf("gateway fournie sans default_route : ignorée en silence, next-hop attendu 10.1.1.1, obtenu %s", gw)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDhcpRouting_DeductionFailureIsReported(t *testing.T) {
|
||||
d := baseSubnet(t, ModeBridge)
|
||||
d.defaultRoute = true
|
||||
|
||||
_, _, err := dhcpRouting(d, func() (net.IP, error) { return nil, errors.New("pas de route") })
|
||||
if err == nil {
|
||||
t.Error("l'échec de déduction de la gateway doit remonter, pas produire une route muette")
|
||||
}
|
||||
}
|
||||
|
||||
// --- route VPC ---
|
||||
|
||||
func TestDhcpRouting_VPCRouteKeptInVxlan(t *testing.T) {
|
||||
_, route, err := dhcpRouting(baseSubnet(t, ModeVxlan), neverDeduced(t))
|
||||
if err != nil {
|
||||
t.Fatalf("dhcpRouting : %v", err)
|
||||
}
|
||||
if route == nil || route.String() != "192.168.0.0/16" {
|
||||
t.Errorf("route VPC attendue, obtenu %v", route)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDhcpRouting_VPCRouteKeptInPublicIP(t *testing.T) {
|
||||
d := baseSubnet(t, ModePublicIP)
|
||||
d.defaultRoute = true
|
||||
d.gateway = net.ParseIP("203.0.113.1").To4()
|
||||
|
||||
gw, route, err := dhcpRouting(d, neverDeduced(t))
|
||||
if err != nil {
|
||||
t.Fatalf("dhcpRouting : %v", err)
|
||||
}
|
||||
if route == nil || route.String() != "192.168.0.0/16" {
|
||||
t.Fatalf("route VPC attendue sur un subnet public, obtenu %v", route)
|
||||
}
|
||||
if gw.String() != "203.0.113.1" {
|
||||
t.Errorf("next-hop par défaut attendu 203.0.113.1, obtenu %s", gw)
|
||||
}
|
||||
// C'est tout l'intérêt du mode : la route VPC garde interface_ip comme next-hop,
|
||||
// donc le trafic interne ne sort jamais par la gateway publique.
|
||||
}
|
||||
|
||||
func TestDhcpRouting_NoVPCRouteInBridge(t *testing.T) {
|
||||
_, route, err := dhcpRouting(baseSubnet(t, ModeBridge), neverDeduced(t))
|
||||
if err != nil {
|
||||
t.Fatalf("dhcpRouting : %v", err)
|
||||
}
|
||||
if route != nil {
|
||||
t.Errorf("le mode bridge n'a pas de route VPC, obtenu %v", route)
|
||||
}
|
||||
}
|
||||
|
||||
// --- modes ---
|
||||
|
||||
func TestValidMode(t *testing.T) {
|
||||
for _, m := range []string{ModeVxlan, ModeBridge, ModePublicIP} {
|
||||
if !ValidMode(m) {
|
||||
t.Errorf("%q devrait être un mode valide", m)
|
||||
}
|
||||
}
|
||||
for _, m := range []string{"", "public", "vxlan ", "VXLAN"} {
|
||||
if ValidMode(m) {
|
||||
t.Errorf("%q ne devrait pas être un mode valide", m)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue