Repository: kubernetes-sigs/nat64
Pull request #23: E2e tests
Author: login: aojea
Labels: name: approved
description: Indicates a PR has been approved by an approver from all required OWNERS files.
name: cncf-cla: yes
description: Indicates the PR's author has signed the CNCF CLA.
name: size/L
description: Denotes a PR that changes 100-499 lines, ignoring generated files.
Created: 2025-04-07T13:20:22Z | Merged: 2025-04-07T16:44:05Z

more fixes and e2e tests


Diff:
diff --git a/.github/workflows/bats.yml b/.github/workflows/bats.yml
new file mode 100644
index 0000000..6b6b3fe
--- /dev/null
+++ b/.github/workflows/bats.yml
@@ -0,0 +1,44 @@
+name: bats
+
+on:
+  push:
+    branches:
+      - 'main'
+    tags:
+      - 'v*'
+  pull_request:
+    branches: [ main ]
+  workflow_dispatch:
+
+env:
+  GO_VERSION: "1.23"
+  K8S_VERSION: "v1.32.0"
+  KIND_VERSION: "v0.26.0"
+  REGISTRY: registry.k8s.io
+  IMAGE_NAME: networking/nat64
+  KIND_CLUSTER_NAME: kind
+
+jobs:
+  bats_tests:
+    runs-on: ubuntu-latest
+    name: Install Bats and bats libs
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+      - name: Setup Bats and bats libs
+        id: setup-bats
+        uses: bats-core/bats-action@3.0.0
+      - name: Bats tests
+        shell: bash
+        env:
+         BATS_LIB_PATH: ${{ steps.setup-bats.outputs.lib-path }}
+         TERM: xterm
+        run: mkdir -p _artifacts && bats -o _artifacts tests/
+
+      - name: Upload logs
+        if: always()
+        uses: actions/upload-artifact@v4
+        with:
+          name: kind-logs-${{ env.JOB_NAME }}-${{ github.run_id }}
+          path: ./_artifacts
+ 
\ No newline at end of file
diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml
index 6d0ef78..d6fd19b 100644
--- a/.github/workflows/e2e.yml
+++ b/.github/workflows/e2e.yml
@@ -138,21 +138,14 @@ jobs:
         original_coredns=$(kubectl get -oyaml -n=kube-system configmap/coredns)
         echo "Original CoreDNS config:"
         echo "${original_coredns}"
-        # Patch it
-        fixed_coredns=$( printf '%s' "${original_coredns}" | sed 's/\/etc\/resolv.conf/[64:ff9b::8.8.8.8]:53/' )
-
-        # Patch the CoreDNS config to add nat64 after errors
-        fixed_coredns=$(printf '%s' "${fixed_coredns}" | sed 's/errors\n/errors\n    dns64 {\n        translate_all\n    }\n/')
+        # Patch the CoreDNS config to resolve upstream
+        fixed_coredns=$(printf '%s' "${original_coredns}" | sed 's/\/etc\/resolv.conf/[64:ff9b::8.8.8.8]:53/' )
 
         echo "Patched CoreDNS config:"
         echo "${fixed_coredns}"
         printf '%s' "${fixed_coredns}" | kubectl apply -f -
         kubectl -n kube-system rollout restart deployment coredns
-        /usr/local/bin/kubectl wait --timeout=1m --for=condition=ready pods --namespace=kube-system -l k8s-app=kube-dns
-
-    - name: Run smoke tests
-      run: |
-        for i in $(seq 1 5) ; do kubectl run -i test-dns$i --image registry.k8s.io/e2e-test-images/agnhost:2.39 --command -- sh -c "curl -6 www.github.com" ; done
+        kubectl -n kube-system rollout status deployment coredns
 
     - name: Run tests
       run: |
@@ -161,7 +154,7 @@ jobs:
 
         # Run tests
         /usr/local/bin/ginkgo --nodes=25                \
-          --focus="DNS"     \
+          --focus="\[Conformance\]"     \
           --skip="Feature|Federation|machinery|PerformanceDNS|DualStack|Disruptive|Serial|Slow|KubeProxy|LoadBalancer|GCE|Netpol|NetworkPolicy|NodeConformance"   \
           /usr/local/bin/e2e.test                       \
           --                                            \
