182 lines
5 KiB
Go
182 lines
5 KiB
Go
package subnet
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"os/exec"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"git.g3e.fr/syonad/two/internal/dhcp"
|
|
"git.g3e.fr/syonad/two/internal/netif"
|
|
"git.g3e.fr/syonad/two/internal/netns"
|
|
"git.g3e.fr/syonad/two/pkg/db/kv"
|
|
"git.g3e.fr/syonad/two/pkg/systemd"
|
|
|
|
"github.com/dgraph-io/badger/v4"
|
|
)
|
|
|
|
func CreateSubnet(db *badger.DB, subnetName string) error {
|
|
state, err := kv.GetFromDB(db, "subnet/"+subnetName+"/state")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if state != "creating" {
|
|
return nil
|
|
}
|
|
|
|
// lecture des paramètres depuis la DB
|
|
vpcName, err := kv.GetFromDB(db, "subnet/"+subnetName+"/vpc")
|
|
if err != nil {
|
|
return fmt.Errorf("get vpc: %w", err)
|
|
}
|
|
|
|
vxlanIDStr, err := kv.GetFromDB(db, "subnet/"+subnetName+"/vxlan_id")
|
|
if err != nil {
|
|
return fmt.Errorf("get vxlan_id: %w", err)
|
|
}
|
|
vxlanID, err := strconv.Atoi(vxlanIDStr)
|
|
if err != nil {
|
|
return fmt.Errorf("parse vxlan_id: %w", err)
|
|
}
|
|
|
|
localIface, err := kv.GetFromDB(db, "subnet/"+subnetName+"/local_iface")
|
|
if err != nil {
|
|
return fmt.Errorf("get local_iface: %w", err)
|
|
}
|
|
|
|
gatewayIPStr, err := kv.GetFromDB(db, "subnet/"+subnetName+"/gateway_ip")
|
|
if err != nil {
|
|
return fmt.Errorf("get gateway_ip: %w", err)
|
|
}
|
|
gatewayIP := net.ParseIP(gatewayIPStr)
|
|
if gatewayIP == nil {
|
|
return fmt.Errorf("invalid gateway_ip: %s", gatewayIPStr)
|
|
}
|
|
|
|
cidr, err := kv.GetFromDB(db, "subnet/"+subnetName+"/cidr")
|
|
if err != nil {
|
|
return fmt.Errorf("get cidr: %w", err)
|
|
}
|
|
_, subnet, err := net.ParseCIDR(cidr)
|
|
if err != nil {
|
|
return fmt.Errorf("parse cidr: %w", err)
|
|
}
|
|
|
|
// subnet_id = partie après le premier '-' (ex: "sn-00001" -> "00001")
|
|
subnetID := strings.SplitN(subnetName, "-", 2)[1]
|
|
bridge := "br-" + subnetID
|
|
vxlanIface := fmt.Sprintf("vxlan-%d", vxlanID)
|
|
|
|
// veth pair
|
|
if err := netif.CreateVethToNetns("v-"+subnetID+"-e", "v-"+subnetID+"-i", "/var/run/netns/"+vpcName, 1500); err != nil {
|
|
return fmt.Errorf("create veth: %w", err)
|
|
}
|
|
|
|
// bridge dans le root netns
|
|
if err := netif.CreateBridge(bridge, 1500); err != nil {
|
|
return fmt.Errorf("create bridge: %w", err)
|
|
}
|
|
|
|
// bridge dans le netns VPC
|
|
if err := netns.Call(vpcName, func() error {
|
|
return netif.CreateBridge(bridge, 1500)
|
|
}); err != nil {
|
|
return fmt.Errorf("create bridge in netns: %w", err)
|
|
}
|
|
|
|
// vxlan
|
|
if err := netif.CreateVxlan(vxlanIface, vxlanID, localIface); err != nil {
|
|
return fmt.Errorf("create vxlan: %w", err)
|
|
}
|
|
|
|
// ajout des interfaces dans les bridges
|
|
if err := netif.BridgeSetMaster("v-"+subnetID+"-e", bridge); err != nil {
|
|
return fmt.Errorf("add veth-e to bridge: %w", err)
|
|
}
|
|
if err := netns.Call(vpcName, func() error {
|
|
return netif.BridgeSetMaster("v-"+subnetID+"-i", bridge)
|
|
}); err != nil {
|
|
return fmt.Errorf("add veth-i to bridge in netns: %w", err)
|
|
}
|
|
if err := netif.BridgeSetMaster(vxlanIface, bridge); err != nil {
|
|
return fmt.Errorf("add vxlan to bridge: %w", err)
|
|
}
|
|
|
|
// montée des interfaces dans le root netns
|
|
for _, iface := range []string{"v-" + subnetID + "-e", vxlanIface, bridge} {
|
|
if err := netif.LinkSetUp(iface); err != nil {
|
|
return fmt.Errorf("set up %s: %w", iface, err)
|
|
}
|
|
}
|
|
|
|
// montée des interfaces dans le netns VPC
|
|
if err := netns.Call(vpcName, func() error {
|
|
for _, iface := range []string{"v-" + subnetID + "-i", bridge} {
|
|
if err := netif.LinkSetUp(iface); err != nil {
|
|
return fmt.Errorf("set up %s: %w", iface, err)
|
|
}
|
|
}
|
|
return nil
|
|
}); err != nil {
|
|
return fmt.Errorf("set up interfaces in netns: %w", err)
|
|
}
|
|
|
|
// IP gateway (/32) sur le bridge interne
|
|
if err := netns.Call(vpcName, func() error {
|
|
return netif.AddrAdd(bridge, gatewayIP)
|
|
}); err != nil {
|
|
return fmt.Errorf("add addr to bridge in netns: %w", err)
|
|
}
|
|
|
|
// route subnet (scope link) dans le netns VPC
|
|
if err := netns.Call(vpcName, func() error {
|
|
return netif.RouteAdd(bridge, subnet)
|
|
}); err != nil {
|
|
return fmt.Errorf("add route in netns: %w", err)
|
|
}
|
|
|
|
// ebtables : drop ARP Request vers la gateway sur ce bridge
|
|
if err := exec.Command("ebtables", "-A", "FORWARD",
|
|
"--out-interface", bridge,
|
|
"-p", "arp",
|
|
"--arp-op", "Request",
|
|
"--arp-ip-dst", gatewayIP.String(),
|
|
"-j", "DROP").Run(); err != nil {
|
|
return fmt.Errorf("ebtables arp rule: %w", err)
|
|
}
|
|
|
|
// ebtables : drop trafic DHCP sur ce bridge
|
|
if err := exec.Command("ebtables", "-A", "FORWARD",
|
|
"--out-interface", bridge,
|
|
"-p", "IPv4",
|
|
"--ip-protocol", "udp",
|
|
"--ip-source-port", "67:68",
|
|
"--ip-destination-port", "67:68",
|
|
"-j", "DROP").Run(); err != nil {
|
|
return fmt.Errorf("ebtables dhcp rule: %w", err)
|
|
}
|
|
|
|
// génération de la config dnsmasq et démarrage du service
|
|
conf := dhcp.Config{
|
|
Network: subnet,
|
|
Gateway: gatewayIP,
|
|
Name: vpcName + "_" + bridge,
|
|
ConfDir: "/etc/dnsmasq.d",
|
|
}
|
|
if _, err := dhcp.GenerateConfig(conf); err != nil {
|
|
return fmt.Errorf("generate dhcp config: %w", err)
|
|
}
|
|
|
|
svc, err := systemd.New()
|
|
if err != nil {
|
|
return fmt.Errorf("connect to systemd: %w", err)
|
|
}
|
|
defer svc.Close()
|
|
|
|
if err := svc.Start("dnsmasq@" + conf.Name + ".service"); err != nil {
|
|
return fmt.Errorf("start dnsmasq: %w", err)
|
|
}
|
|
|
|
return kv.AddInDB(db, "subnet/"+subnetName+"/state", "created")
|
|
}
|