@@ -224,5 +224,253 @@ add rule ip6 cni_hostport prerouting c d fib daddr type local jump hostports_all
224224 Expect (err ).To (HaveOccurred ())
225225 })
226226 })
227+
228+ Describe ("MasqAll configuration" , func () {
229+ var pmNFT * portMapperNFTables
230+ var ipv4Fake , ipv6Fake * knftables.Fake
231+ BeforeEach (func () {
232+ ipv4Fake = knftables .NewFake (knftables .IPv4Family , tableName )
233+ ipv6Fake = knftables .NewFake (knftables .IPv6Family , tableName )
234+ pmNFT = & portMapperNFTables {
235+ ipv4 : ipv4Fake ,
236+ ipv6 : ipv6Fake ,
237+ }
238+ })
239+
240+ It (fmt .Sprintf ("[%s] generates correct rules with masqAll=true for IPv4" , ver ), func () {
241+ configTmpl := `{
242+ "name": "test",
243+ "type": "portmap",
244+ "cniVersion": "%s",
245+ "backend": "nftables",
246+ "runtimeConfig": {
247+ "portMappings": [
248+ { "hostPort": 8080, "containerPort": 80, "protocol": "tcp"}
249+ ]
250+ },
251+ "snat": true,
252+ "masqAll": true
253+ }`
254+ configBytes := []byte (fmt .Sprintf (configTmpl , ver ))
255+
256+ conf , _ , err := parseConfig (configBytes , "foo" )
257+ Expect (err ).NotTo (HaveOccurred ())
258+ conf .ContainerID = containerID
259+
260+ err = pmNFT .forwardPorts (conf , * containerNet4 )
261+ Expect (err ).NotTo (HaveOccurred ())
262+
263+ // With masqAll=true, should have only 1 masquerading rule with saddr 0.0.0.0/0
264+ expectedRules := strings .TrimSpace (`
265+ add table ip cni_hostport { comment "CNI portmap plugin" ; }
266+ add chain ip cni_hostport hostip_hostports
267+ add chain ip cni_hostport hostports
268+ add chain ip cni_hostport hostports_all
269+ add chain ip cni_hostport masquerading { type nat hook postrouting priority 100 ; }
270+ add chain ip cni_hostport output { type nat hook output priority -100 ; }
271+ add chain ip cni_hostport prerouting { type nat hook prerouting priority -100 ; }
272+ add rule ip cni_hostport hostports tcp dport 8080 dnat to 10.0.0.2:80 comment "icee6giejonei6so"
273+ add rule ip cni_hostport hostports_all jump hostip_hostports
274+ add rule ip cni_hostport hostports_all jump hostports
275+ add rule ip cni_hostport masquerading ip saddr 0.0.0.0/0 ip daddr 10.0.0.2 masquerade comment "icee6giejonei6so"
276+ add rule ip cni_hostport output fib daddr type local jump hostports_all
277+ add rule ip cni_hostport prerouting fib daddr type local jump hostports_all
278+ ` )
279+ actualRules := strings .TrimSpace (ipv4Fake .Dump ())
280+ Expect (actualRules ).To (Equal (expectedRules ))
281+
282+ // Check should pass with 1 masquerading rule
283+ err = pmNFT .checkPorts (conf , * containerNet4 )
284+ Expect (err ).NotTo (HaveOccurred ())
285+ })
286+
287+ It (fmt .Sprintf ("[%s] generates correct rules with masqAll=false for IPv4" , ver ), func () {
288+ configTmpl := `{
289+ "name": "test",
290+ "type": "portmap",
291+ "cniVersion": "%s",
292+ "backend": "nftables",
293+ "runtimeConfig": {
294+ "portMappings": [
295+ { "hostPort": 8080, "containerPort": 80, "protocol": "tcp"}
296+ ]
297+ },
298+ "snat": true,
299+ "masqAll": false
300+ }`
301+ configBytes := []byte (fmt .Sprintf (configTmpl , ver ))
302+
303+ conf , _ , err := parseConfig (configBytes , "foo" )
304+ Expect (err ).NotTo (HaveOccurred ())
305+ conf .ContainerID = containerID
306+
307+ err = pmNFT .forwardPorts (conf , * containerNet4 )
308+ Expect (err ).NotTo (HaveOccurred ())
309+
310+ // With masqAll=false, should have 2 masquerading rules: hairpin + localhost
311+ expectedRules := strings .TrimSpace (`
312+ add table ip cni_hostport { comment "CNI portmap plugin" ; }
313+ add chain ip cni_hostport hostip_hostports
314+ add chain ip cni_hostport hostports
315+ add chain ip cni_hostport hostports_all
316+ add chain ip cni_hostport masquerading { type nat hook postrouting priority 100 ; }
317+ add chain ip cni_hostport output { type nat hook output priority -100 ; }
318+ add chain ip cni_hostport prerouting { type nat hook prerouting priority -100 ; }
319+ add rule ip cni_hostport hostports tcp dport 8080 dnat to 10.0.0.2:80 comment "icee6giejonei6so"
320+ add rule ip cni_hostport hostports_all jump hostip_hostports
321+ add rule ip cni_hostport hostports_all jump hostports
322+ add rule ip cni_hostport masquerading ip saddr 10.0.0.2 ip daddr 10.0.0.2 masquerade comment "icee6giejonei6so"
323+ add rule ip cni_hostport masquerading ip saddr 127.0.0.1 ip daddr 10.0.0.2 masquerade comment "icee6giejonei6so"
324+ add rule ip cni_hostport output fib daddr type local jump hostports_all
325+ add rule ip cni_hostport prerouting fib daddr type local jump hostports_all
326+ ` )
327+ actualRules := strings .TrimSpace (ipv4Fake .Dump ())
328+ Expect (actualRules ).To (Equal (expectedRules ))
329+
330+ // Check should pass with 2 masquerading rules
331+ err = pmNFT .checkPorts (conf , * containerNet4 )
332+ Expect (err ).NotTo (HaveOccurred ())
333+ })
334+
335+ It (fmt .Sprintf ("[%s] generates correct rules with masqAll=true for IPv6" , ver ), func () {
336+ configTmpl := `{
337+ "name": "test",
338+ "type": "portmap",
339+ "cniVersion": "%s",
340+ "backend": "nftables",
341+ "runtimeConfig": {
342+ "portMappings": [
343+ { "hostPort": 8080, "containerPort": 80, "protocol": "tcp"}
344+ ]
345+ },
346+ "snat": true,
347+ "masqAll": true
348+ }`
349+ configBytes := []byte (fmt .Sprintf (configTmpl , ver ))
350+
351+ conf , _ , err := parseConfig (configBytes , "foo" )
352+ Expect (err ).NotTo (HaveOccurred ())
353+ conf .ContainerID = containerID
354+
355+ err = pmNFT .forwardPorts (conf , * containerNet6 )
356+ Expect (err ).NotTo (HaveOccurred ())
357+
358+ // With masqAll=true for IPv6, should have only 1 masquerading rule with saddr ::/0
359+ expectedRules := strings .TrimSpace (`
360+ add table ip6 cni_hostport { comment "CNI portmap plugin" ; }
361+ add chain ip6 cni_hostport hostip_hostports
362+ add chain ip6 cni_hostport hostports
363+ add chain ip6 cni_hostport hostports_all
364+ add chain ip6 cni_hostport masquerading { type nat hook postrouting priority 100 ; }
365+ add chain ip6 cni_hostport output { type nat hook output priority -100 ; }
366+ add chain ip6 cni_hostport prerouting { type nat hook prerouting priority -100 ; }
367+ add rule ip6 cni_hostport hostports tcp dport 8080 dnat to [2001:db8::2]:80 comment "icee6giejonei6so"
368+ add rule ip6 cni_hostport hostports_all jump hostip_hostports
369+ add rule ip6 cni_hostport hostports_all jump hostports
370+ add rule ip6 cni_hostport masquerading ip6 saddr ::/0 ip6 daddr 2001:db8::2 masquerade comment "icee6giejonei6so"
371+ add rule ip6 cni_hostport output fib daddr type local jump hostports_all
372+ add rule ip6 cni_hostport prerouting fib daddr type local jump hostports_all
373+ ` )
374+ actualRules := strings .TrimSpace (ipv6Fake .Dump ())
375+ Expect (actualRules ).To (Equal (expectedRules ))
376+
377+ // Check should pass with 1 masquerading rule
378+ err = pmNFT .checkPorts (conf , * containerNet6 )
379+ Expect (err ).NotTo (HaveOccurred ())
380+ })
381+
382+ It (fmt .Sprintf ("[%s] generates correct rules with masqAll=false for IPv6" , ver ), func () {
383+ configTmpl := `{
384+ "name": "test",
385+ "type": "portmap",
386+ "cniVersion": "%s",
387+ "backend": "nftables",
388+ "runtimeConfig": {
389+ "portMappings": [
390+ { "hostPort": 8080, "containerPort": 80, "protocol": "tcp"}
391+ ]
392+ },
393+ "snat": true,
394+ "masqAll": false
395+ }`
396+ configBytes := []byte (fmt .Sprintf (configTmpl , ver ))
397+
398+ conf , _ , err := parseConfig (configBytes , "foo" )
399+ Expect (err ).NotTo (HaveOccurred ())
400+ conf .ContainerID = containerID
401+
402+ err = pmNFT .forwardPorts (conf , * containerNet6 )
403+ Expect (err ).NotTo (HaveOccurred ())
404+
405+ // With masqAll=false for IPv6, should have 1 masquerading rule (no localhost for IPv6)
406+ expectedRules := strings .TrimSpace (`
407+ add table ip6 cni_hostport { comment "CNI portmap plugin" ; }
408+ add chain ip6 cni_hostport hostip_hostports
409+ add chain ip6 cni_hostport hostports
410+ add chain ip6 cni_hostport hostports_all
411+ add chain ip6 cni_hostport masquerading { type nat hook postrouting priority 100 ; }
412+ add chain ip6 cni_hostport output { type nat hook output priority -100 ; }
413+ add chain ip6 cni_hostport prerouting { type nat hook prerouting priority -100 ; }
414+ add rule ip6 cni_hostport hostports tcp dport 8080 dnat to [2001:db8::2]:80 comment "icee6giejonei6so"
415+ add rule ip6 cni_hostport hostports_all jump hostip_hostports
416+ add rule ip6 cni_hostport hostports_all jump hostports
417+ add rule ip6 cni_hostport masquerading ip6 saddr 2001:db8::2 ip6 daddr 2001:db8::2 masquerade comment "icee6giejonei6so"
418+ add rule ip6 cni_hostport output fib daddr type local jump hostports_all
419+ add rule ip6 cni_hostport prerouting fib daddr type local jump hostports_all
420+ ` )
421+ actualRules := strings .TrimSpace (ipv6Fake .Dump ())
422+ Expect (actualRules ).To (Equal (expectedRules ))
423+
424+ // Check should pass with 1 masquerading rule
425+ err = pmNFT .checkPorts (conf , * containerNet6 )
426+ Expect (err ).NotTo (HaveOccurred ())
427+ })
428+
429+ It (fmt .Sprintf ("[%s] checkPorts validates correct rule count with masqAll" , ver ), func () {
430+ configTmpl := `{
431+ "name": "test",
432+ "type": "portmap",
433+ "cniVersion": "%s",
434+ "backend": "nftables",
435+ "runtimeConfig": {
436+ "portMappings": [
437+ { "hostPort": 8080, "containerPort": 80, "protocol": "tcp"}
438+ ]
439+ },
440+ "snat": true,
441+ "masqAll": %t
442+ }`
443+
444+ // Test masqAll=true: expects 1 rule
445+ configBytes := []byte (fmt .Sprintf (configTmpl , ver , true ))
446+ conf , _ , err := parseConfig (configBytes , "foo" )
447+ Expect (err ).NotTo (HaveOccurred ())
448+ conf .ContainerID = containerID
449+
450+ err = pmNFT .forwardPorts (conf , * containerNet4 )
451+ Expect (err ).NotTo (HaveOccurred ())
452+
453+ // Should pass with 1 rule
454+ err = pmNFT .checkPorts (conf , * containerNet4 )
455+ Expect (err ).NotTo (HaveOccurred ())
456+
457+ // Clear the fake nftables
458+ ipv4Fake = knftables .NewFake (knftables .IPv4Family , tableName )
459+ pmNFT .ipv4 = ipv4Fake
460+
461+ // Test masqAll=false: expects 2 rules
462+ configBytes = []byte (fmt .Sprintf (configTmpl , ver , false ))
463+ conf , _ , err = parseConfig (configBytes , "foo" )
464+ Expect (err ).NotTo (HaveOccurred ())
465+ conf .ContainerID = containerID
466+
467+ err = pmNFT .forwardPorts (conf , * containerNet4 )
468+ Expect (err ).NotTo (HaveOccurred ())
469+
470+ // Should pass with 2 rules
471+ err = pmNFT .checkPorts (conf , * containerNet4 )
472+ Expect (err ).NotTo (HaveOccurred ())
473+ })
474+ })
227475 }
228476})
0 commit comments