diff --git a/bpf/nat64.c b/bpf/nat64.c
index a936248..3a268fc 100644
--- a/bpf/nat64.c
+++ b/bpf/nat64.c
@@ -489,7 +489,6 @@ static __always_inline int ip6_to_ip4(struct __sk_buff *skb, const int ip_offset
 	struct ipv6hdr ip6 = {};
 	struct iphdr ip4 = {};
 	int ret = 0;
-	__u16 *p = (void *)&ip4;
 	__be16 tot_len = 0;
 	int l4_offset = 0;
 	__wsum pseudohdr_csum = 0;
diff --git a/kind-ipv6.yaml b/kind-ipv6.yaml
index c8566ca..8e9b283 100644
--- a/kind-ipv6.yaml
+++ b/kind-ipv6.yaml
@@ -11,6 +11,6 @@ kubeadmConfigPatches:
   kind: ClusterConfiguration
   controllerManager:
     extraArgs:
-      node-cidr-mask-size-ipv6: "120"
+      node-cidr-mask-size-ipv6: "124" # default v4 nat64 range is 169.254.64.0/24
   networking:
     podSubnet: "fd00:10:244::/112"
diff --git a/main.go b/main.go
index c9e3185..e465971 100644
--- a/main.go
+++ b/main.go
@@ -89,6 +89,39 @@ func init() {
 	}
 }
 
