mirror of https://github.com/jetkvm/kvm.git
248 lines
6.9 KiB
Go
248 lines
6.9 KiB
Go
package jetdhcpc
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/jetkvm/kvm/internal/network/types"
|
|
)
|
|
|
|
const (
|
|
// DefaultStateDir is the default state directory
|
|
DefaultStateDir = "/var/run/"
|
|
// DHCPStateFile is the name of the DHCP state file
|
|
DHCPStateFile = "jetkvm_dhcp_state.json"
|
|
)
|
|
|
|
// DHCPState represents the persistent state of DHCP clients
|
|
type DHCPState struct {
|
|
InterfaceStates map[string]*InterfaceDHCPState `json:"interface_states"`
|
|
LastUpdated time.Time `json:"last_updated"`
|
|
Version string `json:"version"`
|
|
}
|
|
|
|
// InterfaceDHCPState represents the DHCP state for a specific interface
|
|
type InterfaceDHCPState struct {
|
|
InterfaceName string `json:"interface_name"`
|
|
IPv4Enabled bool `json:"ipv4_enabled"`
|
|
IPv6Enabled bool `json:"ipv6_enabled"`
|
|
IPv4Lease *Lease `json:"ipv4_lease,omitempty"`
|
|
IPv6Lease *Lease `json:"ipv6_lease,omitempty"`
|
|
LastRenewal time.Time `json:"last_renewal"`
|
|
Config *types.NetworkConfig `json:"config,omitempty"`
|
|
}
|
|
|
|
// SaveState saves the current DHCP state to disk
|
|
func (c *Client) SaveState(state *DHCPState) error {
|
|
if state == nil {
|
|
return fmt.Errorf("state cannot be nil")
|
|
}
|
|
|
|
// Return error if state directory doesn't exist
|
|
if _, err := os.Stat(c.stateDir); os.IsNotExist(err) {
|
|
return fmt.Errorf("state directory does not exist: %w", err)
|
|
}
|
|
|
|
// Update timestamp
|
|
state.LastUpdated = time.Now()
|
|
state.Version = "1.0"
|
|
|
|
// Serialize state
|
|
data, err := json.MarshalIndent(state, "", " ")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal state: %w", err)
|
|
}
|
|
|
|
// Write to temporary file first, then rename to ensure atomic operation
|
|
tmpFile, err := os.CreateTemp(c.stateDir, DHCPStateFile)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create temporary file: %w", err)
|
|
}
|
|
defer tmpFile.Close()
|
|
|
|
if err := os.WriteFile(tmpFile.Name(), data, 0644); err != nil {
|
|
return fmt.Errorf("failed to write state file: %w", err)
|
|
}
|
|
|
|
stateFile := filepath.Join(c.stateDir, DHCPStateFile)
|
|
if err := os.Rename(tmpFile.Name(), stateFile); err != nil {
|
|
os.Remove(tmpFile.Name())
|
|
return fmt.Errorf("failed to rename state file: %w", err)
|
|
}
|
|
|
|
c.l.Debug().Str("file", stateFile).Msg("DHCP state saved")
|
|
return nil
|
|
}
|
|
|
|
// LoadState loads the DHCP state from disk
|
|
func (c *Client) LoadState() (*DHCPState, error) {
|
|
stateFile := filepath.Join(c.stateDir, DHCPStateFile)
|
|
|
|
// Check if state file exists
|
|
if _, err := os.Stat(stateFile); os.IsNotExist(err) {
|
|
c.l.Debug().Msg("No existing DHCP state file found")
|
|
return &DHCPState{
|
|
InterfaceStates: make(map[string]*InterfaceDHCPState),
|
|
LastUpdated: time.Now(),
|
|
Version: "1.0",
|
|
}, nil
|
|
}
|
|
|
|
// Read state file
|
|
data, err := os.ReadFile(stateFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read state file: %w", err)
|
|
}
|
|
|
|
// Deserialize state
|
|
var state DHCPState
|
|
if err := json.Unmarshal(data, &state); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal state: %w", err)
|
|
}
|
|
|
|
// Initialize interface states map if nil
|
|
if state.InterfaceStates == nil {
|
|
state.InterfaceStates = make(map[string]*InterfaceDHCPState)
|
|
}
|
|
|
|
c.l.Debug().Str("file", stateFile).Msg("DHCP state loaded")
|
|
return &state, nil
|
|
}
|
|
|
|
// UpdateInterfaceState updates the state for a specific interface
|
|
func (c *Client) UpdateInterfaceState(ifaceName string, state *InterfaceDHCPState) error {
|
|
// Load current state
|
|
currentState, err := c.LoadState()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load current state: %w", err)
|
|
}
|
|
|
|
// Update interface state
|
|
currentState.InterfaceStates[ifaceName] = state
|
|
|
|
// Save updated state
|
|
return c.SaveState(currentState)
|
|
}
|
|
|
|
// GetInterfaceState gets the state for a specific interface
|
|
func (c *Client) GetInterfaceState(ifaceName string) (*InterfaceDHCPState, error) {
|
|
state, err := c.LoadState()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to load state: %w", err)
|
|
}
|
|
|
|
return state.InterfaceStates[ifaceName], nil
|
|
}
|
|
|
|
// RemoveInterfaceState removes the state for a specific interface
|
|
func (c *Client) RemoveInterfaceState(ifaceName string) error {
|
|
// Load current state
|
|
currentState, err := c.LoadState()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load current state: %w", err)
|
|
}
|
|
|
|
// Remove interface state
|
|
delete(currentState.InterfaceStates, ifaceName)
|
|
|
|
// Save updated state
|
|
return c.SaveState(currentState)
|
|
}
|
|
|
|
// IsLeaseValid checks if a DHCP lease is still valid
|
|
func (c *Client) IsLeaseValid(lease *Lease) bool {
|
|
if lease == nil {
|
|
return false
|
|
}
|
|
|
|
// Check if lease has expired
|
|
if lease.LeaseExpiry == nil {
|
|
return false
|
|
}
|
|
|
|
return time.Now().Before(*lease.LeaseExpiry)
|
|
}
|
|
|
|
// ShouldRenewLease checks if a lease should be renewed
|
|
func (c *Client) ShouldRenewLease(lease *Lease) bool {
|
|
if !c.IsLeaseValid(lease) {
|
|
return false
|
|
}
|
|
|
|
expiry := *lease.LeaseExpiry
|
|
leaseTime := time.Now().Add(time.Duration(lease.LeaseTime) * time.Second)
|
|
|
|
// Renew if lease expires within 50% of its lifetime
|
|
leaseDuration := expiry.Sub(leaseTime)
|
|
renewalTime := leaseTime.Add(leaseDuration / 2)
|
|
|
|
return time.Now().After(renewalTime)
|
|
}
|
|
|
|
// CleanupExpiredStates removes expired states from the state file
|
|
func (c *Client) CleanupExpiredStates() error {
|
|
state, err := c.LoadState()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load state: %w", err)
|
|
}
|
|
|
|
cleaned := false
|
|
for ifaceName, ifaceState := range state.InterfaceStates {
|
|
// Remove interface state if both leases are expired
|
|
ipv4Valid := c.IsLeaseValid(ifaceState.IPv4Lease)
|
|
ipv6Valid := c.IsLeaseValid(ifaceState.IPv6Lease)
|
|
|
|
if !ipv4Valid && !ipv6Valid {
|
|
delete(state.InterfaceStates, ifaceName)
|
|
cleaned = true
|
|
c.l.Debug().Str("interface", ifaceName).Msg("Removed expired DHCP state")
|
|
}
|
|
}
|
|
|
|
if cleaned {
|
|
return c.SaveState(state)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetStateSummary returns a summary of the current state
|
|
func (c *Client) GetStateSummary() (map[string]interface{}, error) {
|
|
state, err := c.LoadState()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to load state: %w", err)
|
|
}
|
|
|
|
summary := map[string]interface{}{
|
|
"last_updated": state.LastUpdated,
|
|
"version": state.Version,
|
|
"interface_count": len(state.InterfaceStates),
|
|
"interfaces": make(map[string]interface{}),
|
|
}
|
|
|
|
interfaces := summary["interfaces"].(map[string]interface{})
|
|
for ifaceName, ifaceState := range state.InterfaceStates {
|
|
interfaceInfo := map[string]interface{}{
|
|
"ipv4_enabled": ifaceState.IPv4Enabled,
|
|
"ipv6_enabled": ifaceState.IPv6Enabled,
|
|
"last_renewal": ifaceState.LastRenewal,
|
|
// "ipv4_lease_valid": c.IsLeaseValid(ifaceState.IPv4Lease.(*Lease)),
|
|
// "ipv6_lease_valid": c.IsLeaseValid(ifaceState.IPv6Lease),
|
|
}
|
|
|
|
if ifaceState.IPv4Lease != nil {
|
|
interfaceInfo["ipv4_lease_expiry"] = ifaceState.IPv4Lease.LeaseExpiry
|
|
}
|
|
if ifaceState.IPv6Lease != nil {
|
|
interfaceInfo["ipv6_lease_expiry"] = ifaceState.IPv6Lease.LeaseExpiry
|
|
}
|
|
|
|
interfaces[ifaceName] = interfaceInfo
|
|
}
|
|
|
|
return summary, nil
|
|
}
|