storage.go 2.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. // Copyright 2018-present the CoreDHCP Authors. All rights reserved
  2. // This source code is licensed under the MIT license found in the
  3. // LICENSE file in the root directory of this source tree.
  4. package rangeplugin
  5. import (
  6. "database/sql"
  7. "errors"
  8. "fmt"
  9. "net"
  10. _ "github.com/mattn/go-sqlite3"
  11. )
  12. func loadDB(path string) (*sql.DB, error) {
  13. db, err := sql.Open("sqlite3", fmt.Sprintf("file:%s", path))
  14. if err != nil {
  15. return nil, fmt.Errorf("failed to open database (%T): %w", err, err)
  16. }
  17. if _, err := db.Exec("create table if not exists leases4 (mac string not null, ip string not null, expiry int, primary key (mac, ip))"); err != nil {
  18. return nil, fmt.Errorf("table creation failed: %w", err)
  19. }
  20. return db, nil
  21. }
  22. // loadRecords loads the DHCPv6/v4 Records global map with records stored on
  23. // the specified file. The records have to be one per line, a mac address and an
  24. // IP address.
  25. func loadRecords(db *sql.DB) (map[string]*Record, error) {
  26. rows, err := db.Query("select mac, ip, expiry from leases4")
  27. if err != nil {
  28. return nil, fmt.Errorf("failed to query leases database: %w", err)
  29. }
  30. defer rows.Close()
  31. var (
  32. mac, ip string
  33. expiry int
  34. records = make(map[string]*Record)
  35. )
  36. for rows.Next() {
  37. if err := rows.Scan(&mac, &ip, &expiry); err != nil {
  38. return nil, fmt.Errorf("failed to scan row: %w", err)
  39. }
  40. hwaddr, err := net.ParseMAC(mac)
  41. if err != nil {
  42. return nil, fmt.Errorf("malformed hardware address: %s", mac)
  43. }
  44. ipaddr := net.ParseIP(ip)
  45. if ipaddr.To4() == nil {
  46. return nil, fmt.Errorf("expected an IPv4 address, got: %v", ipaddr)
  47. }
  48. records[hwaddr.String()] = &Record{IP: ipaddr, expires: expiry}
  49. }
  50. if err := rows.Err(); err != nil {
  51. return nil, fmt.Errorf("failed lease database row scanning: %w", err)
  52. }
  53. return records, nil
  54. }
  55. // saveIPAddress writes out a lease to storage
  56. func (p *PluginState) saveIPAddress(mac net.HardwareAddr, record *Record) error {
  57. stmt, err := p.leasedb.Prepare(`insert or replace into leases4(mac, ip, expiry) values (?, ?, ?)`)
  58. if err != nil {
  59. return fmt.Errorf("statement preparation failed: %w", err)
  60. }
  61. if _, err := stmt.Exec(
  62. mac.String(),
  63. record.IP.String(),
  64. record.expires,
  65. ); err != nil {
  66. return fmt.Errorf("record insert/update failed: %w", err)
  67. }
  68. return nil
  69. }
  70. // registerBackingDB installs a database connection string as the backing store for leases
  71. func (p *PluginState) registerBackingDB(filename string) error {
  72. if p.leasedb != nil {
  73. return errors.New("cannot swap out a lease database while running")
  74. }
  75. // We never close this, but that's ok because plugins are never stopped/unregistered
  76. newLeaseDB, err := loadDB(filename)
  77. if err != nil {
  78. return fmt.Errorf("failed to open lease database %s: %w", filename, err)
  79. }
  80. p.leasedb = newLeaseDB
  81. return nil
  82. }