+func validateNetworks(v4nat64, v6nat64, podRange *net.IPNet) error {
+	errorsList := []error{}
+	// this will give us the number of Pods that can be snated 2^(32-masksize)
+	v4netMaskSize, v4netSize := v4nat64.Mask.Size()
+	if v4netSize > 32 {
+		errorsList = append(errorsList, fmt.Errorf("nat-v4-cidr is required to be IPv4 CIDR: %s", v4nat64.String()))
+	}
+
+	// this embeds the IPv4 on the last 4 bytes, so it has to be at least 96
+	v6netMaskSize, _ := v6nat64.Mask.Size()
+	if v6netMaskSize > 96 {
+		errorsList = append(errorsList, fmt.Errorf("nat-v6-cidr must be /96 or wider, need at least 4 variable bytes to embed IPv4 address: %s", v6nat64.String()))
+	}
+
+	// This is an IPv6 only cluster so Pods can only get IPv6 addresses
+	if podRange.IP.To4() != nil {
+		errorsList = append(errorsList, fmt.Errorf("podCIDR is required to be IPv6 CIDR: %s", podRange.String()))
+	}
+
+	podIPNetMaskSize, _ := podRange.Mask.Size()
+	// In stateless NAT algorithm, variable bits from pod CIDR are retained
+	// (for /120 pod CIDR, 1 byte is variable, for /112, 2 bytes, etc.).
+	// Those last bytes of IPv6 pod address are saved in IPv4 source address after IPv6 -> IPv4 NAT
+	// (for /120 pod CIDR, 198.18.0.0/16 IPv4 nat range and fd00:10:244:1::c5ff pod,
+	// bytes 0xc5 (197) and 0xff (255) will be saved, resulting in 198.18.197.255 source address).
+	// The wider IPv4 nat range is provided, the more pods we can handle without collissions, resulting
+	// in wider supported pod CIDR.
+	if (32 - v4netMaskSize) < (128 - podIPNetMaskSize) {
+		errorsList = append(errorsList, fmt.Errorf("mask for pod CIDR %s must be of size %d or narrower to fit into the NAT64 defined range: %s", podRange.String(), 128-(32-v4netMaskSize), v4nat64.String()))
+	}
+	return errors.Join(errorsList...)
+}
+
 func main() {
 	klog.InitFlags(nil)
 	flag.Parse()
@@ -107,10 +140,6 @@ func main() {
 	if err != nil {
 		klog.Fatalf("Wrong nat-v4-cidr %s: %v", natV4Range, err)
 	}
-	v4netMaskSize, v4netSize := v4net.Mask.Size()
-	if v4netSize > 32 {
-		klog.Fatalf("nat-v4-cidr is required to be IPv4 CIDR: %s", natV4Range)
-	}
 
 	routes, err := netlink.RouteGet(v4ip)
 	if err != nil {
@@ -129,11 +158,6 @@ func main() {
 		klog.Fatalf("nat-v6-cidr is required to be IPv6 CIDR: %s", natV6Range)
 	}
 
-	v6netMaskSize, _ := v6net.Mask.Size()
-	if v6netMaskSize > 96 {
-		klog.Fatalf("nat-v6-cidr must be /96 or wider, need at least 4 variable bytes to embed IPv4 address")
-	}
-
 	routes, err = netlink.RouteGet(v6ip)
 	if err != nil {
 		klog.Fatalf("Can not get route to %s: %v", v6ip.String(), err)
@@ -177,21 +201,9 @@ func main() {
 	if err != nil {
 		klog.Fatalf("net.ParseCIDR: %v", err)
 	}
-	if podIPNet.IP.To4() != nil {
-		klog.Fatalf("podCIDR is required to be IPv6 CIDR: %s", podCIDR)
-	}
 
-	podIPNetMaskSize, _ := podIPNet.Mask.Size()
-	// In stateless NAT algorithm, variable bits from pod CIDR are retained
-	// (for /120 pod CIDR, 1 byte is variable, for /112, 2 bytes, etc.).
-	// Those last bytes of IPv6 pod address are saved in IPv4 source address after IPv6 -> IPv4 NAT
-	// (for /120 pod CIDR, 198.18.0.0/16 IPv4 nat range and fd00:10:244:1::c5ff pod,
-	// bytes 0xc5 (197) and 0xff (255) will be saved, resulting in 198.18.197.255 source address).
-	// The wider IPv4 nat range is provided, the more pods we can handle without collissions, resulting
-	// in wider supported pod CIDR.
-	widestPodCIDRRangeAllowed := 128 - v4netMaskSize
-	if podIPNetMaskSize < widestPodCIDRRangeAllowed {
-		klog.Fatalf("Mask for pod CIDR must be of size %d or narrower to avoid source address collision for NAT64: %s", widestPodCIDRRangeAllowed, podCIDR)
+	if err := validateNetworks(v4net, v6net, podIPNet); err != nil {
+		klog.Fatalf("current network parameters are not valid: %v", err)
 	}
 
 	// Obtain the interface with the default route for IPv4 so we can masquerade the traffic
diff --git a/main_test.go b/main_test.go
index 4de2ee5..9240e64 100644
--- a/main_test.go
+++ b/main_test.go
@@ -131,3 +131,96 @@ func compareMultilineStringsIgnoreIndentation(str1, str2 string) bool {
 
 	return str1 == str2
 }
+
+func Test_validateNetworks(t *testing.T) {
+	tests := []struct {
+		name     string
+		v4nat64  string
+		v6nat64  string
+		podRange string
+		wantErr  bool
+	}{
+		{
+			name:     "valid networks",
+			v4nat64:  "192.168.0.0/24",
+			v6nat64:  "64:ff9b::/96",
+			podRange: "fd00:10:244::/120",
+			wantErr:  false,
+		},
+		{
+			name:     "valid networks 2",
+			v4nat64:  "192.168.0.0/18",
+			v6nat64:  "64:ff9b::/96",
+			podRange: "fd00:10:244::/120",
+			wantErr:  false,
+		},
+		{
+			name:     "valid networks 3",
+			v4nat64:  "192.168.0.0/18",
+			v6nat64:  "64:ff9b::/96",
+			podRange: "fd00:10:244::/124",
+			wantErr:  false,
+		},
+		{
+			name:     "invalid pod CIDR",
+			v4nat64:  "192.168.0.0/18",
+			v6nat64:  "64:ff9b::/96",
+			podRange: "192.168.0.0/18",
+			wantErr:  true,
+		},
+		{
+			name:     "invalid v6 CIDR",
+			v4nat64:  "192.168.0.0/18",
+			v6nat64:  "64:ff9b::/120",
+			podRange: "fd00:10:244::/112",
+			wantErr:  true,
+		},
+		{
+			name:     "invalid pod CIDR mask",
+			v4nat64:  "192.168.0.0/24",
+			v6nat64:  "64:ff9b::/96",
+			podRange: "fd00:10:244::/112",
+			wantErr:  true,
+		},
+		{
+			name:     "invalid pod CIDR mask 2",
+			v4nat64:  "192.168.0.0/28",
+			v6nat64:  "64:ff9b::/96",
+			podRange: "fd00:10:244::/120",
+			wantErr:  true,
+		},
+		{
+			name:     "valid pod CIDR mask 3",
+			v4nat64:  "192.168.0.0/8",
+			v6nat64:  "64:ff9b::/96",
+			podRange: "fd00:10:244::/104",
+			wantErr:  false,
+		},
+		{
+			name:     "invalid v6 CIDR mask",
+			v4nat64:  "192.168.0.0/18",
+			v6nat64:  "64:ff9b::/104",
+			podRange: "fd00:10:244::/112",
+			wantErr:  true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			_, v4net, err := net.ParseCIDR(tt.v4nat64)
+			if err != nil {
+				t.Fatalf("unexpected error parsing v4net %s: %v", tt.v4nat64, err)
+			}
+			_, v6net, err := net.ParseCIDR(tt.v6nat64)
+			if err != nil {
+				t.Fatalf("unexpected error parsing v6net %s: %v", tt.v6nat64, err)
+			}
+			_, podNet, err := net.ParseCIDR(tt.podRange)
+			if err != nil {
+				t.Fatalf("unexpected error parsing podNet %s: %v", tt.podRange, err)
+			}
+			if err := validateNetworks(v4net, v6net, podNet); (err != nil) != tt.wantErr {
+				t.Errorf("validateNetworks() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
diff --git a/tests/README.md b/tests/README.md
new file mode 100644
index 0000000..e3d7fe5
--- /dev/null
+++ b/tests/README.md
@@ -0,0 +1,6 @@
+# Integration tests
+
+
+Install `bats` https://bats-core.readthedocs.io/en/stable/installation.html
+Install `kind` https://kind.sigs.k8s.io/
+Run `bats tests/`
\ No newline at end of file
diff --git a/tests/e2e.bats b/tests/e2e.bats
new file mode 100644
index 0000000..d74fe71
--- /dev/null
+++ b/tests/e2e.bats
@@ -0,0 +1,33 @@
+#!/usr/bin/env bats
+
+@test "test curl works from Pods" {
+  for i in $(seq 1 5) ; do
+    echo "Test Pod $i"
+    output=$(kubectl \
+      run -i test-dns$i \
+      --image registry.k8s.io/e2e-test-images/agnhost:2.39 \
+      --restart=Never \
+      --command \
+      -- sh -c "curl -6 --silent --output /dev/null www.google.com && echo ok || echo fail")
+    test "$output" = "ok"
+  done
+}
+
+@test "test curl works from host network Pods" {
+  skip "Host Network Pods need to use as source one address from the Pod IP range"
+  # TODO We can have a route that src from one of the IPs of the host in the Pod range
+  # However, we need to think well how do we want to generalize this.
+  # In this scenario, host network Pods run in the host namespace, so they already
+  # have IPv4 access
+  for i in $(seq 1 5) ; do
+    echo "Test Pod $i"
+    output=$(kubectl \
+      run -i test-dns$i \
+      --image registry.k8s.io/e2e-test-images/agnhost:2.39 \
+      --overrides='{"spec": {"hostNetwork": true}}' \
+      --restart=Never \
+      --command \
+      -- sh -c "curl -6 --silent --output /dev/null www.google.com && echo ok || echo fail")
+    test "$output" = "ok"
+  done
+}
diff --git a/tests/setup_suite.bash b/tests/setup_suite.bash
new file mode 100644
index 0000000..ef4e1cc
--- /dev/null
+++ b/tests/setup_suite.bash
@@ -0,0 +1,40 @@
+#!/bin/bash
+
+set -eu
+
+function setup_suite {
+  # Build the nat64 project
+  docker build -t registry.k8s.io/networking/nat64:test -f Dockerfile "$BATS_TEST_DIRNAME"/.. --load
+
+  # Define the name of the kind cluster
+  export CLUSTER_NAME="nat64-test-cluster"
+
+  # Check if the kind cluster exists
+  if kind get clusters | grep -q "$CLUSTER_NAME"; then
+    return
+  fi
+
+  kind create cluster --wait 1m --retain --name "$CLUSTER_NAME" --config="$BATS_TEST_DIRNAME"/../kind-ipv6.yaml
+  # Install nat64
+  kind load docker-image registry.k8s.io/networking/nat64:test --name "$CLUSTER_NAME"
+  nat64_install=$(sed 's#registry.k8s.io/networking/nat64.*#registry.k8s.io/networking/nat64:test#' < "$BATS_TEST_DIRNAME"/../install.yaml)
+  printf '%s' "${nat64_install}" | kubectl apply -f -
+  kubectl wait --for=condition=ready pods --namespace=kube-system -l k8s-app=nat64
+
+  # Use Google Public DNS64 https://developers.google.com/speed/public-dns/docs/dns64 
+  original_coredns=$(kubectl get -oyaml -n=kube-system configmap/coredns)
+  echo "Original CoreDNS config:"
+  echo "${original_coredns}"
+  # Patch it
+  fixed_coredns=$(printf '%s' "${original_coredns}" | awk '{ print } /errors/ && !inserted { print "        dns64 {\n          translate_all\n        }"; inserted = 1 }' | sed 's/\/etc\/resolv.conf/[64:ff9b::8.8.8.8]:53/' )
+  echo "Patched CoreDNS config with dns64:"
+  echo "${fixed_coredns}"
+  printf '%s' "${fixed_coredns}" | kubectl apply -f -
+  kubectl -n kube-system rollout restart deployment coredns
+  kubectl -n kube-system rollout status deployment coredns
+}
+
+function teardown_suite {
+    kind export logs "$BATS_TEST_DIRNAME"/../_artifacts --name "$CLUSTER_NAME"
+    kind delete cluster --name "$CLUSTER_NAME"
+}
\ No newline at end of file


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: 

Copilot reviewed 5 out of 7 changed files in this pull request and generated 1 comment.

<details>
<summary>Files not reviewed (2)</summary>

* **tests/e2e.bats**: Language not supported
* **tests/setup_suite.bash**: Language not supported
</details>

<details>
<summary>Comments suppressed due to low confidence (1)</summary>

**main.go:92**
* [nitpick] Consider renaming 'v4nat64' and 'v6nat64' to 'ipv4CIDR' and 'ipv6CIDR' respectively for improved clarity.
```
func validateNetworks(v4nat64, v6nat64, podRange *net.IPNet) error {
```
</details>


state: COMMENTED
author_association: NONE
submitted_at: 2025-04-07T13:21:24Z
user: login: aojea
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T13:22:24Z
user: login: siwiutki
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T13:49:40Z
user: login: aojea
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T13:58:33Z
user: login: aojea
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T13:58:59Z
user: login: aojea
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T14:34:51Z
user: login: aojea
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T14:35:18Z

Review comments:
diff_hunk: @@ -152,7 +152,15 @@ jobs:
 
     - name: Run smoke tests
       run: |
-        for i in $(seq 1 5) ; do kubectl run -i test-dns$i --image registry.k8s.io/e2e-test-images/agnhost:2.39 --command -- sh -c "curl -6 www.github.com" ; done
+        for i in $(seq 1 5) ; do
+          output=$(kubectl run -i test-dns$i --image registry.k8s.io/e2e-test-images/agnhost:2.39 --command -- sh -c "curl -6 --silent www.github.com && echo ok || echo fail") 
+          if [ "$output" == "ok" ]; then
path: .github/workflows/e2e.yml
user: login: Copilot
body: [nitpick] Consider using '=' instead of '==' in the test condition to improve POSIX shell compatibility.
```suggestion
          if [ "$output" = "ok" ]; then
```
created_at: 2025-04-07T13:21:23Z
author_association: NONE
diff_hunk: @@ -177,21 +201,9 @@ func main() {
 	if err != nil {
 		klog.Fatalf("net.ParseCIDR: %v", err)
 	}
-	if podIPNet.IP.To4() != nil {
-		klog.Fatalf("podCIDR is required to be IPv6 CIDR: %s", podCIDR)
-	}
 
-	podIPNetMaskSize, _ := podIPNet.Mask.Size()
-	// In stateless NAT algorithm, variable bits from pod CIDR are retained
-	// (for /120 pod CIDR, 1 byte is variable, for /112, 2 bytes, etc.).
-	// Those last bytes of IPv6 pod address are saved in IPv4 source address after IPv6 -> IPv4 NAT
-	// (for /120 pod CIDR, 198.18.0.0/16 IPv4 nat range and fd00:10:244:1::c5ff pod,
-	// bytes 0xc5 (197) and 0xff (255) will be saved, resulting in 198.18.197.255 source address).
-	// The wider IPv4 nat range is provided, the more pods we can handle without collissions, resulting
-	// in wider supported pod CIDR.
-	widestPodCIDRRangeAllowed := 128 - v4netMaskSize
-	if podIPNetMaskSize < widestPodCIDRRangeAllowed {
path: main.go
user: login: aojea
body: we need 32 - v4netMaskSize  to obtain the number of available ips, so we can compare with 128 - podIPNetMaskSize
created_at: 2025-04-07T13:22:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -152,7 +152,15 @@ jobs:
 
     - name: Run smoke tests
       run: |
-        for i in $(seq 1 5) ; do kubectl run -i test-dns$i --image registry.k8s.io/e2e-test-images/agnhost:2.39 --command -- sh -c "curl -6 www.github.com" ; done
+        for i in $(seq 1 5) ; do
+          output=$(kubectl run -i test-dns$i --image registry.k8s.io/e2e-test-images/agnhost:2.39 --command -- sh -c "curl -6 --silent www.github.com && echo ok || echo fail") 
path: .github/workflows/e2e.yml
user: login: siwiutki
body: It's worth to have something similar for UDP and ICMP as well. So together with `curl`, it might be worth to run `dig` and `ping`.
created_at: 2025-04-07T13:36:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,42 @@
+#!/bin/bash
+
+set -eu
+
+function setup_suite {
+
+  # Build the nat64 project
+  docker build -t registry.k8s.io/networking/nat64:test -f Dockerfile "$BATS_TEST_DIRNAME"/.. --load
+
+  # Define the name of the kind cluster
+  export CLUSTER_NAME="nat64-test-cluster"
+
+  # Check if the kind cluster exists
+  if kind get clusters | grep -q "$CLUSTER_NAME"; then
+    return
+  fi
+
+  kind create cluster --wait 1m --retain --name "$CLUSTER_NAME" --config="$BATS_TEST_DIRNAME"/../kind-ipv6.yaml
+  # Install nat64
+  kind load docker-image registry.k8s.io/networking/nat64:test --name "$CLUSTER_NAME"
+  nat64_install=$(sed 's#registry.k8s.io/networking/nat64.*#registry.k8s.io/networking/nat64:test#' < "$BATS_TEST_DIRNAME"/../install.yaml)
+  printf '%s' "${nat64_install}" | kubectl apply -f -
+  kubectl wait --for=condition=ready pods --namespace=kube-system -l k8s-app=nat64
+
+  # Use Google Public DNS64 https://developers.google.com/speed/public-dns/docs/dns64 
+  original_coredns=$(kubectl get -oyaml -n=kube-system configmap/coredns)
+  echo "Original CoreDNS config:"
+  echo "${original_coredns}"
+  # Patch it
+  fixed_coredns=$(printf '%s' "${original_coredns}" | sed 's/errors\n/errors\n    dns64 {\n        translate_all\n    }\n/' | sed 's/\/etc\/resolv.conf/[64:ff9b::8.8.8.8]:53/' )
+  echo "Patched CoreDNS config with dns64:"
+  echo "${fixed_coredns}"
+  printf '%s' "${fixed_coredns}" | kubectl apply -f -
+  kubectl -n kube-system rollout restart deployment coredns
+
+  kubectl wait --for=condition=ready pods --namespace=kube-system -l k8s-app=kube-dns
+}
+
+function teardown_suite {
+    kind export logs  --name "$CLUSTER_NAME"
+    # kind delete cluster --name "$CLUSTER_NAME"
path: tests/setup_suite.bash
user: login: siwiutki
body: Why is this commented out?
created_at: 2025-04-07T13:39:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,42 @@
+#!/bin/bash
+
+set -eu
+
+function setup_suite {
+
+  # Build the nat64 project
path: tests/setup_suite.bash
user: login: siwiutki
body: nit: Mention that this image tag is fake (i.e. does not exist in that repository nor is pushed there) and only for tests
created_at: 2025-04-07T13:44:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -152,7 +152,15 @@ jobs:
 
     - name: Run smoke tests
       run: |
-        for i in $(seq 1 5) ; do kubectl run -i test-dns$i --image registry.k8s.io/e2e-test-images/agnhost:2.39 --command -- sh -c "curl -6 www.github.com" ; done
+        for i in $(seq 1 5) ; do
path: .github/workflows/e2e.yml
user: login: siwiutki
body: This seems to be duplicated, something similar is already in `tests/e2e.bats`. Is it intended?
created_at: 2025-04-07T13:46:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,42 @@
+#!/bin/bash
+
+set -eu
+
+function setup_suite {
+
+  # Build the nat64 project
+  docker build -t registry.k8s.io/networking/nat64:test -f Dockerfile "$BATS_TEST_DIRNAME"/.. --load
+
+  # Define the name of the kind cluster
+  export CLUSTER_NAME="nat64-test-cluster"
+
+  # Check if the kind cluster exists
+  if kind get clusters | grep -q "$CLUSTER_NAME"; then
+    return
+  fi
+
+  kind create cluster --wait 1m --retain --name "$CLUSTER_NAME" --config="$BATS_TEST_DIRNAME"/../kind-ipv6.yaml
+  # Install nat64
+  kind load docker-image registry.k8s.io/networking/nat64:test --name "$CLUSTER_NAME"
+  nat64_install=$(sed 's#registry.k8s.io/networking/nat64.*#registry.k8s.io/networking/nat64:test#' < "$BATS_TEST_DIRNAME"/../install.yaml)
+  printf '%s' "${nat64_install}" | kubectl apply -f -
+  kubectl wait --for=condition=ready pods --namespace=kube-system -l k8s-app=nat64
path: tests/setup_suite.bash
user: login: siwiutki
body: nit: Is default timeout 30 second enough? I would bump it up to 1 minute just to be more safe.
created_at: 2025-04-07T13:48:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -152,7 +152,15 @@ jobs:
 
     - name: Run smoke tests
       run: |
-        for i in $(seq 1 5) ; do kubectl run -i test-dns$i --image registry.k8s.io/e2e-test-images/agnhost:2.39 --command -- sh -c "curl -6 www.github.com" ; done
+        for i in $(seq 1 5) ; do
path: .github/workflows/e2e.yml
user: login: aojea
body: I just want to have a double check, I was relying on the e2e test and it turned out they didn't catch the regression.
tests/e2e.bats need to grow to support much more use cases , the ones I added are just so others can see how they work
created_at: 2025-04-07T13:58:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -152,7 +152,15 @@ jobs:
 
     - name: Run smoke tests
       run: |
-        for i in $(seq 1 5) ; do kubectl run -i test-dns$i --image registry.k8s.io/e2e-test-images/agnhost:2.39 --command -- sh -c "curl -6 www.github.com" ; done
+        for i in $(seq 1 5) ; do
+          output=$(kubectl run -i test-dns$i --image registry.k8s.io/e2e-test-images/agnhost:2.39 --command -- sh -c "curl -6 --silent www.github.com && echo ok || echo fail") 
path: .github/workflows/e2e.yml
user: login: aojea
body: I leave this for future controbutors, but yeag, I expect to see much more permutations
created_at: 2025-04-07T13:58:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,42 @@
+#!/bin/bash
+
+set -eu
+
+function setup_suite {
+
+  # Build the nat64 project
+  docker build -t registry.k8s.io/networking/nat64:test -f Dockerfile "$BATS_TEST_DIRNAME"/.. --load
+
+  # Define the name of the kind cluster
+  export CLUSTER_NAME="nat64-test-cluster"
+
+  # Check if the kind cluster exists
+  if kind get clusters | grep -q "$CLUSTER_NAME"; then
+    return
+  fi
+
+  kind create cluster --wait 1m --retain --name "$CLUSTER_NAME" --config="$BATS_TEST_DIRNAME"/../kind-ipv6.yaml
+  # Install nat64
+  kind load docker-image registry.k8s.io/networking/nat64:test --name "$CLUSTER_NAME"
+  nat64_install=$(sed 's#registry.k8s.io/networking/nat64.*#registry.k8s.io/networking/nat64:test#' < "$BATS_TEST_DIRNAME"/../install.yaml)
+  printf '%s' "${nat64_install}" | kubectl apply -f -
+  kubectl wait --for=condition=ready pods --namespace=kube-system -l k8s-app=nat64
+
+  # Use Google Public DNS64 https://developers.google.com/speed/public-dns/docs/dns64 
+  original_coredns=$(kubectl get -oyaml -n=kube-system configmap/coredns)
+  echo "Original CoreDNS config:"
+  echo "${original_coredns}"
+  # Patch it
+  fixed_coredns=$(printf '%s' "${original_coredns}" | sed 's/errors\n/errors\n    dns64 {\n        translate_all\n    }\n/' | sed 's/\/etc\/resolv.conf/[64:ff9b::8.8.8.8]:53/' )
+  echo "Patched CoreDNS config with dns64:"
+  echo "${fixed_coredns}"
+  printf '%s' "${fixed_coredns}" | kubectl apply -f -
+  kubectl -n kube-system rollout restart deployment coredns
+
+  kubectl wait --for=condition=ready pods --namespace=kube-system -l k8s-app=kube-dns
+}
+
+function teardown_suite {
+    kind export logs  --name "$CLUSTER_NAME"
+    # kind delete cluster --name "$CLUSTER_NAME"
path: tests/setup_suite.bash
user: login: aojea
body: mistake
created_at: 2025-04-07T14:34:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,42 @@
+#!/bin/bash
+
+set -eu
+
+function setup_suite {
+
+  # Build the nat64 project
+  docker build -t registry.k8s.io/networking/nat64:test -f Dockerfile "$BATS_TEST_DIRNAME"/.. --load
+
+  # Define the name of the kind cluster
+  export CLUSTER_NAME="nat64-test-cluster"
+
+  # Check if the kind cluster exists
+  if kind get clusters | grep -q "$CLUSTER_NAME"; then
+    return
+  fi
+
+  kind create cluster --wait 1m --retain --name "$CLUSTER_NAME" --config="$BATS_TEST_DIRNAME"/../kind-ipv6.yaml
+  # Install nat64
+  kind load docker-image registry.k8s.io/networking/nat64:test --name "$CLUSTER_NAME"
+  nat64_install=$(sed 's#registry.k8s.io/networking/nat64.*#registry.k8s.io/networking/nat64:test#' < "$BATS_TEST_DIRNAME"/../install.yaml)
+  printf '%s' "${nat64_install}" | kubectl apply -f -
+  kubectl wait --for=condition=ready pods --namespace=kube-system -l k8s-app=nat64
path: tests/setup_suite.bash
user: login: aojea
body: if we take more than 30 seconds in getting ready we have a problem
created_at: 2025-04-07T14:35:18Z
author_association: CONTRIBUTOR

PR comments:
user: login: k8s-ci-robot
created_at: 2025-04-07T13:20:29Z
body: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/nat64/pull/23#" title="Author self-approved">aojea</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fnat64).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/kubernetes-sigs/nat64/blob/main/OWNERS)~~ [aojea]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
author_association: CONTRIBUTOR
user: login: krzykwas
created_at: 2025-04-07T14:36:12Z
body: /lgtm

Thank you @aojea!

Maybe you could add more integration tests similar to the ones Antonio proposed, @siwiutki?
author_association: NONE
user: login: k8s-ci-robot
created_at: 2025-04-07T14:36:17Z
body: @krzykwas: changing LGTM is restricted to collaborators

<details>

In response to [this](https://github.com/kubernetes-sigs/nat64/pull/23#issuecomment-2783562556):

>/lgtm
>
>Thank you @aojea!
>
>Maybe you could add more integration tests similar to the ones Antonio proposed, @siwiutki?


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>
author_association: CONTRIBUTOR