plugin_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  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. "net"
  7. "net/netip"
  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 := os.CreateTemp("", "test_plugin_file")
  20. require.NoError(t, err)
  21. defer func() {
  22. require.NoError(t, os.Remove(tmp.Name()))
  23. }()
  24. // fill temp file with valid lease lines (mixed case) and some comments
  25. _, err = tmp.WriteString(`00:11:22:33:44:aa 192.0.2.100
  26. 11:BB:33:DD:55:FF 192.0.2.101 # arbitrary spaces and trailing comment
  27. # this is a simple comment
  28. `)
  29. require.NoError(t, err)
  30. require.NoError(t, tmp.Close())
  31. records, err := LoadDHCPv4Records(tmp.Name())
  32. if !assert.NoError(t, err) {
  33. return
  34. }
  35. if assert.Equal(t, 2, len(records)) {
  36. if assert.Contains(t, records, "00:11:22:33:44:aa") {
  37. assert.Equal(t, netip.MustParseAddr("192.0.2.100"), records["00:11:22:33:44:aa"])
  38. }
  39. if assert.Contains(t, records, "11:bb:33:dd:55:ff") {
  40. assert.Equal(t, netip.MustParseAddr("192.0.2.101"), records["11:bb:33:dd:55:ff"])
  41. }
  42. }
  43. })
  44. t.Run("missing field should raise error", func(t *testing.T) {
  45. // setup temp leases file
  46. tmp, err := os.CreateTemp("", "test_plugin_file")
  47. require.NoError(t, err)
  48. defer func() {
  49. require.NoError(t, os.Remove(tmp.Name()))
  50. }()
  51. // add line with too few fields
  52. _, err = tmp.WriteString("foo\n")
  53. require.NoError(t, err)
  54. require.NoError(t, tmp.Close())
  55. _, err = LoadDHCPv4Records(tmp.Name())
  56. assert.Error(t, err)
  57. })
  58. t.Run("invalid MAC address should raise error", func(t *testing.T) {
  59. // setup temp leases file
  60. tmp, err := os.CreateTemp("", "test_plugin_file")
  61. require.NoError(t, err)
  62. defer func() {
  63. require.NoError(t, os.Remove(tmp.Name()))
  64. }()
  65. // add line with invalid MAC address to trigger an error
  66. _, err = tmp.WriteString("abcd 192.0.2.102\n")
  67. require.NoError(t, err)
  68. require.NoError(t, tmp.Close())
  69. _, err = LoadDHCPv4Records(tmp.Name())
  70. assert.Error(t, err)
  71. })
  72. t.Run("invalid IP address should raise error", func(t *testing.T) {
  73. // setup temp leases file
  74. tmp, err := os.CreateTemp("", "test_plugin_file")
  75. require.NoError(t, err)
  76. defer func() {
  77. require.NoError(t, os.Remove(tmp.Name()))
  78. }()
  79. // add line with invalid IPv4 address to trigger an error
  80. _, err = tmp.WriteString("22:33:44:55:66:77 bcde\n")
  81. require.NoError(t, err)
  82. require.NoError(t, tmp.Close())
  83. _, err = LoadDHCPv4Records(tmp.Name())
  84. assert.Error(t, err)
  85. })
  86. t.Run("duplicate MAC address are allowed", func(t *testing.T) {
  87. // setup temp leases file
  88. tmp, err := os.CreateTemp("", "test_plugin_file")
  89. require.NoError(t, err)
  90. defer func() {
  91. require.NoError(t, os.Remove(tmp.Name()))
  92. }()
  93. // add lines with duplicate MAC addresses to check for no error
  94. _, err = tmp.WriteString(`aa:11:11:11:11:11 1.2.3.4
  95. AA:11:11:11:11:11 5.6.7.8
  96. `)
  97. require.NoError(t, err)
  98. require.NoError(t, tmp.Close())
  99. _, err = LoadDHCPv4Records(tmp.Name())
  100. assert.NoError(t, err)
  101. })
  102. t.Run("duplicate IP address are allowed", func(t *testing.T) {
  103. // setup temp leases file
  104. tmp, err := os.CreateTemp("", "test_plugin_file")
  105. require.NoError(t, err)
  106. defer func() {
  107. require.NoError(t, os.Remove(tmp.Name()))
  108. }()
  109. // add line with duplicate IPv4 addresses to check for no error
  110. _, err = tmp.WriteString(`11:11:11:11:11:11 1.2.3.4
  111. 22:22:22:22:22:22 1.2.3.4
  112. 33:33:33:33:33:33 1.2.3.4
  113. `)
  114. require.NoError(t, err)
  115. require.NoError(t, tmp.Close())
  116. _, err = LoadDHCPv4Records(tmp.Name())
  117. assert.NoError(t, err)
  118. })
  119. t.Run("lease with IPv6 address should raise error", func(t *testing.T) {
  120. // setup temp leases file
  121. tmp, err := os.CreateTemp("", "test_plugin_file")
  122. require.NoError(t, err)
  123. defer func() {
  124. require.NoError(t, os.Remove(tmp.Name()))
  125. }()
  126. // add line with IPv6 address instead to trigger an error
  127. _, err = tmp.WriteString("00:11:22:33:44:55 2001:db8::10:1\n")
  128. require.NoError(t, err)
  129. require.NoError(t, tmp.Close())
  130. _, err = LoadDHCPv4Records(tmp.Name())
  131. assert.Error(t, err)
  132. })
  133. }
  134. func TestLoadDHCPv6Records(t *testing.T) {
  135. t.Run("valid leases", func(t *testing.T) {
  136. // setup temp leases file
  137. tmp, err := os.CreateTemp("", "test_plugin_file")
  138. require.NoError(t, err)
  139. defer func() {
  140. require.NoError(t, os.Remove(tmp.Name()))
  141. }()
  142. // fill temp file with valid lease lines and some comments
  143. _, err = tmp.WriteString(`00:11:22:33:44:aa 2001:db8::10:1
  144. 11:BB:33:DD:55:FF 2001:db8::10:2 # arbitrary spaces and trailing comment
  145. # this is a simple comment
  146. `)
  147. require.NoError(t, err)
  148. require.NoError(t, tmp.Close())
  149. records, err := LoadDHCPv6Records(tmp.Name())
  150. if !assert.NoError(t, err) {
  151. return
  152. }
  153. if assert.Equal(t, 2, len(records)) {
  154. if assert.Contains(t, records, "00:11:22:33:44:aa") {
  155. assert.Equal(t, netip.MustParseAddr("2001:db8::10:1"), records["00:11:22:33:44:aa"])
  156. }
  157. if assert.Contains(t, records, "11:bb:33:dd:55:ff") {
  158. assert.Equal(t, netip.MustParseAddr("2001:db8::10:2"), records["11:bb:33:dd:55:ff"])
  159. }
  160. }
  161. })
  162. t.Run("missing field should raise error", func(t *testing.T) {
  163. // setup temp leases file
  164. tmp, err := os.CreateTemp("", "test_plugin_file")
  165. require.NoError(t, err)
  166. defer func() {
  167. require.NoError(t, os.Remove(tmp.Name()))
  168. }()
  169. // add line with too few fields
  170. _, err = tmp.WriteString("foo\n")
  171. require.NoError(t, err)
  172. require.NoError(t, tmp.Close())
  173. _, err = LoadDHCPv6Records(tmp.Name())
  174. assert.Error(t, err)
  175. })
  176. t.Run("invalid MAC address should raise error", func(t *testing.T) {
  177. // setup temp leases file
  178. tmp, err := os.CreateTemp("", "test_plugin_file")
  179. require.NoError(t, err)
  180. defer func() {
  181. require.NoError(t, os.Remove(tmp.Name()))
  182. }()
  183. // add line with invalid MAC address to trigger an error
  184. _, err = tmp.WriteString("abcd 2001:db8::10:3\n")
  185. require.NoError(t, err)
  186. require.NoError(t, tmp.Close())
  187. _, err = LoadDHCPv6Records(tmp.Name())
  188. assert.Error(t, err)
  189. })
  190. t.Run("invalid IP address should raise error", func(t *testing.T) {
  191. // setup temp leases file
  192. tmp, err := os.CreateTemp("", "test_plugin_file")
  193. require.NoError(t, err)
  194. defer func() {
  195. require.NoError(t, os.Remove(tmp.Name()))
  196. }()
  197. // add line with invalid MAC address to trigger an error
  198. _, err = tmp.WriteString("22:33:44:55:66:77 bcde\n")
  199. require.NoError(t, err)
  200. require.NoError(t, tmp.Close())
  201. _, err = LoadDHCPv6Records(tmp.Name())
  202. assert.Error(t, err)
  203. })
  204. t.Run("duplicate MAC address are allowed", func(t *testing.T) {
  205. // setup temp leases file
  206. tmp, err := os.CreateTemp("", "test_plugin_file")
  207. require.NoError(t, err)
  208. defer func() {
  209. require.NoError(t, os.Remove(tmp.Name()))
  210. }()
  211. // add lines with duplicate MAC addresses to trigger an error
  212. _, err = tmp.WriteString(`aa:11:11:11:11:11 2001:db8::10:1
  213. AA:11:11:11:11:11 2001:db8::10:2
  214. `)
  215. require.NoError(t, err)
  216. require.NoError(t, tmp.Close())
  217. _, err = LoadDHCPv6Records(tmp.Name())
  218. assert.NoError(t, err)
  219. })
  220. t.Run("duplicate IP address are allowed", func(t *testing.T) {
  221. // setup temp leases file
  222. tmp, err := os.CreateTemp("", "test_plugin_file")
  223. require.NoError(t, err)
  224. defer func() {
  225. require.NoError(t, os.Remove(tmp.Name()))
  226. }()
  227. // add lines with duplicate IPv6 addresses to trigger an error
  228. _, err = tmp.WriteString(`11:11:11:11:11:11 2001:db8::10:1
  229. 22:22:22:22:22:22 2001:db8::10:1
  230. 33:33:33:33:33:33 2001:db8::10:1
  231. `)
  232. require.NoError(t, err)
  233. require.NoError(t, tmp.Close())
  234. _, err = LoadDHCPv6Records(tmp.Name())
  235. assert.NoError(t, err)
  236. })
  237. t.Run("lease with IPv4 address should raise error", func(t *testing.T) {
  238. // setup temp leases file
  239. tmp, err := os.CreateTemp("", "test_plugin_file")
  240. require.NoError(t, err)
  241. defer func() {
  242. require.NoError(t, os.Remove(tmp.Name()))
  243. }()
  244. // add line with IPv4 address instead to trigger an error
  245. _, err = tmp.WriteString("00:11:22:33:44:55 192.0.2.100\n")
  246. require.NoError(t, err)
  247. require.NoError(t, tmp.Close())
  248. _, err = LoadDHCPv6Records(tmp.Name())
  249. assert.Error(t, err)
  250. })
  251. }
  252. func TestHandler4(t *testing.T) {
  253. t.Run("unknown MAC", func(t *testing.T) {
  254. // prepare DHCPv4 request
  255. mac := "aa:11:22:33:44:55"
  256. claddr, _ := net.ParseMAC(mac)
  257. req := &dhcpv4.DHCPv4{
  258. ClientHWAddr: claddr,
  259. }
  260. resp := &dhcpv4.DHCPv4{}
  261. assert.Nil(t, resp.ClientIPAddr)
  262. // if we handle this DHCP request, nothing should change since the lease is
  263. // unknown
  264. result, stop := Handler4(req, resp)
  265. assert.Same(t, result, resp)
  266. assert.False(t, stop)
  267. assert.Nil(t, result.YourIPAddr)
  268. })
  269. t.Run("known MAC", func(t *testing.T) {
  270. // prepare DHCPv4 request
  271. mac := "aa:11:22:33:44:55"
  272. claddr, _ := net.ParseMAC(mac)
  273. req := &dhcpv4.DHCPv4{
  274. ClientHWAddr: claddr,
  275. }
  276. resp := &dhcpv4.DHCPv4{}
  277. assert.Nil(t, resp.ClientIPAddr)
  278. // add lease for the MAC in the lease map
  279. clIPAddr := netip.MustParseAddr("192.0.2.100")
  280. StaticRecords = map[string]netip.Addr{
  281. mac: clIPAddr,
  282. }
  283. // if we handle this DHCP request, the YourIPAddr field should be set
  284. // in the result
  285. result, stop := Handler4(req, resp)
  286. assert.Same(t, result, resp)
  287. assert.True(t, stop)
  288. assert.Equal(t, net.IP(clIPAddr.AsSlice()), result.YourIPAddr)
  289. // cleanup
  290. StaticRecords = make(map[string]netip.Addr)
  291. })
  292. }
  293. func TestHandler6(t *testing.T) {
  294. t.Run("unknown MAC", func(t *testing.T) {
  295. // prepare DHCPv6 request
  296. mac := "aa:11:22:33:44:55"
  297. claddr, _ := net.ParseMAC(mac)
  298. req, err := dhcpv6.NewSolicit(claddr)
  299. require.NoError(t, err)
  300. resp, err := dhcpv6.NewAdvertiseFromSolicit(req)
  301. require.NoError(t, err)
  302. assert.Equal(t, 0, len(resp.GetOption(dhcpv6.OptionIANA)))
  303. // if we handle this DHCP request, nothing should change since the lease is
  304. // unknown
  305. result, stop := Handler6(req, resp)
  306. assert.False(t, stop)
  307. assert.Equal(t, 0, len(result.GetOption(dhcpv6.OptionIANA)))
  308. })
  309. t.Run("known MAC", func(t *testing.T) {
  310. // prepare DHCPv6 request
  311. mac := "aa:11:22:33:44:55"
  312. claddr, _ := net.ParseMAC(mac)
  313. req, err := dhcpv6.NewSolicit(claddr)
  314. require.NoError(t, err)
  315. resp, err := dhcpv6.NewAdvertiseFromSolicit(req)
  316. require.NoError(t, err)
  317. assert.Equal(t, 0, len(resp.GetOption(dhcpv6.OptionIANA)))
  318. // add lease for the MAC in the lease map
  319. clIPAddr := netip.MustParseAddr("2001:db8::10:1")
  320. StaticRecords = map[string]netip.Addr{
  321. mac: clIPAddr,
  322. }
  323. // if we handle this DHCP request, there should be a specific IANA option
  324. // set in the resulting response
  325. result, stop := Handler6(req, resp)
  326. assert.False(t, stop)
  327. if assert.Equal(t, 1, len(result.GetOption(dhcpv6.OptionIANA))) {
  328. opt := result.GetOneOption(dhcpv6.OptionIANA)
  329. assert.Contains(t, opt.String(), "IP=2001:db8::10:1")
  330. }
  331. // cleanup
  332. StaticRecords = make(map[string]netip.Addr)
  333. })
  334. }
  335. func TestSetupFile(t *testing.T) {
  336. // too few arguments
  337. _, _, err := setupFile(false)
  338. assert.Error(t, err)
  339. // empty file name
  340. _, _, err = setupFile(false, "")
  341. assert.Error(t, err)
  342. // trigger error in LoadDHCPv*Records
  343. _, _, err = setupFile(false, "/foo/bar")
  344. assert.Error(t, err)
  345. _, _, err = setupFile(true, "/foo/bar")
  346. assert.Error(t, err)
  347. // setup temp leases file
  348. tmp, err := os.CreateTemp("", "test_plugin_file")
  349. require.NoError(t, err)
  350. defer func() {
  351. tmp.Close()
  352. os.Remove(tmp.Name())
  353. }()
  354. t.Run("typical case", func(t *testing.T) {
  355. _, err = tmp.WriteString("aa:11:22:33:44:55 2001:db8::10:1\n")
  356. require.NoError(t, err)
  357. _, err = tmp.WriteString("11:22:33:44:55:66 2001:db8::10:2\n")
  358. require.NoError(t, err)
  359. assert.Equal(t, 0, len(StaticRecords))
  360. // leases should show up in StaticRecords
  361. _, _, err = setupFile(true, tmp.Name())
  362. if assert.NoError(t, err) {
  363. assert.Equal(t, 2, len(StaticRecords))
  364. assert.Equal(t, StaticRecords["aa:11:22:33:44:55"], netip.MustParseAddr("2001:db8::10:1"))
  365. assert.Equal(t, StaticRecords["11:22:33:44:55:66"], netip.MustParseAddr("2001:db8::10:2"))
  366. }
  367. })
  368. t.Run("autorefresh enabled", func(t *testing.T) {
  369. _, _, err = setupFile(true, tmp.Name(), autoRefreshArg)
  370. if assert.NoError(t, err) {
  371. assert.Equal(t, 2, len(StaticRecords))
  372. }
  373. // we add more leases to the file
  374. // this should trigger an event to refresh the leases database
  375. // without calling setupFile again.
  376. // Note that the IPv6 address is uppercase (allowed but not best practice)
  377. _, err = tmp.WriteString("22:33:44:55:66:77 2001:DB8::10:3\n")
  378. require.NoError(t, err)
  379. // since the event is processed asynchronously, give it a little time
  380. time.Sleep(time.Millisecond * 100)
  381. // an additional record should show up in the database
  382. // but we should respect the locking first
  383. recLock.RLock()
  384. defer recLock.RUnlock()
  385. assert.Equal(t, 3, len(StaticRecords))
  386. assert.Equal(t, StaticRecords["22:33:44:55:66:77"], netip.MustParseAddr("2001:db8::10:3"))
  387. })
  388. }