package leases import ( "encoding/json" "errors" "fmt" "os" "path/filepath" "time" "github.com/adrianokf/go-dhcp/pkg/types" "go.uber.org/zap" ) type LeaseState uint8 const ( Offered LeaseState = iota Requested ) const DumpFile = "/var/lib/go-dhcp/leases.json" type Lease struct { TransactionId types.TxId `json:"TransactionID"` TTL time.Time `json:"TTL"` State LeaseState `json:"State"` ClientAddr types.HwAddr `json:"ClientAddr"` Address types.Ipv4Addr `json:"Address"` } type ILeaseManager interface { Request(xid types.TxId, clientAddr types.HwAddr) (*Lease, error) Release(l Lease) error Lookup(xid types.TxId) error } type LeaseManager struct { leases map[types.TxId]Lease count byte } func (m LeaseManager) DumpLeases() { err := os.MkdirAll(filepath.Dir(DumpFile), 0755) if err != nil { zap.S().Panic(err) } f, err := os.Create(DumpFile) if err != nil { zap.S().Panic(err) } defer f.Close() leases := make([]Lease, len(m.leases)) for _, l := range m.leases { leases = append(leases, l) } enc := json.NewEncoder(f) enc.Encode(leases) f.Sync() } func LoadLeases() (map[types.TxId]Lease, error) { var leaseMap = make(map[types.TxId]Lease) data, err := os.ReadFile(DumpFile) if err != nil { return leaseMap, err } var leases []Lease json.Unmarshal(data, &leases) for _, l := range leases { leaseMap[l.TransactionId] = l } return leaseMap, nil } func NewLeaseManager() *LeaseManager { leases, err := LoadLeases() if err != nil { zap.S().Warnf("Could not restore saved leases from %s: %s", DumpFile, err) } m := &LeaseManager{ leases: leases, } return m } func (m *LeaseManager) Request(xid types.TxId, clientAddr types.HwAddr) (*Lease, error) { zap.S().Debugf("LeaseManager.Request(%v, %v)", xid, clientAddr) if m.count > 254 { return nil, errors.New("lease address pool exhausted") } assigned := [4]byte{10, 0, 0, m.count + 2} lease := Lease{ TransactionId: xid, TTL: time.Now().Add(1 * time.Hour), State: Requested, ClientAddr: clientAddr, Address: assigned, } m.leases[xid] = lease m.count += 1 zap.S().Debug("lease=", lease) m.DumpLeases() return &lease, nil } func (m LeaseManager) Release(l Lease) error { _, found := m.leases[l.TransactionId] if !found { return fmt.Errorf("invalid lease %v", l) } delete(m.leases, l.TransactionId) m.DumpLeases() return nil } func (m LeaseManager) Lookup(xid types.TxId) (*Lease, error) { lease, found := m.leases[xid] if found { return &lease, nil } return nil, fmt.Errorf("no lease found for xid %v", xid) }