plugin.go 6.4 KB

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