nbp.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  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 nbp implements handling of an NBP (Network Boot Program) using an
  5. // URL, e.g. http://[fe80::abcd:efff:fe12:3456]/my-nbp or tftp://10.0.0.1/my-nbp .
  6. // The NBP information is only added if it is requested by the client.
  7. //
  8. // Note that for DHCPv4, unless the URL is prefixed with a "http", "https" or
  9. // "ftp" scheme, the URL will be split into TFTP server name (option 66)
  10. // and Bootfile name (option 67), so the scheme will be stripped out, and it
  11. // will be treated as a TFTP URL. Anything other than host name and file path
  12. // will be ignored (no port, no query string, etc).
  13. //
  14. // For DHCPv6 OPT_BOOTFILE_URL (option 59) is used, and the value is passed
  15. // unmodified. If the query string is specified and contains a "param" key,
  16. // its value is also passed as OPT_BOOTFILE_PARAM (option 60), so it will be
  17. // duplicated between option 59 and 60.
  18. //
  19. // Example usage:
  20. //
  21. // server6:
  22. // - plugins:
  23. // - nbp: http://[2001:db8:a::1]/nbp
  24. //
  25. // server4:
  26. // - plugins:
  27. // - nbp: tftp://10.0.0.254/nbp
  28. //
  29. package nbp
  30. import (
  31. "fmt"
  32. "net/url"
  33. "github.com/coredhcp/coredhcp/handler"
  34. "github.com/coredhcp/coredhcp/logger"
  35. "github.com/coredhcp/coredhcp/plugins"
  36. "github.com/insomniacslk/dhcp/dhcpv4"
  37. "github.com/insomniacslk/dhcp/dhcpv6"
  38. )
  39. var log = logger.GetLogger("plugins/nbp")
  40. // Plugin wraps plugin registration information
  41. var Plugin = plugins.Plugin{
  42. Name: "nbp",
  43. Setup6: setup6,
  44. Setup4: setup4,
  45. }
  46. var (
  47. opt59, opt60 dhcpv6.Option
  48. opt66, opt67 *dhcpv4.Option
  49. )
  50. func parseArgs(args ...string) (*url.URL, error) {
  51. if len(args) != 1 {
  52. return nil, fmt.Errorf("Exactly one argument must be passed to NBP plugin, got %d", len(args))
  53. }
  54. return url.Parse(args[0])
  55. }
  56. func setup6(args ...string) (handler.Handler6, error) {
  57. u, err := parseArgs(args...)
  58. if err != nil {
  59. return nil, err
  60. }
  61. opt59 = dhcpv6.OptBootFileURL(u.String())
  62. params := u.Query().Get("params")
  63. if params != "" {
  64. opt60 = &dhcpv6.OptionGeneric{
  65. OptionCode: dhcpv6.OptionBootfileParam,
  66. OptionData: []byte(params),
  67. }
  68. }
  69. log.Printf("loaded NBP plugin for DHCPv6.")
  70. return nbpHandler6, nil
  71. }
  72. func setup4(args ...string) (handler.Handler4, error) {
  73. u, err := parseArgs(args...)
  74. if err != nil {
  75. return nil, err
  76. }
  77. var otsn, obfn dhcpv4.Option
  78. switch u.Scheme {
  79. case "http", "https", "ftp":
  80. obfn = dhcpv4.OptBootFileName(u.String())
  81. default:
  82. otsn = dhcpv4.OptTFTPServerName(u.Host)
  83. obfn = dhcpv4.OptBootFileName(u.Path)
  84. opt66 = &otsn
  85. }
  86. opt67 = &obfn
  87. log.Printf("loaded NBP plugin for DHCPv4.")
  88. return nbpHandler4, nil
  89. }
  90. func nbpHandler6(req, resp dhcpv6.DHCPv6) (dhcpv6.DHCPv6, bool) {
  91. if opt59 == nil {
  92. // nothing to do
  93. return resp, true
  94. }
  95. decap, err := req.GetInnerMessage()
  96. if err != nil {
  97. log.Errorf("Could not decapsulate request: %v", err)
  98. // drop the request, this is probably a critical error in the packet.
  99. return nil, true
  100. }
  101. for _, code := range decap.Options.RequestedOptions() {
  102. if code == dhcpv6.OptionBootfileURL {
  103. // bootfile URL is requested
  104. resp.AddOption(opt59)
  105. } else if code == dhcpv6.OptionBootfileParam {
  106. // optionally add opt60, bootfile params, if requested
  107. if opt60 != nil {
  108. resp.AddOption(opt60)
  109. }
  110. }
  111. }
  112. log.Debugf("Added NBP %s to request", opt59)
  113. return resp, true
  114. }
  115. func nbpHandler4(req, resp *dhcpv4.DHCPv4) (*dhcpv4.DHCPv4, bool) {
  116. if opt67 == nil {
  117. // nothing to do
  118. return resp, true
  119. }
  120. if req.IsOptionRequested(dhcpv4.OptionTFTPServerName) && opt66 != nil {
  121. resp.Options.Update(*opt66)
  122. log.Debugf("Added NBP %s / %s to request", opt66, opt67)
  123. }
  124. if req.IsOptionRequested(dhcpv4.OptionBootfileName) {
  125. resp.Options.Update(*opt67)
  126. log.Debugf("Added NBP %s to request", opt67)
  127. }
  128. return resp, true
  129. }