plugin.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  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. "bufio"
  7. "encoding/binary"
  8. "errors"
  9. "fmt"
  10. "io"
  11. "math/rand"
  12. "net"
  13. "os"
  14. "strings"
  15. "time"
  16. "github.com/coredhcp/coredhcp/handler"
  17. "github.com/coredhcp/coredhcp/logger"
  18. "github.com/coredhcp/coredhcp/plugins"
  19. "github.com/insomniacslk/dhcp/dhcpv4"
  20. "github.com/insomniacslk/dhcp/dhcpv6"
  21. )
  22. var log = logger.GetLogger("plugins/range")
  23. // Plugin wraps plugin registration information
  24. var Plugin = plugins.Plugin{
  25. Name: "range",
  26. Setup6: setup6,
  27. Setup4: setup4,
  28. }
  29. //Record holds an IP lease record
  30. type Record struct {
  31. IP net.IP
  32. expires time.Time
  33. }
  34. // various global variables
  35. var (
  36. // Recordsv4 holds a MAC -> IP address and lease time mapping
  37. Recordsv4 map[string]*Record
  38. Recordsv6 map[string]*Record
  39. LeaseTime time.Duration
  40. filename string
  41. ipRangeStart net.IP
  42. ipRangeEnd net.IP
  43. )
  44. // loadRecords loads the DHCPv6/v4 Records global map with records stored on
  45. // the specified file. The records have to be one per line, a mac address and an
  46. // IP address.
  47. func loadRecords(r io.Reader, v6 bool) (map[string]*Record, error) {
  48. sc := bufio.NewScanner(r)
  49. records := make(map[string]*Record)
  50. for sc.Scan() {
  51. line := sc.Text()
  52. if len(line) == 0 {
  53. continue
  54. }
  55. tokens := strings.Fields(line)
  56. if len(tokens) != 3 {
  57. return nil, fmt.Errorf("malformed line, want 3 fields, got %d: %s", len(tokens), line)
  58. }
  59. hwaddr, err := net.ParseMAC(tokens[0])
  60. if err != nil {
  61. return nil, fmt.Errorf("malformed hardware address: %s", tokens[0])
  62. }
  63. ipaddr := net.ParseIP(tokens[1])
  64. if v6 {
  65. if len(ipaddr) == net.IPv6len {
  66. return nil, fmt.Errorf("expected an IPv6 address, got: %v", ipaddr)
  67. }
  68. } else {
  69. if ipaddr.To4() == nil {
  70. return nil, fmt.Errorf("expected an IPv4 address, got: %v", ipaddr)
  71. }
  72. }
  73. expires, err := time.Parse(time.RFC3339, tokens[2])
  74. if err != nil {
  75. return nil, fmt.Errorf("expected time of exipry in RFC3339 format, got: %v", tokens[2])
  76. }
  77. records[hwaddr.String()] = &Record{IP: ipaddr, expires: expires}
  78. }
  79. return records, nil
  80. }
  81. // Handler6 handles DHCPv6 packets for the file plugin
  82. func Handler6(req, resp dhcpv6.DHCPv6) (dhcpv6.DHCPv6, bool) {
  83. // TODO add IPv6 netmask to the response
  84. return resp, false
  85. }
  86. // Handler4 handles DHCPv4 packets for the range plugin
  87. func Handler4(req, resp *dhcpv4.DHCPv4) (*dhcpv4.DHCPv4, bool) {
  88. record, ok := Recordsv4[req.ClientHWAddr.String()]
  89. if !ok {
  90. log.Printf("MAC address %s is new, leasing new IPv4 address", req.ClientHWAddr.String())
  91. rec, err := createIP(ipRangeStart, ipRangeEnd)
  92. if err != nil {
  93. log.Error(err)
  94. return nil, true
  95. }
  96. err = saveIPAddress(req.ClientHWAddr, rec)
  97. if err != nil {
  98. log.Printf("SaveIPAddress for MAC %s failed: %v", req.ClientHWAddr.String(), err)
  99. }
  100. Recordsv4[req.ClientHWAddr.String()] = rec
  101. record = rec
  102. }
  103. resp.YourIPAddr = record.IP
  104. resp.Options.Update(dhcpv4.OptIPAddressLeaseTime(LeaseTime))
  105. log.Printf("found IP address %s for MAC %s", record.IP, req.ClientHWAddr.String())
  106. return resp, false
  107. }
  108. func setup6(args ...string) (handler.Handler6, error) {
  109. // TODO setup function for IPv6
  110. log.Warning("not implemented for IPv6")
  111. return Handler6, nil
  112. }
  113. func setup4(args ...string) (handler.Handler4, error) {
  114. _, h4, err := setupRange(false, args...)
  115. return h4, err
  116. }
  117. func setupRange(v6 bool, args ...string) (handler.Handler6, handler.Handler4, error) {
  118. var err error
  119. if len(args) < 4 {
  120. return nil, nil, fmt.Errorf("invalid number of arguments, want: 4 (file name, start IP, end IP, lease time), got: %d", len(args))
  121. }
  122. filename = args[0]
  123. if filename == "" {
  124. return nil, nil, errors.New("file name cannot be empty")
  125. }
  126. ipRangeStart = net.ParseIP(args[1])
  127. if ipRangeStart.To4() == nil {
  128. return nil, nil, fmt.Errorf("invalid IPv4 address: %v", args[1])
  129. }
  130. ipRangeEnd = net.ParseIP(args[2])
  131. if ipRangeEnd.To4() == nil {
  132. return nil, nil, fmt.Errorf("invalid IPv4 address: %v", args[2])
  133. }
  134. if binary.BigEndian.Uint32(ipRangeStart.To4()) >= binary.BigEndian.Uint32(ipRangeEnd.To4()) {
  135. return nil, nil, errors.New("start of IP range has to be lower than the end of an IP range")
  136. }
  137. LeaseTime, err = time.ParseDuration(args[3])
  138. if err != nil {
  139. return Handler6, Handler4, fmt.Errorf("invalid duration: %v", args[3])
  140. }
  141. r, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0640)
  142. defer func() {
  143. if err := r.Close(); err != nil {
  144. log.Warningf("Failed to close file %s: %v", filename, err)
  145. }
  146. }()
  147. if err != nil {
  148. return nil, nil, fmt.Errorf("cannot open lease file %s: %v", filename, err)
  149. }
  150. if v6 {
  151. Recordsv6, err = loadRecords(r, true)
  152. } else {
  153. Recordsv4, err = loadRecords(r, false)
  154. }
  155. if err != nil {
  156. return nil, nil, fmt.Errorf("failed to load records: %v", err)
  157. }
  158. rand.Seed(time.Now().Unix())
  159. if v6 {
  160. log.Printf("Loaded %d DHCPv6 leases from %s", len(Recordsv6), filename)
  161. } else {
  162. log.Printf("Loaded %d DHCPv4 leases from %s", len(Recordsv4), filename)
  163. }
  164. return Handler6, Handler4, nil
  165. }
  166. // createIP allocates a new lease in the provided range.
  167. // TODO this is not concurrency-safe
  168. func createIP(rangeStart net.IP, rangeEnd net.IP) (*Record, error) {
  169. ip := make([]byte, 4)
  170. rangeStartInt := binary.BigEndian.Uint32(rangeStart.To4())
  171. rangeEndInt := binary.BigEndian.Uint32(rangeEnd.To4())
  172. binary.BigEndian.PutUint32(ip, random(rangeStartInt, rangeEndInt))
  173. taken := checkIfTaken(ip)
  174. for taken {
  175. ipInt := binary.BigEndian.Uint32(ip)
  176. ipInt++
  177. binary.BigEndian.PutUint32(ip, ipInt)
  178. if ipInt > rangeEndInt {
  179. break
  180. }
  181. taken = checkIfTaken(ip)
  182. }
  183. for taken {
  184. ipInt := binary.BigEndian.Uint32(ip)
  185. ipInt--
  186. binary.BigEndian.PutUint32(ip, ipInt)
  187. if ipInt < rangeStartInt {
  188. return &Record{}, errors.New("no new IP addresses available")
  189. }
  190. taken = checkIfTaken(ip)
  191. }
  192. return &Record{IP: ip, expires: time.Now().Add(LeaseTime)}, nil
  193. }
  194. func random(min uint32, max uint32) uint32 {
  195. return uint32(rand.Intn(int(max-min))) + min
  196. }
  197. // check if an IP address is already leased. DHCPv4 only.
  198. func checkIfTaken(ip net.IP) bool {
  199. taken := false
  200. for _, v := range Recordsv4 {
  201. if v.IP.String() == ip.String() && (v.expires.After(time.Now())) {
  202. taken = true
  203. break
  204. }
  205. }
  206. return taken
  207. }
  208. func saveIPAddress(mac net.HardwareAddr, record *Record) error {
  209. f, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
  210. if err != nil {
  211. return err
  212. }
  213. defer f.Close()
  214. _, err = f.WriteString(mac.String() + " " + record.IP.String() + " " + record.expires.Format(time.RFC3339) + "\n")
  215. if err != nil {
  216. return err
  217. }
  218. err = f.Sync()
  219. if err != nil {
  220. return err
  221. }
  222. return nil
  223. }