From 4d78e2deb58872eafae29869b60d2ff6baa76a90 Mon Sep 17 00:00:00 2001 From: CMGS Date: Thu, 3 Sep 2026 13:25:29 +0800 Subject: [PATCH 1/3] fix: resolve --lease-file under --state-dir The daemon hardcoded /var/lib/cocoon/net/leases.json while teardown deletes /leases.json, so a custom --state-dir made the daemon persist leases outside the state directory and teardown remove a file nothing wrote. The flag now defaults to empty and resolves to /leases.json at run time; the default installation path is unchanged. Two adjacent output fixes: --primary-nic help claimed auto-detection when the real fallback is platform.DefaultNIC, and adopt --dry-run did not list the ENI IDs it records into pool.json for teardown to delete. --- cmd/adopt.go | 1 + cmd/daemon.go | 5 ++--- cmd/teardown.go | 2 -- cmd/utils.go | 13 +++++++++++-- cmd/utils_test.go | 21 +++++++++++++++++++++ docs/configuration.md | 2 +- docs/dhcp.md | 2 +- 7 files changed, 37 insertions(+), 9 deletions(-) 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/cmd/daemon.go b/cmd/daemon.go index c3b5b16..20e88e5 100644 --- a/cmd/daemon.go +++ b/cmd/daemon.go @@ -24,7 +24,6 @@ import ( ) const ( - defaultLeaseFile = "/var/lib/cocoon/net/leases.json" defaultControlSocket = "/run/cocoon-net/control.sock" defaultMetricsAddr = ":9092" @@ -52,7 +51,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", "", "lease persistence file (default /leases.json)") 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)") @@ -109,7 +108,7 @@ func runDaemon(cmd *cobra.Command, _ []string) error { Gateway: gateway, SubnetMask: ipNet.Mask, DNSServers: dnsIPs, - LeaseFile: flagLeaseFile, + LeaseFile: resolveLeaseFile(), ControlSocket: flagControlSocket, }, poolIPs) diff --git a/cmd/teardown.go b/cmd/teardown.go index f6e9b3c..213ec51 100644 --- a/cmd/teardown.go +++ b/cmd/teardown.go @@ -14,8 +14,6 @@ import ( "github.com/cocoonstack/cocoon-net/platform" ) -const leaseFileName = "leases.json" - func newTeardownCmd() *cobra.Command { cmd := &cobra.Command{ Use: "teardown", diff --git a/cmd/utils.go b/cmd/utils.go index 2c69d89..d09bc1d 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -1,9 +1,11 @@ package cmd import ( + "cmp" "context" "fmt" "net" + "path/filepath" "slices" "strings" @@ -13,7 +15,10 @@ import ( "github.com/cocoonstack/cocoon-net/pool" ) -const defaultStateDir = "/var/lib/cocoon/net" +const ( + defaultStateDir = "/var/lib/cocoon/net" + leaseFileName = "leases.json" +) var ( flagPlatform string @@ -38,7 +43,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") @@ -91,6 +96,10 @@ func resolveSubnet() error { return nil } +func resolveLeaseFile() string { + return cmp.Or(flagLeaseFile, filepath.Join(flagStateDir, leaseFileName)) +} + func splitTrim(s, sep string) []string { parts := strings.Split(s, sep) for i := range parts { diff --git a/cmd/utils_test.go b/cmd/utils_test.go index 4015d49..34d2a4e 100644 --- a/cmd/utils_test.go +++ b/cmd/utils_test.go @@ -61,3 +61,24 @@ func TestResolveSubnet(t *testing.T) { }) } } + +func TestResolveLeaseFile(t *testing.T) { + tests := []struct { + name string + stateDir string + leaseFile string + want string + }{ + {"default state dir", defaultStateDir, "", "/var/lib/cocoon/net/leases.json"}, + {"custom state dir", "/srv/cocoon/net", "", "/srv/cocoon/net/leases.json"}, + {"explicit lease file wins", "/srv/cocoon/net", "/run/cocoon/leases.json", "/run/cocoon/leases.json"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + flagStateDir, flagLeaseFile = tt.stateDir, tt.leaseFile + if got := resolveLeaseFile(); got != tt.want { + t.Errorf("resolveLeaseFile() = %q, want %q", got, tt.want) + } + }) + } +} diff --git a/docs/configuration.md b/docs/configuration.md index 97fa430..8d636ac 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -16,7 +16,7 @@ variables below; runtime state (what was provisioned, and for whom) lives in | `--primary-nic` | `eth0` (Volcengine) / `ens4` (other platforms) | Host primary NIC | | `--dns` | `8.8.8.8,1.1.1.1` | DNS servers for DHCP clients | | `--state-dir` | `/var/lib/cocoon/net` | State directory for `pool.json` | -| `--lease-file` | `/var/lib/cocoon/net/leases.json` (independent of `--state-dir`) | (daemon) DHCP lease persistence file | +| `--lease-file` | `/leases.json` | (daemon) DHCP lease persistence file | | `--control-socket` | `/run/cocoon-net/control.sock` | (daemon) Root-only Unix socket used by local VM lifecycle managers to reclaim leases; empty to disable | | `--drop-cidr` | none | (repeatable, `init`/`adopt`) Destination CIDR to DROP at `FORWARD` for VM traffic -- see [DHCP: traffic isolation](dhcp.md#traffic-isolation) | | `--drop-internal-access` | `false` | (`init`/`adopt`) DROP `FORWARD` traffic within the node's own `--subnet` | diff --git a/docs/dhcp.md b/docs/dhcp.md index 43234a4..e0c0f75 100644 --- a/docs/dhcp.md +++ b/docs/dhcp.md @@ -25,7 +25,7 @@ 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 + `/leases.json`) on every allocation/release, and reloaded at daemon startup so a restart doesn't strand or double-assign leases. From efbc43e9773dd23cc9821fe8d4bb0f03eeb3d147 Mon Sep 17 00:00:00 2001 From: CMGS Date: Thu, 3 Sep 2026 13:34:34 +0800 Subject: [PATCH 2/3] review: drop restated comment from ip_test.go --- platform/ip_test.go | 1 - 1 file changed, 1 deletion(-) 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) From 1a07864c197dc7a23e2506dd0033ed3f6ef4fb54 Mon Sep 17 00:00:00 2001 From: CMGS Date: Thu, 3 Sep 2026 13:34:43 +0800 Subject: [PATCH 3/3] docs: record why volcengine Adopt is a no-op --- platform/volcengine/adopt.go | 1 + 1 file changed, 1 insertion(+) 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 }