From 62a931d089ced72e47dfdbea639d22ebf793e187 Mon Sep 17 00:00:00 2001 From: CMGS Date: Thu, 3 Sep 2026 14:47:50 +0800 Subject: [PATCH 1/2] fix: teardown removes the lease file the daemon writes Teardown deleted /leases.json while the daemon persisted leases at its constant default /var/lib/cocoon/net/leases.json, so a custom --state-dir left the lease file behind. The default stays the constant path, independent of --state-dir because vk-cocoon's reader defaults to the same file; teardown removes exactly that file and the daemon creates its directory at start, since init only creates the state dir. --- cmd/daemon.go | 7 +++++-- cmd/teardown.go | 5 +---- cmd/utils.go | 8 ++++++-- docs/dhcp.md | 7 ++++--- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/cmd/daemon.go b/cmd/daemon.go index c3b5b16..c8695d4 100644 --- a/cmd/daemon.go +++ b/cmd/daemon.go @@ -9,6 +9,7 @@ import ( "net" "net/http" "os" + "path/filepath" "time" "github.com/projecteru2/core/log" @@ -24,7 +25,6 @@ import ( ) const ( - defaultLeaseFile = "/var/lib/cocoon/net/leases.json" defaultControlSocket = "/run/cocoon-net/control.sock" defaultMetricsAddr = ":9092" @@ -52,7 +52,7 @@ they expire.`, RunE: runDaemon, } cmd.Flags().StringVar(&flagStateDir, "state-dir", defaultStateDir, "directory containing pool.json") - cmd.Flags().StringVar(&flagLeaseFile, "lease-file", defaultLeaseFile, "path to lease persistence file") + cmd.Flags().StringVar(&flagLeaseFile, "lease-file", defaultLeaseFile, "lease persistence file") cmd.Flags().StringVar(&flagControlSocket, "control-socket", cmp.Or(os.Getenv("COCOON_NET_CONTROL_SOCKET"), defaultControlSocket), "root-only Unix socket for local lease lifecycle operations (empty to disable)") cmd.Flags().BoolVar(&flagSkipIPTables, "skip-iptables", false, "skip iptables setup (for pre-configured nodes)") cmd.Flags().StringVar(&flagMetricsAddr, "metrics-addr", cmp.Or(os.Getenv("COCOON_NET_METRICS_ADDR"), defaultMetricsAddr), "prometheus metrics listen address (empty to disable)") @@ -104,6 +104,9 @@ func runDaemon(cmd *cobra.Command, _ []string) error { } dnsIPs := parseIPs(dnsList) + if err := os.MkdirAll(filepath.Dir(flagLeaseFile), 0o750); err != nil { + return fmt.Errorf("create lease dir: %w", err) + } srv := dhcp.New(dhcp.Config{ Interface: node.BridgeName, Gateway: gateway, diff --git a/cmd/teardown.go b/cmd/teardown.go index f6e9b3c..6e34299 100644 --- a/cmd/teardown.go +++ b/cmd/teardown.go @@ -5,7 +5,6 @@ import ( "fmt" "io/fs" "os" - "path/filepath" "github.com/projecteru2/core/log" "github.com/spf13/cobra" @@ -14,8 +13,6 @@ import ( "github.com/cocoonstack/cocoon-net/platform" ) -const leaseFileName = "leases.json" - func newTeardownCmd() *cobra.Command { cmd := &cobra.Command{ Use: "teardown", @@ -67,7 +64,7 @@ func runTeardown(cmd *cobra.Command, _ []string) error { logger.Warnf(ctx, "delete pool state: %v", err) } - leasePath := filepath.Join(flagStateDir, leaseFileName) + leasePath := defaultLeaseFile if err := os.Remove(leasePath); err != nil && !errors.Is(err, fs.ErrNotExist) { logger.Warnf(ctx, "delete lease file %s: %v", leasePath, err) } diff --git a/cmd/utils.go b/cmd/utils.go index 2c69d89..4426062 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -13,7 +13,11 @@ import ( "github.com/cocoonstack/cocoon-net/pool" ) -const defaultStateDir = "/var/lib/cocoon/net" +const ( + defaultStateDir = "/var/lib/cocoon/net" + leaseFileName = "leases.json" + defaultLeaseFile = defaultStateDir + "/" + leaseFileName +) var ( flagPlatform string @@ -38,7 +42,7 @@ func registerCommonFlags(cmd *cobra.Command, defaultPoolSize int) { cmd.Flags().StringVar(&flagSubnet, "subnet", "", "VM subnet CIDR, e.g. 172.20.100.0/24 (required)") cmd.Flags().IntVar(&flagPoolSize, "pool-size", defaultPoolSize, "number of IPs in the pool") cmd.Flags().StringVar(&flagGateway, "gateway", "", "gateway IP on cni0 (default: first IP in subnet)") - cmd.Flags().StringVar(&flagPrimaryNIC, "primary-nic", "", "host primary NIC (auto-detect if empty)") + cmd.Flags().StringVar(&flagPrimaryNIC, "primary-nic", "", "host primary NIC (default: eth0 on volcengine, ens4 otherwise)") cmd.Flags().StringVar(&flagDNS, "dns", "8.8.8.8,1.1.1.1", "comma-separated DNS servers for DHCP clients") cmd.Flags().StringVar(&flagStateDir, "state-dir", defaultStateDir, "state directory") cmd.Flags().BoolVar(&flagDryRun, "dry-run", false, "show what would be done without making changes") diff --git a/docs/dhcp.md b/docs/dhcp.md index 43234a4..b0633a9 100644 --- a/docs/dhcp.md +++ b/docs/dhcp.md @@ -25,7 +25,8 @@ VPC-routable IP directly from this server. `204` after a successful or already-completed release, `400` for an invalid MAC, and `500` when persistence fails; callers may safely retry a `500`. - Leases are persisted to the `--lease-file` (default - `/var/lib/cocoon/net/leases.json`) on every allocation/release, and + `/var/lib/cocoon/net/leases.json`, independent of `--state-dir` so + vk-cocoon's default reader path matches) on every allocation/release, and reloaded at daemon startup so a restart doesn't strand or double-assign leases. @@ -125,8 +126,8 @@ The DHCP server binds UDP port 67 on all addresses, not just `cni0`; a host `dnsmasq` or `dhcpd` already holding that port makes the daemon fail to start. On `cocoon-net teardown`, the cloud resources (ENIs on Volcengine, the alias -range on GKE), the tagged `cocoon-net-drop` iptables rules, `pool.json`, and -`/leases.json` are all removed (on GKE the boot cron job that +range on GKE), the tagged `cocoon-net-drop` iptables rules, `pool.json`, and the +default lease file `/var/lib/cocoon/net/leases.json` are all removed (on GKE the boot cron job that reapplies the guest-agent route fix is removed too); a daemon started with a custom `--lease-file` keeps that file. The `cni0` bridge, the FORWARD ACCEPT / NAT MASQUERADE rules, and the CNI conflist are left in place. From cccb4b4fec2ccfd6508b9a007761b2719dbee5cc Mon Sep 17 00:00:00 2001 From: CMGS Date: Thu, 3 Sep 2026 14:47:50 +0800 Subject: [PATCH 2/2] review: enis dry-run row, volcengine Adopt note, drop a restating test comment --- cmd/adopt.go | 1 + platform/ip_test.go | 1 - platform/volcengine/adopt.go | 1 + 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/adopt.go b/cmd/adopt.go index 84d7aa2..89e4202 100644 --- a/cmd/adopt.go +++ b/cmd/adopt.go @@ -84,6 +84,7 @@ func runAdopt(cmd *cobra.Command, _ []string) error { fmt.Printf(" gateway: %s\n", gateway) fmt.Printf(" primary-nic: %s\n", primaryNIC) fmt.Printf(" secondary-nics: %s\n", strings.Join(secondaryNICs, ",")) + fmt.Printf(" enis: %s\n", cmp.Or(strings.Join(eniIDs, ","), "none")) if len(ips) > 0 { fmt.Printf(" pool-size: %d (first=%s, last=%s)\n", len(ips), ips[0], ips[len(ips)-1]) } else { diff --git a/platform/ip_test.go b/platform/ip_test.go index 7c53a86..7430002 100644 --- a/platform/ip_test.go +++ b/platform/ip_test.go @@ -45,7 +45,6 @@ func TestSubnetIPs_Slash24SkipsBroadcast(t *testing.T) { func TestSubnetIPs_Slash28(t *testing.T) { t.Parallel() - // /28 has 16 addresses, 14 hosts, minus gateway = 13. got, err := SubnetIPs("192.168.10.0/28", "192.168.10.1", 32) if err != nil { t.Fatalf("SubnetIPs: %v", err) diff --git a/platform/volcengine/adopt.go b/platform/volcengine/adopt.go index 3e5740c..b0f26e5 100644 --- a/platform/volcengine/adopt.go +++ b/platform/volcengine/adopt.go @@ -6,4 +6,5 @@ import ( "github.com/cocoonstack/cocoon-net/platform" ) +// Adopt is a no-op: the VPC routes the VM subnet to the secondary ENIs, so no host route hijacks it. func (v *Volcengine) Adopt(context.Context, *platform.Config) error { return nil }