plugin.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. // Package file enables static mapping of MAC <--> IP addresses.
  2. // The mapping is stored in a text file, where each mapping is described by one line containing
  3. // two fields separated by spaces: MAC address, and IP address. For example:
  4. //
  5. // $ cat file_leases.txt
  6. // 00:11:22:33:44:55 10.0.0.1
  7. // 01:23:45:67:89:01 10.0.10.10
  8. //
  9. // To specify the plugin configuration in the server6/server4 sections of the config file, just
  10. // pass the leases file name as plugin argument, e.g.:
  11. //
  12. // $ cat config.yml
  13. //
  14. // server6:
  15. // ...
  16. // plugins:
  17. // - file: "file_leases.txt"
  18. // ...
  19. //
  20. // If the file path is not absolute, it is relative to the cwd where coredhcp is run.
  21. package file
  22. import (
  23. "bytes"
  24. "errors"
  25. "fmt"
  26. "io/ioutil"
  27. "net"
  28. "strings"
  29. "github.com/coredhcp/coredhcp/handler"
  30. "github.com/coredhcp/coredhcp/logger"
  31. "github.com/coredhcp/coredhcp/plugins"
  32. "github.com/insomniacslk/dhcp/dhcpv4"
  33. "github.com/insomniacslk/dhcp/dhcpv6"
  34. )
  35. var log = logger.GetLogger("plugins/file")
  36. func init() {
  37. plugins.RegisterPlugin("file", setupFile6, setupFile4)
  38. }
  39. // StaticRecords holds a MAC -> IP address mapping
  40. var StaticRecords map[string]net.IP
  41. // DHCPv6Records and DHCPv4Records are mappings between MAC addresses in
  42. // form of a string, to network configurations.
  43. var (
  44. DHCPv6Records map[string]net.IP
  45. DHCPv4Records map[string]net.IP
  46. )
  47. // LoadDHCPv4Records loads the DHCPv4Records global map with records stored on
  48. // the specified file. The records have to be one per line, a mac address and an
  49. // IPv4 address.
  50. func LoadDHCPv4Records(filename string) (map[string]net.IP, error) {
  51. log.Printf("reading leases from %s", filename)
  52. data, err := ioutil.ReadFile(filename)
  53. if err != nil {
  54. return nil, err
  55. }
  56. records := make(map[string]net.IP)
  57. for _, lineBytes := range bytes.Split(data, []byte{'\n'}) {
  58. line := string(lineBytes)
  59. if len(line) == 0 {
  60. continue
  61. }
  62. tokens := strings.Fields(line)
  63. if len(tokens) != 2 {
  64. return nil, fmt.Errorf("malformed line, want 2 fields, got %d: %s", len(tokens), line)
  65. }
  66. hwaddr, err := net.ParseMAC(tokens[0])
  67. if err != nil {
  68. return nil, fmt.Errorf("malformed hardware address: %s", tokens[0])
  69. }
  70. ipaddr := net.ParseIP(tokens[1])
  71. if ipaddr.To4() == nil {
  72. return nil, fmt.Errorf("expected an IPv4 address, got: %v", ipaddr)
  73. }
  74. records[hwaddr.String()] = ipaddr
  75. }
  76. return records, nil
  77. }
  78. // LoadDHCPv6Records loads the DHCPv6Records global map with records stored on
  79. // the specified file. The records have to be one per line, a mac address and an
  80. // IPv6 address.
  81. func LoadDHCPv6Records(filename string) (map[string]net.IP, error) {
  82. log.Printf("reading leases from %s", filename)
  83. data, err := ioutil.ReadFile(filename)
  84. if err != nil {
  85. return nil, err
  86. }
  87. records := make(map[string]net.IP)
  88. // TODO ignore comments
  89. for _, lineBytes := range bytes.Split(data, []byte{'\n'}) {
  90. line := string(lineBytes)
  91. if len(line) == 0 {
  92. continue
  93. }
  94. tokens := strings.Fields(line)
  95. if len(tokens) != 2 {
  96. return nil, fmt.Errorf("malformed line: %s", line)
  97. }
  98. hwaddr, err := net.ParseMAC(tokens[0])
  99. if err != nil {
  100. return nil, fmt.Errorf("malformed hardware address: %s", tokens[0])
  101. }
  102. ipaddr := net.ParseIP(tokens[1])
  103. if ipaddr.To16() == nil {
  104. return nil, fmt.Errorf("expected an IPv6 address, got: %v", ipaddr)
  105. }
  106. records[hwaddr.String()] = ipaddr
  107. }
  108. return records, nil
  109. }
  110. // Handler6 handles DHCPv6 packets for the file plugin
  111. func Handler6(req, resp dhcpv6.DHCPv6) (dhcpv6.DHCPv6, bool) {
  112. mac, err := dhcpv6.ExtractMAC(req)
  113. if err != nil {
  114. log.Warningf("Could not find client MAC, passing")
  115. return resp, false
  116. }
  117. log.Printf("looking up an IP address for MAC %s", mac.String())
  118. ipaddr, ok := StaticRecords[mac.String()]
  119. if !ok {
  120. log.Warningf("MAC address %s is unknown", mac.String())
  121. return resp, false
  122. }
  123. log.Printf("found IP address %s for MAC %s", ipaddr, mac.String())
  124. resp.AddOption(&dhcpv6.OptIANA{
  125. // FIXME copy this field from the client, reject/drop if missing
  126. IaId: [4]byte{0xaa, 0xbb, 0xcc, 0xdd},
  127. Options: []dhcpv6.Option{
  128. &dhcpv6.OptIAAddress{
  129. IPv6Addr: ipaddr,
  130. PreferredLifetime: 3600,
  131. ValidLifetime: 3600,
  132. },
  133. },
  134. })
  135. decap, err := req.GetInnerMessage()
  136. if err != nil {
  137. log.Errorf("Could not decapsulate: %v", err)
  138. return nil, true
  139. }
  140. if oro := decap.GetOption(dhcpv6.OptionORO); len(oro) > 0 {
  141. for _, code := range oro[0].(*dhcpv6.OptRequestedOption).RequestedOptions() {
  142. if code == dhcpv6.OptionBootfileURL {
  143. // bootfile URL is requested
  144. // FIXME this field should come from the configuration, not
  145. // being hardcoded
  146. resp.AddOption(
  147. &dhcpv6.OptBootFileURL{BootFileURL: []byte("http://[2001:db8::0:1]/nbp")},
  148. )
  149. }
  150. }
  151. }
  152. // XXX: We should maybe allow other plugins to run after this to add other options/handle non-IANA requests
  153. return resp, true
  154. }
  155. // Handler4 handles DHCPv4 packets for the file plugin
  156. func Handler4(req, resp *dhcpv4.DHCPv4) (*dhcpv4.DHCPv4, bool) {
  157. ipaddr, ok := StaticRecords[req.ClientHWAddr.String()]
  158. if !ok {
  159. log.Warningf("MAC address %s is unknown", req.ClientHWAddr.String())
  160. return resp, false
  161. }
  162. resp.YourIPAddr = ipaddr
  163. log.Printf("found IP address %s for MAC %s", ipaddr, req.ClientHWAddr.String())
  164. return resp, true
  165. }
  166. func setupFile6(args ...string) (handler.Handler6, error) {
  167. h6, _, err := setupFile(true, args...)
  168. return h6, err
  169. }
  170. func setupFile4(args ...string) (handler.Handler4, error) {
  171. _, h4, err := setupFile(false, args...)
  172. return h4, err
  173. }
  174. func setupFile(v6 bool, args ...string) (handler.Handler6, handler.Handler4, error) {
  175. var err error
  176. var records map[string]net.IP
  177. if len(args) < 1 {
  178. return nil, nil, errors.New("need a file name")
  179. }
  180. filename := args[0]
  181. if filename == "" {
  182. return nil, nil, errors.New("got empty file name")
  183. }
  184. if v6 {
  185. records, err = LoadDHCPv6Records(filename)
  186. } else {
  187. records, err = LoadDHCPv4Records(filename)
  188. }
  189. if err != nil {
  190. return nil, nil, fmt.Errorf("failed to load DHCPv6 records: %v", err)
  191. }
  192. StaticRecords = records
  193. log.Printf("loaded %d leases from %s", len(records), filename)
  194. return Handler6, Handler4, nil
  195. }