2023-04-18 13:12:11 +01:00
|
|
|
package dhcpd
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
2023-11-16 11:14:40 +00:00
|
|
|
"fmt"
|
2023-04-18 13:12:11 +01:00
|
|
|
"net"
|
|
|
|
"net/netip"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/AdguardTeam/golibs/errors"
|
|
|
|
"github.com/AdguardTeam/golibs/log"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// leaseExpireStatic is used to define the Expiry field for static
|
|
|
|
// leases.
|
|
|
|
//
|
|
|
|
// Deprecated: Remove it when migration of DHCP leases will be not needed.
|
|
|
|
leaseExpireStatic = 1
|
|
|
|
|
|
|
|
// dbFilename contains saved leases.
|
|
|
|
//
|
|
|
|
// Deprecated: Use dataFilename.
|
|
|
|
dbFilename = "leases.db"
|
|
|
|
)
|
|
|
|
|
2023-11-16 11:14:40 +00:00
|
|
|
// leaseJSON is the structure of stored lease in a legacy database.
|
2023-04-18 13:12:11 +01:00
|
|
|
//
|
2023-11-16 11:14:40 +00:00
|
|
|
// Deprecated: Use [dbLease].
|
2023-04-18 13:12:11 +01:00
|
|
|
type leaseJSON struct {
|
|
|
|
HWAddr []byte `json:"mac"`
|
|
|
|
IP []byte `json:"ip"`
|
|
|
|
Hostname string `json:"host"`
|
|
|
|
Expiry int64 `json:"exp"`
|
|
|
|
}
|
|
|
|
|
2023-11-16 11:14:40 +00:00
|
|
|
// readOldDB reads the old database from the given path.
|
|
|
|
func readOldDB(path string) (leases []*leaseJSON, err error) {
|
2023-06-29 12:34:06 +01:00
|
|
|
// #nosec G304 -- Trust this path, since it's taken from the old file name
|
|
|
|
// relative to the working directory and should generally be considered
|
|
|
|
// safe.
|
2023-11-16 11:14:40 +00:00
|
|
|
file, err := os.Open(path)
|
2023-04-18 13:12:11 +01:00
|
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
|
|
// Nothing to migrate.
|
2023-11-16 11:14:40 +00:00
|
|
|
return nil, nil
|
2023-04-18 13:12:11 +01:00
|
|
|
} else if err != nil {
|
|
|
|
// Don't wrap the error since it's informative enough as is.
|
2023-11-16 11:14:40 +00:00
|
|
|
return nil, err
|
2023-04-18 13:12:11 +01:00
|
|
|
}
|
2023-11-16 11:14:40 +00:00
|
|
|
defer func() { err = errors.WithDeferred(err, file.Close()) }()
|
2023-04-18 13:12:11 +01:00
|
|
|
|
2023-11-16 11:14:40 +00:00
|
|
|
leases = []*leaseJSON{}
|
|
|
|
err = json.NewDecoder(file).Decode(&leases)
|
2023-04-18 13:12:11 +01:00
|
|
|
if err != nil {
|
2023-11-16 11:14:40 +00:00
|
|
|
return nil, fmt.Errorf("decoding old db: %w", err)
|
2023-04-18 13:12:11 +01:00
|
|
|
}
|
|
|
|
|
2023-11-16 11:14:40 +00:00
|
|
|
return leases, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// migrateDB migrates stored leases if necessary.
|
|
|
|
func migrateDB(conf *ServerConfig) (err error) {
|
|
|
|
defer func() { err = errors.Annotate(err, "migrating db: %w") }()
|
|
|
|
|
|
|
|
oldLeasesPath := filepath.Join(conf.WorkDir, dbFilename)
|
|
|
|
dataDirPath := filepath.Join(conf.DataDir, dataFilename)
|
|
|
|
|
|
|
|
oldLeases, err := readOldDB(oldLeasesPath)
|
2023-04-18 13:12:11 +01:00
|
|
|
if err != nil {
|
|
|
|
// Don't wrap the error since it's informative enough as is.
|
|
|
|
return err
|
2023-11-16 11:14:40 +00:00
|
|
|
} else if oldLeases == nil {
|
|
|
|
// Nothing to migrate.
|
|
|
|
return nil
|
2023-04-18 13:12:11 +01:00
|
|
|
}
|
|
|
|
|
2023-11-16 11:14:40 +00:00
|
|
|
leases := make([]*dbLease, 0, len(oldLeases))
|
|
|
|
for _, l := range oldLeases {
|
|
|
|
l.IP = normalizeIP(l.IP)
|
|
|
|
ip, ok := netip.AddrFromSlice(l.IP)
|
2023-04-18 13:12:11 +01:00
|
|
|
if !ok {
|
2023-11-16 11:14:40 +00:00
|
|
|
log.Info("dhcp: invalid IP: %s", l.IP)
|
2023-04-18 13:12:11 +01:00
|
|
|
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2023-11-16 11:14:40 +00:00
|
|
|
leases = append(leases, &dbLease{
|
|
|
|
Expiry: time.Unix(l.Expiry, 0).Format(time.RFC3339),
|
|
|
|
Hostname: l.Hostname,
|
|
|
|
HWAddr: net.HardwareAddr(l.HWAddr).String(),
|
2023-04-18 13:12:11 +01:00
|
|
|
IP: ip,
|
2023-11-16 11:14:40 +00:00
|
|
|
IsStatic: l.Expiry == leaseExpireStatic,
|
|
|
|
})
|
2023-04-18 13:12:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
err = writeDB(dataDirPath, leases)
|
|
|
|
if err != nil {
|
2023-11-16 11:14:40 +00:00
|
|
|
// Don't wrap the error since an annotation deferred already.
|
2023-04-18 13:12:11 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return os.Remove(oldLeasesPath)
|
|
|
|
}
|
2023-11-16 11:14:40 +00:00
|
|
|
|
|
|
|
// normalizeIP converts the given IP address to IPv4 if it's IPv4-mapped IPv6,
|
|
|
|
// or leaves it as is otherwise.
|
|
|
|
func normalizeIP(ip net.IP) (normalized net.IP) {
|
|
|
|
normalized = ip.To4()
|
|
|
|
if normalized != nil {
|
|
|
|
return normalized
|
|
|
|
}
|
|
|
|
|
|
|
|
return ip
|
|
|
|
}
|