clampip_test.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. package internal
  2. import (
  3. "net/netip"
  4. "testing"
  5. )
  6. func TestClampIP(t *testing.T) {
  7. tests := []struct {
  8. name string
  9. input string
  10. expected string
  11. }{
  12. // IPv4 addresses
  13. {
  14. name: "IPv4 normal address",
  15. input: "192.168.1.100",
  16. expected: "192.168.1.0/24",
  17. },
  18. {
  19. name: "IPv4 boundary - network address",
  20. input: "192.168.1.0",
  21. expected: "192.168.1.0/24",
  22. },
  23. {
  24. name: "IPv4 boundary - broadcast address",
  25. input: "192.168.1.255",
  26. expected: "192.168.1.0/24",
  27. },
  28. {
  29. name: "IPv4 class A address",
  30. input: "10.0.0.1",
  31. expected: "10.0.0.0/24",
  32. },
  33. {
  34. name: "IPv4 loopback",
  35. input: "127.0.0.1",
  36. expected: "127.0.0.0/24",
  37. },
  38. {
  39. name: "IPv4 link-local",
  40. input: "169.254.0.1",
  41. expected: "169.254.0.0/24",
  42. },
  43. {
  44. name: "IPv4 public address",
  45. input: "203.0.113.1",
  46. expected: "203.0.113.0/24",
  47. },
  48. // IPv6 addresses
  49. {
  50. name: "IPv6 normal address",
  51. input: "2001:db8::1",
  52. expected: "2001:db8::/48",
  53. },
  54. {
  55. name: "IPv6 with full expansion",
  56. input: "2001:0db8:0000:0000:0000:0000:0000:0001",
  57. expected: "2001:db8::/48",
  58. },
  59. {
  60. name: "IPv6 loopback",
  61. input: "::1",
  62. expected: "::/48",
  63. },
  64. {
  65. name: "IPv6 unspecified address",
  66. input: "::",
  67. expected: "::/48",
  68. },
  69. {
  70. name: "IPv6 link-local",
  71. input: "fe80::1",
  72. expected: "fe80::/48",
  73. },
  74. {
  75. name: "IPv6 unique local",
  76. input: "fc00::1",
  77. expected: "fc00::/48",
  78. },
  79. {
  80. name: "IPv6 documentation prefix",
  81. input: "2001:db8:abcd:ef01::1234",
  82. expected: "2001:db8:abcd::/48",
  83. },
  84. {
  85. name: "IPv6 global unicast",
  86. input: "2606:4700:4700::1111",
  87. expected: "2606:4700:4700::/48",
  88. },
  89. {
  90. name: "IPv6 multicast",
  91. input: "ff02::1",
  92. expected: "ff02::/48",
  93. },
  94. // IPv4-mapped IPv6 addresses
  95. {
  96. name: "IPv4-mapped IPv6 address",
  97. input: "::ffff:192.168.1.100",
  98. expected: "192.168.1.0/24",
  99. },
  100. {
  101. name: "IPv4-mapped IPv6 with different format",
  102. input: "::ffff:10.0.0.1",
  103. expected: "10.0.0.0/24",
  104. },
  105. {
  106. name: "IPv4-mapped IPv6 loopback",
  107. input: "::ffff:127.0.0.1",
  108. expected: "127.0.0.0/24",
  109. },
  110. }
  111. for _, tt := range tests {
  112. t.Run(tt.name, func(t *testing.T) {
  113. addr := netip.MustParseAddr(tt.input)
  114. result, ok := ClampIP(addr)
  115. if !ok {
  116. t.Fatalf("ClampIP(%s) returned false, want true", tt.input)
  117. }
  118. if result.String() != tt.expected {
  119. t.Errorf("ClampIP(%s) = %s, want %s", tt.input, result.String(), tt.expected)
  120. }
  121. })
  122. }
  123. }
  124. func TestClampIPSuccess(t *testing.T) {
  125. // Test that valid inputs return success
  126. tests := []struct {
  127. name string
  128. input string
  129. }{
  130. {
  131. name: "IPv4 address",
  132. input: "192.168.1.100",
  133. },
  134. {
  135. name: "IPv6 address",
  136. input: "2001:db8::1",
  137. },
  138. {
  139. name: "IPv4-mapped IPv6",
  140. input: "::ffff:192.168.1.100",
  141. },
  142. }
  143. for _, tt := range tests {
  144. t.Run(tt.name, func(t *testing.T) {
  145. addr := netip.MustParseAddr(tt.input)
  146. result, ok := ClampIP(addr)
  147. if !ok {
  148. t.Fatalf("ClampIP(%s) returned false, want true", tt.input)
  149. }
  150. // For valid inputs, we should get the clamped prefix
  151. if addr.Is4() || addr.Is4In6() {
  152. if result.Bits() != 24 {
  153. t.Errorf("Expected 24 bits for IPv4, got %d", result.Bits())
  154. }
  155. } else if addr.Is6() {
  156. if result.Bits() != 48 {
  157. t.Errorf("Expected 48 bits for IPv6, got %d", result.Bits())
  158. }
  159. }
  160. })
  161. }
  162. }
  163. func TestClampIPZeroValue(t *testing.T) {
  164. // Test that when ClampIP fails, it returns zero value
  165. // Note: It's hard to make addr.Prefix() fail with valid inputs,
  166. // so this test demonstrates the expected behavior
  167. addr := netip.MustParseAddr("192.168.1.100")
  168. // Manually create a zero value for comparison
  169. zeroPrefix := netip.Prefix{}
  170. // Call ClampIP - it should succeed with valid input
  171. result, ok := ClampIP(addr)
  172. // Verify the function succeeded
  173. if !ok {
  174. t.Error("ClampIP should succeed with valid input")
  175. }
  176. // Verify that the result is not a zero value
  177. if result == zeroPrefix {
  178. t.Error("Result should not be zero value for successful operation")
  179. }
  180. }
  181. func TestClampIPSpecialCases(t *testing.T) {
  182. tests := []struct {
  183. name string
  184. input string
  185. expectedPrefix int
  186. expectedNetwork string
  187. }{
  188. {
  189. name: "Minimum IPv4",
  190. input: "0.0.0.0",
  191. expectedPrefix: 24,
  192. expectedNetwork: "0.0.0.0",
  193. },
  194. {
  195. name: "Maximum IPv4",
  196. input: "255.255.255.255",
  197. expectedPrefix: 24,
  198. expectedNetwork: "255.255.255.0",
  199. },
  200. {
  201. name: "Minimum IPv6",
  202. input: "::",
  203. expectedPrefix: 48,
  204. expectedNetwork: "::",
  205. },
  206. {
  207. name: "Maximum IPv6 prefix part",
  208. input: "ffff:ffff:ffff::",
  209. expectedPrefix: 48,
  210. expectedNetwork: "ffff:ffff:ffff::",
  211. },
  212. }
  213. for _, tt := range tests {
  214. t.Run(tt.name, func(t *testing.T) {
  215. addr := netip.MustParseAddr(tt.input)
  216. result, ok := ClampIP(addr)
  217. if !ok {
  218. t.Fatalf("ClampIP(%s) returned false, want true", tt.input)
  219. }
  220. if result.Bits() != tt.expectedPrefix {
  221. t.Errorf("ClampIP(%s) bits = %d, want %d", tt.input, result.Bits(), tt.expectedPrefix)
  222. }
  223. if result.Addr().String() != tt.expectedNetwork {
  224. t.Errorf("ClampIP(%s) network = %s, want %s", tt.input, result.Addr().String(), tt.expectedNetwork)
  225. }
  226. })
  227. }
  228. }
  229. // Benchmark to ensure the function is performant
  230. func BenchmarkClampIP(b *testing.B) {
  231. ipv4 := netip.MustParseAddr("192.168.1.100")
  232. ipv6 := netip.MustParseAddr("2001:db8::1")
  233. ipv4mapped := netip.MustParseAddr("::ffff:192.168.1.100")
  234. b.Run("IPv4", func(b *testing.B) {
  235. for i := 0; i < b.N; i++ {
  236. ClampIP(ipv4)
  237. }
  238. })
  239. b.Run("IPv6", func(b *testing.B) {
  240. for i := 0; i < b.N; i++ {
  241. ClampIP(ipv6)
  242. }
  243. })
  244. b.Run("IPv4-mapped", func(b *testing.B) {
  245. for i := 0; i < b.N; i++ {
  246. ClampIP(ipv4mapped)
  247. }
  248. })
  249. }