@@ -512,11 +512,25 @@ func transparentUDPDialAndSend(t *testing.T, parentAddr string) string {
512512 if err != nil {
513513 t .Fatal (err )
514514 }
515+ defer conn .Close ()
515516 clientAddr := conn .LocalAddr ().String ()
516517 if _ , err := conn .Write ([]byte ("hello" )); err != nil {
517518 t .Fatal (err )
518519 }
519- conn .Close ()
520+ // Verify the return path: the echoed reply must reach the client. This is
521+ // the regression assertion for rootless-containers/rootlesskit#592, where
522+ // UDP responses were lost for non-loopback clients.
523+ if err := conn .SetReadDeadline (time .Now ().Add (5 * time .Second )); err != nil {
524+ t .Fatal (err )
525+ }
526+ buf := make ([]byte , 64 )
527+ n , err := conn .Read (buf )
528+ if err != nil {
529+ t .Fatalf ("did not receive UDP echo reply (issue #592 return-path regression): %v" , err )
530+ }
531+ if got := string (buf [:n ]); got != "hello" {
532+ t .Fatalf ("unexpected UDP echo reply: %q" , got )
533+ }
520534 return clientAddr
521535}
522536
@@ -652,8 +666,6 @@ func testTransparentWithPID(t *testing.T, proto string, d port.ParentDriver, chi
652666
653667 echoCmd .Wait ()
654668
655- // Parse and verify: the echo server should see the client's non-loopback IP,
656- // not 127.0.0.1 or a hard-coded router address.
657669 clientHost , _ , err := net .SplitHostPort (clientAddr )
658670 if err != nil {
659671 t .Fatalf ("failed to parse client address %q: %v" , clientAddr , err )
@@ -663,8 +675,23 @@ func testTransparentWithPID(t *testing.T, proto string, d port.ParentDriver, chi
663675 t .Fatalf ("failed to parse server-seen address %q: %v" , serverSawAddr , err )
664676 }
665677
666- if clientHost != serverHost {
667- t .Errorf ("IP mismatch: client=%s, server saw=%s" , clientHost , serverHost )
678+ switch proto {
679+ case "tcp" :
680+ // TCP preserves the real client source IP via IP_TRANSPARENT: the echo
681+ // server must see the client's non-loopback IP, not 127.0.0.1 or a
682+ // hard-coded router address.
683+ if clientHost != serverHost {
684+ t .Errorf ("IP mismatch: client=%s, server saw=%s" , clientHost , serverHost )
685+ }
686+ case "udp" :
687+ // UDP does not preserve the source IP: it falls back to the
688+ // non-transparent path (see rootless-containers/rootlesskit#592 and the
689+ // comment in pkg/port/builtin/child). The server therefore sees a
690+ // loopback source, and the reply still reaches the client (verified by
691+ // transparentUDPDialAndSend reading the echo above).
692+ if clientHost == serverHost {
693+ t .Errorf ("expected UDP source IP not to be preserved, but server saw client IP %s" , serverHost )
694+ }
668695 }
669696
670697 // Cleanup
@@ -707,6 +734,13 @@ func runUDPEchoServer() {
707734 conn .WriteToUDP (buf [:n ], from )
708735}
709736
737+ // RunUDPTransparent exercises the source-ip-transparent code path for UDP. UDP
738+ // does not actually support IP_TRANSPARENT (it falls back to the non-transparent
739+ // path), so this is also the regression test for
740+ // rootless-containers/rootlesskit#592: the client connects from a non-loopback
741+ // address (which previously triggered the broken path) and the test asserts that
742+ // the echo reply is delivered back to the client. Source IP preservation is
743+ // intentionally not expected for UDP.
710744func RunUDPTransparent (t * testing.T , pf func () port.ParentDriver ) {
711745 t .Run ("TestUDPTransparent" , func (t * testing.T ) { TestUDPTransparent (t , pf ()) })
712746}
0 commit comments