plugin_test.go 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  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 file
  5. import (
  6. "io/ioutil"
  7. "net"
  8. "os"
  9. "testing"
  10. "time"
  11. "github.com/insomniacslk/dhcp/dhcpv4"
  12. "github.com/insomniacslk/dhcp/dhcpv6"
  13. "github.com/stretchr/testify/assert"
  14. "github.com/stretchr/testify/require"
  15. )
  16. func TestLoadDHCPv4Records(t *testing.T) {
  17. t.Run("valid leases", func(t *testing.T) {
  18. // setup temp leases file
  19. tmp, err := ioutil.TempFile("", "test_plugin_file")
  20. require.NoError(t, err)
  21. defer func() {
  22. tmp.Close()
  23. os.Remove(tmp.Name())
  24. }()
  25. // fill temp file with valid lease lines
  26. _, err = tmp.WriteString("00:11:22:33:44:55 192.0.2.100\n")
  27. require.NoError(t, err)
  28. _, err = tmp.WriteString("11:22:33:44:55:66 192.0.2.101\n")
  29. require.NoError(t, err)
  30. records, err := LoadDHCPv4Records(tmp.Name())
  31. if assert.NoError(t, err) {
  32. if assert.Contains(t, records, "00:11:22:33:44:55") {
  33. assert.Equal(t, net.ParseIP("192.0.2.100"), records["00:11:22:33:44:55"])
  34. }
  35. if assert.Contains(t, records, "11:22:33:44:55:66") {
  36. assert.Equal(t, net.ParseIP("192.0.2.101"), records["11:22:33:44:55:66"])
  37. }
  38. }
  39. })
  40. t.Run("missing field", func(t *testing.T) {
  41. // setup temp leases file
  42. tmp, err := ioutil.TempFile("", "test_plugin_file")
  43. require.NoError(t, err)
  44. defer func() {
  45. tmp.Close()
  46. os.Remove(tmp.Name())
  47. }()
  48. // add line with too few fields
  49. _, err = tmp.WriteString("foo\n")
  50. require.NoError(t, err)
  51. _, err = LoadDHCPv4Records(tmp.Name())
  52. assert.Error(t, err)
  53. })
  54. t.Run("invalid MAC", func(t *testing.T) {
  55. // setup temp leases file
  56. tmp, err := ioutil.TempFile("", "test_plugin_file")
  57. require.NoError(t, err)
  58. defer func() {
  59. tmp.Close()
  60. os.Remove(tmp.Name())
  61. }()
  62. // add line with invalid MAC address to trigger an error
  63. _, err = tmp.WriteString("abcd 192.0.2.102\n")
  64. require.NoError(t, err)
  65. _, err = LoadDHCPv4Records(tmp.Name())
  66. assert.Error(t, err)
  67. })
  68. t.Run("invalid IP address", func(t *testing.T) {
  69. // setup temp leases file
  70. tmp, err := ioutil.TempFile("", "test_plugin_file")
  71. require.NoError(t, err)
  72. defer func() {
  73. tmp.Close()
  74. os.Remove(tmp.Name())
  75. }()
  76. // add line with invalid MAC address to trigger an error
  77. _, err = tmp.WriteString("22:33:44:55:66:77 bcde\n")
  78. require.NoError(t, err)
  79. _, err = LoadDHCPv4Records(tmp.Name())
  80. assert.Error(t, err)
  81. })
  82. t.Run("lease with IPv6 address", func(t *testing.T) {
  83. // setup temp leases file
  84. tmp, err := ioutil.TempFile("", "test_plugin_file")
  85. require.NoError(t, err)
  86. defer func() {
  87. tmp.Close()
  88. os.Remove(tmp.Name())
  89. }()
  90. // add line with IPv6 address instead to trigger an error
  91. _, err = tmp.WriteString("00:11:22:33:44:55 2001:db8::10:1\n")
  92. require.NoError(t, err)
  93. _, err = LoadDHCPv4Records(tmp.Name())
  94. assert.Error(t, err)
  95. })
  96. }
  97. func TestLoadDHCPv6Records(t *testing.T) {
  98. t.Run("valid leases", func(t *testing.T) {
  99. // setup temp leases file
  100. tmp, err := ioutil.TempFile("", "test_plugin_file")
  101. require.NoError(t, err)
  102. defer func() {
  103. tmp.Close()
  104. os.Remove(tmp.Name())
  105. }()
  106. // fill temp file with valid lease lines
  107. _, err = tmp.WriteString("00:11:22:33:44:55 2001:db8::10:1\n")
  108. require.NoError(t, err)
  109. _, err = tmp.WriteString("11:22:33:44:55:66 2001:db8::10:2\n")
  110. require.NoError(t, err)
  111. records, err := LoadDHCPv6Records(tmp.Name())
  112. if assert.NoError(t, err) {
  113. if assert.Contains(t, records, "00:11:22:33:44:55") {
  114. assert.Equal(t, net.ParseIP("2001:db8::10:1"), records["00:11:22:33:44:55"])
  115. }
  116. if assert.Contains(t, records, "11:22:33:44:55:66") {
  117. assert.Equal(t, net.ParseIP("2001:db8::10:2"), records["11:22:33:44:55:66"])
  118. }
  119. }
  120. })
  121. t.Run("missing field", func(t *testing.T) {
  122. // setup temp leases file
  123. tmp, err := ioutil.TempFile("", "test_plugin_file")
  124. require.NoError(t, err)
  125. defer func() {
  126. tmp.Close()
  127. os.Remove(tmp.Name())
  128. }()
  129. // add line with too few fields
  130. _, err = tmp.WriteString("foo\n")
  131. require.NoError(t, err)
  132. _, err = LoadDHCPv6Records(tmp.Name())
  133. assert.Error(t, err)
  134. })
  135. t.Run("invalid MAC", func(t *testing.T) {
  136. // setup temp leases file
  137. tmp, err := ioutil.TempFile("", "test_plugin_file")
  138. require.NoError(t, err)
  139. defer func() {
  140. tmp.Close()
  141. os.Remove(tmp.Name())
  142. }()
  143. // add line with invalid MAC address to trigger an error
  144. _, err = tmp.WriteString("abcd 2001:db8::10:3\n")
  145. require.NoError(t, err)
  146. _, err = LoadDHCPv6Records(tmp.Name())
  147. assert.Error(t, err)
  148. })
  149. t.Run("invalid IP address", func(t *testing.T) {
  150. // setup temp leases file
  151. tmp, err := ioutil.TempFile("", "test_plugin_file")
  152. require.NoError(t, err)
  153. defer func() {
  154. tmp.Close()
  155. os.Remove(tmp.Name())
  156. }()
  157. // add line with invalid MAC address to trigger an error
  158. _, err = tmp.WriteString("22:33:44:55:66:77 bcde\n")
  159. require.NoError(t, err)
  160. _, err = LoadDHCPv6Records(tmp.Name())
  161. assert.Error(t, err)
  162. })
  163. t.Run("lease with IPv4 address", func(t *testing.T) {
  164. // setup temp leases file
  165. tmp, err := ioutil.TempFile("", "test_plugin_file")
  166. require.NoError(t, err)
  167. defer func() {
  168. tmp.Close()
  169. os.Remove(tmp.Name())
  170. }()
  171. // add line with IPv4 address instead to trigger an error
  172. _, err = tmp.WriteString("00:11:22:33:44:55 192.0.2.100\n")
  173. require.NoError(t, err)
  174. _, err = LoadDHCPv6Records(tmp.Name())
  175. assert.Error(t, err)
  176. })
  177. }
  178. func TestHandler4(t *testing.T) {
  179. t.Run("unknown MAC", func(t *testing.T) {
  180. // prepare DHCPv4 request
  181. mac := "00:11:22:33:44:55"
  182. claddr, _ := net.ParseMAC(mac)
  183. req := &dhcpv4.DHCPv4{
  184. ClientHWAddr: claddr,
  185. }
  186. resp := &dhcpv4.DHCPv4{}
  187. assert.Nil(t, resp.ClientIPAddr)
  188. // if we handle this DHCP request, nothing should change since the lease is
  189. // unknown
  190. result, stop := Handler4(req, resp)
  191. assert.Same(t, result, resp)
  192. assert.False(t, stop)
  193. assert.Nil(t, result.YourIPAddr)
  194. })
  195. t.Run("known MAC", func(t *testing.T) {
  196. // prepare DHCPv4 request
  197. mac := "00:11:22:33:44:55"
  198. claddr, _ := net.ParseMAC(mac)
  199. req := &dhcpv4.DHCPv4{
  200. ClientHWAddr: claddr,
  201. }
  202. resp := &dhcpv4.DHCPv4{}
  203. assert.Nil(t, resp.ClientIPAddr)
  204. // add lease for the MAC in the lease map
  205. clIPAddr := net.ParseIP("192.0.2.100")
  206. StaticRecords = map[string]net.IP{
  207. mac: clIPAddr,
  208. }
  209. // if we handle this DHCP request, the YourIPAddr field should be set
  210. // in the result
  211. result, stop := Handler4(req, resp)
  212. assert.Same(t, result, resp)
  213. assert.True(t, stop)
  214. assert.Equal(t, clIPAddr, result.YourIPAddr)
  215. // cleanup
  216. StaticRecords = make(map[string]net.IP)
  217. })
  218. }
  219. func TestHandler6(t *testing.T) {
  220. t.Run("unknown MAC", func(t *testing.T) {
  221. // prepare DHCPv6 request
  222. mac := "11:22:33:44:55:66"
  223. claddr, _ := net.ParseMAC(mac)
  224. req, err := dhcpv6.NewSolicit(claddr)
  225. require.NoError(t, err)
  226. resp, err := dhcpv6.NewAdvertiseFromSolicit(req)
  227. require.NoError(t, err)
  228. assert.Equal(t, 0, len(resp.GetOption(dhcpv6.OptionIANA)))
  229. // if we handle this DHCP request, nothing should change since the lease is
  230. // unknown
  231. result, stop := Handler6(req, resp)
  232. assert.False(t, stop)
  233. assert.Equal(t, 0, len(result.GetOption(dhcpv6.OptionIANA)))
  234. })
  235. t.Run("known MAC", func(t *testing.T) {
  236. // prepare DHCPv6 request
  237. mac := "11:22:33:44:55:66"
  238. claddr, _ := net.ParseMAC(mac)
  239. req, err := dhcpv6.NewSolicit(claddr)
  240. require.NoError(t, err)
  241. resp, err := dhcpv6.NewAdvertiseFromSolicit(req)
  242. require.NoError(t, err)
  243. assert.Equal(t, 0, len(resp.GetOption(dhcpv6.OptionIANA)))
  244. // add lease for the MAC in the lease map
  245. clIPAddr := net.ParseIP("2001:db8::10:1")
  246. StaticRecords = map[string]net.IP{
  247. mac: clIPAddr,
  248. }
  249. // if we handle this DHCP request, there should be a specific IANA option
  250. // set in the resulting response
  251. result, stop := Handler6(req, resp)
  252. assert.False(t, stop)
  253. if assert.Equal(t, 1, len(result.GetOption(dhcpv6.OptionIANA))) {
  254. opt := result.GetOneOption(dhcpv6.OptionIANA)
  255. assert.Contains(t, opt.String(), "IP=2001:db8::10:1")
  256. }
  257. // cleanup
  258. StaticRecords = make(map[string]net.IP)
  259. })
  260. }
  261. func TestSetupFile(t *testing.T) {
  262. // too few arguments
  263. _, _, err := setupFile(false)
  264. assert.Error(t, err)
  265. // empty file name
  266. _, _, err = setupFile(false, "")
  267. assert.Error(t, err)
  268. // trigger error in LoadDHCPv*Records
  269. _, _, err = setupFile(false, "/foo/bar")
  270. assert.Error(t, err)
  271. _, _, err = setupFile(true, "/foo/bar")
  272. assert.Error(t, err)
  273. // setup temp leases file
  274. tmp, err := ioutil.TempFile("", "test_plugin_file")
  275. require.NoError(t, err)
  276. defer func() {
  277. tmp.Close()
  278. os.Remove(tmp.Name())
  279. }()
  280. t.Run("typical case", func(t *testing.T) {
  281. _, err = tmp.WriteString("00:11:22:33:44:55 2001:db8::10:1\n")
  282. require.NoError(t, err)
  283. _, err = tmp.WriteString("11:22:33:44:55:66 2001:db8::10:2\n")
  284. require.NoError(t, err)
  285. assert.Equal(t, 0, len(StaticRecords))
  286. // leases should show up in StaticRecords
  287. _, _, err = setupFile(true, tmp.Name())
  288. if assert.NoError(t, err) {
  289. assert.Equal(t, 2, len(StaticRecords))
  290. }
  291. })
  292. t.Run("autorefresh enabled", func(t *testing.T) {
  293. _, _, err = setupFile(true, tmp.Name(), autoRefreshArg)
  294. if assert.NoError(t, err) {
  295. assert.Equal(t, 2, len(StaticRecords))
  296. }
  297. // we add more leases to the file
  298. // this should trigger an event to refresh the leases database
  299. // without calling setupFile again
  300. _, err = tmp.WriteString("22:33:44:55:66:77 2001:db8::10:3\n")
  301. require.NoError(t, err)
  302. // since the event is processed asynchronously, give it a little time
  303. time.Sleep(time.Millisecond * 100)
  304. // an additional record should show up in the database
  305. // but we should respect the locking first
  306. recLock.RLock()
  307. defer recLock.RUnlock()
  308. assert.Equal(t, 3, len(StaticRecords))
  309. })
  310. }