Repository: apache/apisix-ingress-controller

Issue:
bug: Gateway not showing correct number of attached routes in status.listeners
### Current Behavior

I have created an `UDPRoute` [0] and matching listener in a `Gateway` [1]. Running `kubectl -n apisix-ingress get gateway/gatty-udp-12345 -o yaml` shows no attached route.

The route is being attached and forwarding traffic correctly.

An `HTTPRoute` is also showing the same behaviour with no attached route despite the route being forwarded to the service correctly. Making an edit to the route forces an update of the gateway status showing that one route is attached.

I noticed `Kind` is not supported between `protocol: UDP` and `UDPRoute` as `Gateway` emits the following in the output.
```yaml
supportedKinds:
    - group: gateway.networking.k8s.io
      kind: HTTPRoute
```
I tried overwriting with the `UDPRoute` as in [1], this resulted in `supportedKinds` being empty.


[0]
```yaml
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: UDPRoute
metadata:
  name: myroute
  namespace: myapp
spec:
  parentRefs:
    - name: gatty-udp-12345
      namespace: apisix-ingress
  rules:
    - name: myroute
      backendRefs:
        - name: myservice
          port: 12345
```

[1]
```yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: gatty-udp-12345
  namespace: apisix-ingress
spec:
  gatewayClassName: apisix-gc
  listeners:
    - protocol: UDP
      port: 12345
      name: udp-12345
      allowedRoutes:
        namespaces:
          from: All
        # kinds:
        #   - group: gateway.networking.k8s.io
        #     kind: UDPRoute
  infrastructure:
    parametersRef:
      group: apisix.apache.org
      kind: GatewayProxy
      name: apisix-config
```

### Expected Behavior

* Adding an UDPRoute or HTTPRoute should update the `spec.listeners.attachedRoutes` field of the gateway
* gateway should show `UDPRoute` in `supportedKinds`
* gateway should show `attachedRoutes: 1` on applying the UDPRoute


### Error Logs

_No response_

### Steps to Reproduce

1. Install the APISIX Ingress controller in traditional mode with the Helm chart, updating the image of the ingress controller to the most recent version
2. Create a route and gateway as described above
3. Check attached routes using `kubectl -n apisix-ingress get gateway/gatty-udp-12345 -o yaml`

### Environment
- Helm chart 2.12.1
- APISIX Ingress controller version (run `apisix-ingress-controller version --long`)
docker image [2.0.0-rc5](https://hub.docker.com/layers/apache/apisix-ingress-controller/2.0.0-rc5/images/sha256-566abf199cfff5fd23bcfb7dd8f07f5362b6fb791aa4fd7f10393d51547e680e) (it did not work with the original helm chart version either)
- Kubernetes cluster version (run `kubectl version`)
Client Version: v1.34.1
Kustomize Version: v5.7.1
Server Version: v1.33.1
- OS version if running APISIX Ingress controller in a bare-metal environment (run `uname -a`)
N/A

Discussion:
@mulimoen: This is only an UI issue as the UDPRoute works as intended with apisix-ingress-controller=2.0.0-rc5, both with and without specifying `kinds` in `AllowedRoutes`. It seems the `status.listeners.attachedRoutes` field of the gateway is not automatically updated on acceptance of the route.@bzp2010: @mulimoen 

This appears to be an implementation flaw, which we shall investigate to determine how best to improve it.

Our current focus with the Gateway API has primarily been on passing those conformance tests, but it seems this particular scenario was not covered by test cases from the Gateway API project.

Solution patch:
diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml
index da3ad2fc..9a2b8342 100644
--- a/config/webhook/manifests.yaml
+++ b/config/webhook/manifests.yaml
@@ -11,7 +11,7 @@ webhooks:
       name: webhook-service
       namespace: system
       path: /validate-apisix-apache-org-v2-apisixconsumer
-  failurePolicy: Fail
+  failurePolicy: Ignore
   name: vapisixconsumer-v2.kb.io
   rules:
   - apiGroups:
@@ -31,7 +31,7 @@ webhooks:
       name: webhook-service
       namespace: system
       path: /validate-apisix-apache-org-v2-apisixroute
-  failurePolicy: Fail
+  failurePolicy: Ignore
   name: vapisixroute-v2.kb.io
   rules:
   - apiGroups:
@@ -51,7 +51,7 @@ webhooks:
       name: webhook-service
       namespace: system
       path: /validate-apisix-apache-org-v2-apisixtls
-  failurePolicy: Fail
+  failurePolicy: Ignore
   name: vapisixtls-v2.kb.io
   rules:
   - apiGroups:
@@ -71,7 +71,7 @@ webhooks:
       name: webhook-service
       namespace: system
       path: /validate-apisix-apache-org-v1alpha1-consumer
-  failurePolicy: Fail
+  failurePolicy: Ignore
   name: vconsumer-v1alpha1.kb.io
   rules:
   - apiGroups:
@@ -91,7 +91,7 @@ webhooks:
       name: webhook-service
       namespace: system
       path: /validate-gateway-networking-k8s-io-v1-gateway
-  failurePolicy: Fail
+  failurePolicy: Ignore
   name: vgateway-v1.kb.io
   rules:
   - apiGroups:
@@ -111,7 +111,7 @@ webhooks:
       name: webhook-service
       namespace: system
       path: /validate-apisix-apache-org-v1alpha1-gatewayproxy
-  failurePolicy: Fail
+  failurePolicy: Ignore
   name: vgatewayproxy-v1alpha1.kb.io
   rules:
   - apiGroups:
@@ -131,7 +131,7 @@ webhooks:
       name: webhook-service
       namespace: system
       path: /validate-gateway-networking-k8s-io-v1-grpcroute
-  failurePolicy: Fail
+  failurePolicy: Ignore
   name: vgrpcroute-v1.kb.io
   rules:
   - apiGroups:
@@ -151,7 +151,7 @@ webhooks:
       name: webhook-service
       namespace: system
       path: /validate-gateway-networking-k8s-io-v1-httproute
-  failurePolicy: Fail
+  failurePolicy: Ignore
   name: vhttproute-v1.kb.io
   rules:
   - apiGroups:
@@ -171,7 +171,7 @@ webhooks:
       name: webhook-service
       namespace: system
       path: /validate-networking-k8s-io-v1-ingress
-  failurePolicy: Fail
+  failurePolicy: Ignore
   name: vingress-v1.kb.io
   rules:
   - apiGroups:
@@ -191,7 +191,7 @@ webhooks:
       name: webhook-service
       namespace: system
       path: /validate-networking-k8s-io-v1-ingressclass
-  failurePolicy: Fail
+  failurePolicy: Ignore
   name: vingressclass-v1.kb.io
   rules:
   - apiGroups:
@@ -211,7 +211,7 @@ webhooks:
       name: webhook-service
       namespace: system
       path: /validate-gateway-networking-k8s-io-v1alpha2-tcproute
-  failurePolicy: Fail
+  failurePolicy: Ignore
   name: vtcproute-v1alpha2.kb.io
   rules:
   - apiGroups:
@@ -231,7 +231,7 @@ webhooks:
       name: webhook-service
       namespace: system
       path: /validate-gateway-networking-k8s-io-v1alpha2-udproute
-  failurePolicy: Fail
+  failurePolicy: Ignore
   name: vudproute-v1alpha2.kb.io
   rules:
   - apiGroups:
diff --git a/internal/controller/gateway_controller.go b/internal/controller/gateway_controller.go
index 76648a4a..e2806304 100644
--- a/internal/controller/gateway_controller.go
+++ b/internal/controller/gateway_controller.go
@@ -21,7 +21,6 @@ import (
 	"context"
 	"errors"
 	"fmt"
-	"reflect"
 
 	"github.com/go-logr/logr"
 	corev1 "k8s.io/api/core/v1"
@@ -35,6 +34,7 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/predicate"
 	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 	gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
+	gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
 	"sigs.k8s.io/gateway-api/apis/v1beta1"
 
 	"github.com/apache/apisix-ingress-controller/api/v1alpha1"
@@ -62,13 +62,10 @@ func (r *GatewayReconciler) SetupWithManager(mgr ctrl.Manager) error {
 		For(
 			&gatewayv1.Gateway{},
 			builder.WithPredicates(
-				predicate.NewPredicateFuncs(r.checkGatewayClass),
-			),
-		).
-		WithEventFilter(
-			predicate.Or(
-				predicate.GenerationChangedPredicate{},
-				predicate.NewPredicateFuncs(TypePredicate[*corev1.Secret]()),
+				predicate.And(
+					predicate.NewPredicateFuncs(r.checkGatewayClass),
+					predicate.GenerationChangedPredicate{},
+				),
 			),
 		).
 		Watches(
@@ -80,7 +77,23 @@ func (r *GatewayReconciler) SetupWithManager(mgr ctrl.Manager) error {
 		).
 		Watches(
 			&gatewayv1.HTTPRoute{},
-			handler.EnqueueRequestsFromMapFunc(r.listGatewaysForHTTPRoute),
+			handler.EnqueueRequestsFromMapFunc(r.listGatewaysForStatusParentRefs),
+		).
+		Watches(
+			&gatewayv1.GRPCRoute{},
+			handler.EnqueueRequestsFromMapFunc(r.listGatewaysForStatusParentRefs),
+		).
+		Watches(
+			&gatewayv1alpha2.TCPRoute{},
+			handler.EnqueueRequestsFromMapFunc(r.listGatewaysForStatusParentRefs),
+		).
+		Watches(
+			&gatewayv1alpha2.TLSRoute{},
+			handler.EnqueueRequestsFromMapFunc(r.listGatewaysForStatusParentRefs),
+		).
+		Watches(
+			&gatewayv1alpha2.UDPRoute{},
+			handler.EnqueueRequestsFromMapFunc(r.listGatewaysForStatusParentRefs),
 		).
 		Watches(
 			&v1alpha1.GatewayProxy{},
@@ -300,19 +313,11 @@ func (r *GatewayReconciler) listGatewaysForGatewayProxy(ctx context.Context, obj
 	return recs
 }
 
-func (r *GatewayReconciler) listGatewaysForHTTPRoute(ctx context.Context, obj client.Object) []reconcile.Request {
-	httpRoute, ok := obj.(*gatewayv1.HTTPRoute)
-	if !ok {
-		r.Log.Error(
-			fmt.Errorf("unexpected object type"),
-			"HTTPRoute watch predicate received unexpected object type",
-			"expected", "*gatewayapi.HTTPRoute", "found", reflect.TypeOf(obj),
-		)
-		return nil
-	}
-	recs := []reconcile.Request{}
-	for _, routeParentStatus := range httpRoute.Status.Parents {
-		gatewayNamespace := httpRoute.GetNamespace()
+func (r *GatewayReconciler) listGatewaysForStatusParentRefs(ctx context.Context, obj client.Object) []reconcile.Request {
+	route := internaltypes.NewRouteAdapter(obj)
+	reqs := []reconcile.Request{}
+	for _, routeParentStatus := range route.GetParentStatuses() {
+		gatewayNamespace := route.GetNamespace()
 		parentRef := routeParentStatus.ParentRef
 		if parentRef.Group != nil && *parentRef.Group != gatewayv1.GroupName {
 			continue
@@ -336,14 +341,14 @@ func (r *GatewayReconciler) listGatewaysForHTTPRoute(ctx context.Context, obj cl
 			continue
 		}
 
-		recs = append(recs, reconcile.Request{
+		reqs = append(reqs, reconcile.Request{
 			NamespacedName: client.ObjectKey{
 				Namespace: gatewayNamespace,
 				Name:      string(parentRef.Name),
 			},
 		})
 	}
-	return recs
+	return reqs
 }
 
 func (r *GatewayReconciler) listGatewaysForSecret(ctx context.Context, obj client.Object) (requests []reconcile.Request) {
diff --git a/internal/controller/utils.go b/internal/controller/utils.go
index 845d56df..869efefc 100644
--- a/internal/controller/utils.go
+++ b/internal/controller/utils.go
@@ -376,7 +376,7 @@ func ParseRouteParentRefs(
 				}
 			}
 
-			if !routeMatchesListenerType(route, listener) {
+			if ok, _ := routeMatchesListenerType(route, listener); !ok {
 				continue
 			}
 
@@ -475,8 +475,8 @@ func checkRouteAcceptedByListener(
 			return false, gatewayv1.RouteReasonNoMatchingParent, nil
 		}
 	}
-	if !routeMatchesListenerType(route, listener) {
-		return false, gatewayv1.RouteReasonNoMatchingParent, nil
+	if ok, err := routeMatchesListenerType(route, listener); !ok {
+		return false, gatewayv1.RouteReasonNoMatchingParent, err
 	}
 	if !routeHostnamesIntersectsWithListenerHostname(route, listener) {
 		return false, gatewayv1.RouteReasonNoMatchingListenerHostname, nil
@@ -649,71 +649,95 @@ func isRouteNamespaceAllowed(
 	}
 }
 
-func routeMatchesListenerType(route client.Object, listener gatewayv1.Listener) bool {
+func routeMatchesListenerType(route client.Object, listener gatewayv1.Listener) (bool, error) {
 	switch route.(type) {
 	case *gatewayv1.HTTPRoute, *gatewayv1.GRPCRoute:
 		if listener.Protocol != gatewayv1.HTTPProtocolType && listener.Protocol != gatewayv1.HTTPSProtocolType {
-			return false
+			return false, nil
 		}
 
 		if listener.Protocol == gatewayv1.HTTPSProtocolType {
 			if listener.TLS == nil {
-				return false
+				return false, nil
 			}
 
 			if listener.TLS.Mode != nil && *listener.TLS.Mode != gatewayv1.TLSModeTerminate {
-				return false
+				return false, nil
 			}
 		}
 	case *gatewayv1alpha2.TCPRoute:
 		if listener.Protocol != gatewayv1.TCPProtocolType {
-			return false
+			return false, nil
 		}
 	case *gatewayv1alpha2.UDPRoute:
 		if listener.Protocol != gatewayv1.UDPProtocolType {
-			return false
+			return false, nil
 		}
 	case *gatewayv1alpha2.TLSRoute:
 		if listener.Protocol != gatewayv1.TLSProtocolType {
-			return false
+			return false, nil
 		}
 	default:
-		return false
+		return false, fmt.Errorf("unsupported route type %T", route)
 	}
-	return true
+	return true, nil
 }
 
 func getAttachedRoutesForListener(ctx context.Context, mgrc client.Client, gateway gatewayv1.Gateway, listener gatewayv1.Listener) (int32, error) {
-	httpRouteList := gatewayv1.HTTPRouteList{}
-	if err := mgrc.List(ctx, &httpRouteList); err != nil {
-		return 0, err
+	routes := []types.RouteAdapter{}
+	routeList := []client.ObjectList{}
+
+	listOption := client.MatchingFields{
+		indexer.ParentRefs: indexer.GenIndexKey(gateway.Namespace, gateway.Name),
 	}
-	var attachedRoutes int32
-	for _, route := range httpRouteList.Items {
-		route := route
-		acceptedByGateway := lo.ContainsBy(route.Status.Parents, func(parentStatus gatewayv1.RouteParentStatus) bool {
-			parentRef := parentStatus.ParentRef
-			if parentRef.Group != nil && *parentRef.Group != gatewayv1.GroupName {
-				return false
-			}
-			if parentRef.Kind != nil && *parentRef.Kind != KindGateway {
-				return false
+	if listener.AllowedRoutes != nil && listener.AllowedRoutes.Kinds != nil {
+		for _, rgk := range listener.AllowedRoutes.Kinds {
+			if rgk.Group != nil && *rgk.Group != gatewayv1.GroupName {
+				continue
 			}
-			gatewayNamespace := route.Namespace
-			if parentRef.Namespace != nil {
-				gatewayNamespace = string(*parentRef.Namespace)
+			switch rgk.Kind {
+			case types.KindHTTPRoute:
+				routeList = append(routeList, &gatewayv1.HTTPRouteList{})
+			case types.KindGRPCRoute:
+				routeList = append(routeList, &gatewayv1.GRPCRouteList{})
+			case types.KindTCPRoute:
+				routeList = append(routeList, &gatewayv1alpha2.TCPRouteList{})
+			case types.KindUDPRoute:
+				routeList = append(routeList, &gatewayv1alpha2.UDPRouteList{})
+			case types.KindTLSRoute:
+				routeList = append(routeList, &gatewayv1alpha2.TLSRouteList{})
 			}
-			return gateway.Namespace == gatewayNamespace && gateway.Name == string(parentRef.Name)
-		})
-		if !acceptedByGateway {
-			continue
 		}
+	} else {
+		switch listener.Protocol {
+		case gatewayv1.HTTPProtocolType, gatewayv1.HTTPSProtocolType:
+			routeList = append(routeList, &gatewayv1.HTTPRouteList{}, &gatewayv1.GRPCRouteList{})
+		case gatewayv1.TCPProtocolType:
+			routeList = append(routeList, &gatewayv1alpha2.TCPRouteList{})
+		case gatewayv1.UDPProtocolType:
+			routeList = append(routeList, &gatewayv1alpha2.UDPRouteList{})
+		case gatewayv1.TLSProtocolType:
+			routeList = append(routeList, &gatewayv1alpha2.TLSRouteList{})
+		}
+	}
+
+	for _, rl := range routeList {
+		if err := mgrc.List(ctx, rl, listOption); err != nil {
+			return 0, fmt.Errorf("failed to list %T: %w", rl, err)
+		}
+		routes = append(routes, types.NewRouteListAdapter(rl)...)
+	}
 
-		for _, parentRef := range route.Spec.ParentRefs {
+	var attachedRoutes int32
+	for _, route := range routes {
+		if !checkStatusParent(route.GetParentStatuses(), route.GetNamespace(), gateway) {
+			continue
+		}
+		for _, parentRef := range route.GetParentRefs() {
 			ok, _, err := checkRouteAcceptedByListener(
 				ctx,
 				mgrc,
-				&route,
+				route.GetObject(),
 				gateway,
 				listener,
 				parentRef,
@@ -729,13 +753,29 @@ func getAttachedRoutesForListener(ctx context.Context, mgrc client.Client, gatew
 	return attachedRoutes, nil
 }
 
+func checkStatusParent(parents []gatewayv1.RouteParentStatus, routeNamespace string, gateway gatewayv1.Gateway) bool {
+	return lo.ContainsBy(parents, func(parentStatus gatewayv1.RouteParentStatus) bool {
+		parentRef := parentStatus.ParentRef
+		if parentRef.Group != nil && *parentRef.Group != gatewayv1.GroupName {
+			return false
+		}
+		if parentRef.Kind != nil && *parentRef.Kind != KindGateway {
+			return false
+		}
+		gatewayNamespace := routeNamespace
+		if parentRef.Namespace != nil {
+			gatewayNamespace = string(*parentRef.Namespace)
+		}
+		return gateway.Namespace == gatewayNamespace && gateway.Name == string(parentRef.Name)
+	})
+}
+
 func getListenerStatus(
 	ctx context.Context,
 	mrgc client.Client,
 	gateway *gatewayv1.Gateway,
 ) ([]gatewayv1.ListenerStatus, error) {
-	statuses := make(map[gatewayv1.SectionName]gatewayv1.ListenerStatus, len(gateway.Spec.Listeners))
-
+	statusArray := make([]gatewayv1.ListenerStatus, 0, len(gateway.Spec.Listeners))
 	for i, listener := range gateway.Spec.Listeners {
 		attachedRoutes, err := getAttachedRoutesForListener(ctx, mrgc, *gateway, listener)
 		if err != nil {
@@ -776,10 +816,35 @@ func getListenerStatus(
 		)
 
 		if listener.AllowedRoutes == nil || listener.AllowedRoutes.Kinds == nil {
-			supportedKinds = []gatewayv1.RouteGroupKind{
-				{
-					Kind: KindHTTPRoute,
-				},
+			group := gatewayv1.Group(gatewayv1.GroupName)
+			supportedKinds = []gatewayv1.RouteGroupKind{}
+			switch listener.Protocol {
+			case gatewayv1.TLSProtocolType:
+				supportedKinds = append(supportedKinds, gatewayv1.RouteGroupKind{
+					Group: &group,
+					Kind:  types.KindTLSRoute,
+				})
+			case gatewayv1.TCPProtocolType:
+				supportedKinds = append(supportedKinds, gatewayv1.RouteGroupKind{
+					Group: &group,
+					Kind:  types.KindTCPRoute,
+				})
+			case gatewayv1.UDPProtocolType:
+				supportedKinds = append(supportedKinds, gatewayv1.RouteGroupKind{
+					Group: &group,
+					Kind:  types.KindUDPRoute,
+				})
+			case gatewayv1.HTTPProtocolType, gatewayv1.HTTPSProtocolType:
+				supportedKinds = append(supportedKinds, []gatewayv1.RouteGroupKind{
+					{
+						Group: &group,
+						Kind:  types.KindGRPCRoute,
+					},
+					{
+						Group: &group,
+						Kind:  types.KindHTTPRoute,
+					},
+				}...)
 			}
 		} else {
 			for _, kind := range listener.AllowedRoutes.Kinds {
@@ -789,7 +854,7 @@ func getListenerStatus(
 					continue
 				}
 				switch kind.Kind {
-				case KindHTTPRoute:
+				case KindHTTPRoute, types.KindGRPCRoute, types.KindTLSRoute, types.KindTCPRoute, types.KindUDPRoute:
 					supportedKinds = append(supportedKinds, kind)
 				default:
 					conditionResolvedRefs.Status = metav1.ConditionFalse
@@ -892,17 +957,9 @@ func getListenerStatus(
 			changed = true
 		}
 
-		if changed {
-			statuses[listener.Name] = status
-		} else {
-			statuses[listener.Name] = gateway.Status.Listeners[i]
+		if !changed {
+			status = gateway.Status.Listeners[i]
 		}
-	}
-
-	// check for conflicts
-
-	statusArray := []gatewayv1.ListenerStatus{}
-	for _, status := range statuses {
 		statusArray = append(statusArray, status)
 	}
 
diff --git a/internal/types/route_adapter.go b/internal/types/route_adapter.go
new file mode 100644
index 00000000..11da083d
--- /dev/null
+++ b/internal/types/route_adapter.go
@@ -0,0 +1,147 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package types
+
+import (
+	"github.com/samber/lo"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
+	gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
+)
+
+type HTTPRouteAdapter struct {
+	*gatewayv1.HTTPRoute
+}
+
+func (r HTTPRouteAdapter) GetParentStatuses() []gatewayv1.RouteParentStatus {
+	return r.Status.Parents
+}
+func (r HTTPRouteAdapter) GetParentRefs() []gatewayv1.ParentReference {
+	return r.Spec.ParentRefs
+}
+func (r HTTPRouteAdapter) GetObject() client.Object {
+	return r.HTTPRoute
+}
+
+type GRPCRouteAdapter struct {
+	*gatewayv1.GRPCRoute
+}
+
+func (r GRPCRouteAdapter) GetParentStatuses() []gatewayv1.RouteParentStatus {
+	return r.Status.Parents
+}
+func (r GRPCRouteAdapter) GetParentRefs() []gatewayv1.ParentReference {
+	return r.Spec.ParentRefs
+}
+func (r GRPCRouteAdapter) GetObject() client.Object {
+	return r.GRPCRoute
+}
+
+type TCPRouteAdapter struct {
+	*gatewayv1alpha2.TCPRoute
+}
+
+func (r TCPRouteAdapter) GetParentStatuses() []gatewayv1.RouteParentStatus {
+	return r.Status.Parents
+}
+
+func (r TCPRouteAdapter) GetParentRefs() []gatewayv1.ParentReference {
+	return r.Spec.ParentRefs
+}
+func (r TCPRouteAdapter) GetObject() client.Object {
+	return r.TCPRoute
+}
+
+type UDPRouteAdapter struct {
+	*gatewayv1alpha2.UDPRoute
+}
+
+func (r UDPRouteAdapter) GetParentStatuses() []gatewayv1.RouteParentStatus {
+	return r.Status.Parents
+}
+func (r UDPRouteAdapter) GetParentRefs() []gatewayv1.ParentReference {
+	return r.Spec.ParentRefs
+}
+func (r UDPRouteAdapter) GetObject() client.Object {
+	return r.UDPRoute
+}
+
+type TLSRouteAdapter struct {
+	*gatewayv1alpha2.TLSRoute
+}
+
+func (r TLSRouteAdapter) GetParentStatuses() []gatewayv1.RouteParentStatus {
+	return r.Status.Parents
+}
+func (r TLSRouteAdapter) GetParentRefs() []gatewayv1.ParentReference {
+	return r.Spec.ParentRefs
+}
+func (r TLSRouteAdapter) GetObject() client.Object {
+	return r.TLSRoute
+}
+
+type RouteAdapter interface {
+	client.Object
+	GetParentStatuses() []gatewayv1.RouteParentStatus
+	GetParentRefs() []gatewayv1.ParentReference
+	GetObject() client.Object
+}
+
+func NewRouteAdapter(obj client.Object) RouteAdapter {
+	switch r := obj.(type) {
+	case *gatewayv1.HTTPRoute:
+		return &HTTPRouteAdapter{HTTPRoute: r}
+	case *gatewayv1.GRPCRoute:
+		return &GRPCRouteAdapter{GRPCRoute: r}
+	case *gatewayv1alpha2.TLSRoute:
+		return &TLSRouteAdapter{TLSRoute: r}
+	case *gatewayv1alpha2.TCPRoute:
+		return &TCPRouteAdapter{TCPRoute: r}
+	case *gatewayv1alpha2.UDPRoute:
+		return &UDPRouteAdapter{UDPRoute: r}
+	default:
+		return nil
+	}
+}
+
+func NewRouteListAdapter(objList client.ObjectList) []RouteAdapter {
+	switch r := objList.(type) {
+	case *gatewayv1.HTTPRouteList:
+		return lo.Map(r.Items, func(item gatewayv1.HTTPRoute, _ int) RouteAdapter {
+			return &HTTPRouteAdapter{HTTPRoute: &item}
+		})
+	case *gatewayv1.GRPCRouteList:
+		return lo.Map(r.Items, func(item gatewayv1.GRPCRoute, _ int) RouteAdapter {
+			return &GRPCRouteAdapter{GRPCRoute: &item}
+		})
+	case *gatewayv1alpha2.TLSRouteList:
+		return lo.Map(r.Items, func(item gatewayv1alpha2.TLSRoute, _ int) RouteAdapter {
+			return &TLSRouteAdapter{TLSRoute: &item}
+		})
+	case *gatewayv1alpha2.TCPRouteList:
+		return lo.Map(r.Items, func(item gatewayv1alpha2.TCPRoute, _ int) RouteAdapter {
+			return &TCPRouteAdapter{TCPRoute: &item}
+		})
+	case *gatewayv1alpha2.UDPRouteList:
+		return lo.Map(r.Items, func(item gatewayv1alpha2.UDPRoute, _ int) RouteAdapter {
+			return &UDPRouteAdapter{UDPRoute: &item}
+		})
+	default:
+		return nil
+	}
+}
diff --git a/internal/webhook/v1/apisixconsumer_webhook.go b/internal/webhook/v1/apisixconsumer_webhook.go
index ad22d4b2..b0561259 100644
--- a/internal/webhook/v1/apisixconsumer_webhook.go
+++ b/internal/webhook/v1/apisixconsumer_webhook.go
@@ -42,7 +42,7 @@ func SetupApisixConsumerWebhookWithManager(mgr ctrl.Manager) error {
 		Complete()
 }
 
-// +kubebuilder:webhook:path=/validate-apisix-apache-org-v2-apisixconsumer,mutating=false,failurePolicy=fail,sideEffects=None,groups=apisix.apache.org,resources=apisixconsumers,verbs=create;update,versions=v2,name=vapisixconsumer-v2.kb.io,admissionReviewVersions=v1
+// +kubebuilder:webhook:path=/validate-apisix-apache-org-v2-apisixconsumer,mutating=false,failurePolicy=fail,sideEffects=None,groups=apisix.apache.org,resources=apisixconsumers,verbs=create;update,versions=v2,name=vapisixconsumer-v2.kb.io,admissionReviewVersions=v1,failurePolicy=Ignore
 
 type ApisixConsumerCustomValidator struct {
 	Client  client.Client
diff --git a/internal/webhook/v1/apisixroute_webhook.go b/internal/webhook/v1/apisixroute_webhook.go
index aeb801ec..31ec796e 100644
--- a/internal/webhook/v1/apisixroute_webhook.go
+++ b/internal/webhook/v1/apisixroute_webhook.go
@@ -41,7 +41,7 @@ func SetupApisixRouteWebhookWithManager(mgr ctrl.Manager) error {
 		Complete()
 }
 
-// +kubebuilder:webhook:path=/validate-apisix-apache-org-v2-apisixroute,mutating=false,failurePolicy=fail,sideEffects=None,groups=apisix.apache.org,resources=apisixroutes,verbs=create;update,versions=v2,name=vapisixroute-v2.kb.io,admissionReviewVersions=v1
+// +kubebuilder:webhook:path=/validate-apisix-apache-org-v2-apisixroute,mutating=false,failurePolicy=fail,sideEffects=None,groups=apisix.apache.org,resources=apisixroutes,verbs=create;update,versions=v2,name=vapisixroute-v2.kb.io,admissionReviewVersions=v1,failurePolicy=Ignore
 
 type ApisixRouteCustomValidator struct {
 	Client  client.Client
diff --git a/internal/webhook/v1/apisixtls_webhook.go b/internal/webhook/v1/apisixtls_webhook.go
index a033fc48..985c3b87 100644
--- a/internal/webhook/v1/apisixtls_webhook.go
+++ b/internal/webhook/v1/apisixtls_webhook.go
@@ -42,7 +42,7 @@ func SetupApisixTlsWebhookWithManager(mgr ctrl.Manager) error {
 		Complete()
 }
 
-// +kubebuilder:webhook:path=/validate-apisix-apache-org-v2-apisixtls,mutating=false,failurePolicy=fail,sideEffects=None,groups=apisix.apache.org,resources=apisixtlses,verbs=create;update,versions=v2,name=vapisixtls-v2.kb.io,admissionReviewVersions=v1
+// +kubebuilder:webhook:path=/validate-apisix-apache-org-v2-apisixtls,mutating=false,failurePolicy=fail,sideEffects=None,groups=apisix.apache.org,resources=apisixtlses,verbs=create;update,versions=v2,name=vapisixtls-v2.kb.io,admissionReviewVersions=v1,failurePolicy=Ignore
 
 type ApisixTlsCustomValidator struct {
 	Client  client.Client
diff --git a/internal/webhook/v1/consumer_webhook.go b/internal/webhook/v1/consumer_webhook.go
index 0b74c714..f9b3bd77 100644
--- a/internal/webhook/v1/consumer_webhook.go
+++ b/internal/webhook/v1/consumer_webhook.go
@@ -41,7 +41,7 @@ func SetupConsumerWebhookWithManager(mgr ctrl.Manager) error {
 		Complete()
 }
 
-// +kubebuilder:webhook:path=/validate-apisix-apache-org-v1alpha1-consumer,mutating=false,failurePolicy=fail,sideEffects=None,groups=apisix.apache.org,resources=consumers,verbs=create;update,versions=v1alpha1,name=vconsumer-v1alpha1.kb.io,admissionReviewVersions=v1
+// +kubebuilder:webhook:path=/validate-apisix-apache-org-v1alpha1-consumer,mutating=false,failurePolicy=fail,sideEffects=None,groups=apisix.apache.org,resources=consumers,verbs=create;update,versions=v1alpha1,name=vconsumer-v1alpha1.kb.io,admissionReviewVersions=v1,failurePolicy=Ignore
 
 type ConsumerCustomValidator struct {
 	Client  client.Client
diff --git a/internal/webhook/v1/gateway_webhook.go b/internal/webhook/v1/gateway_webhook.go
index baf67784..55b7900b 100644
--- a/internal/webhook/v1/gateway_webhook.go
+++ b/internal/webhook/v1/gateway_webhook.go
@@ -49,7 +49,7 @@ func SetupGatewayWebhookWithManager(mgr ctrl.Manager) error {
 
 // NOTE: The 'path' attribute must follow a specific pattern and should not be modified directly here.
 // Modifying the path for an invalid path can cause API server errors; failing to locate the webhook.
-// +kubebuilder:webhook:path=/validate-gateway-networking-k8s-io-v1-gateway,mutating=false,failurePolicy=fail,sideEffects=None,groups=gateway.networking.k8s.io,resources=gateways,verbs=create;update,versions=v1,name=vgateway-v1.kb.io,admissionReviewVersions=v1
+// +kubebuilder:webhook:path=/validate-gateway-networking-k8s-io-v1-gateway,mutating=false,failurePolicy=fail,sideEffects=None,groups=gateway.networking.k8s.io,resources=gateways,verbs=create;update,versions=v1,name=vgateway-v1.kb.io,admissionReviewVersions=v1,failurePolicy=Ignore
 
 // GatewayCustomValidator struct is responsible for validating the Gateway resource
 // when it is created, updated, or deleted.
diff --git a/internal/webhook/v1/gatewayproxy_webhook.go b/internal/webhook/v1/gatewayproxy_webhook.go
index b76a8cf7..18ced0a4 100644
--- a/internal/webhook/v1/gatewayproxy_webhook.go
+++ b/internal/webhook/v1/gatewayproxy_webhook.go
@@ -42,7 +42,7 @@ func SetupGatewayProxyWebhookWithManager(mgr ctrl.Manager) error {
 		Complete()
 }
 
-// +kubebuilder:webhook:path=/validate-apisix-apache-org-v1alpha1-gatewayproxy,mutating=false,failurePolicy=fail,sideEffects=None,groups=apisix.apache.org,resources=gatewayproxies,verbs=create;update,versions=v1alpha1,name=vgatewayproxy-v1alpha1.kb.io,admissionReviewVersions=v1
+// +kubebuilder:webhook:path=/validate-apisix-apache-org-v1alpha1-gatewayproxy,mutating=false,failurePolicy=fail,sideEffects=None,groups=apisix.apache.org,resources=gatewayproxies,verbs=create;update,versions=v1alpha1,name=vgatewayproxy-v1alpha1.kb.io,admissionReviewVersions=v1,failurePolicy=Ignore
 
 type GatewayProxyCustomValidator struct {
 	Client  client.Client
diff --git a/internal/webhook/v1/grpcroute_webhook.go b/internal/webhook/v1/grpcroute_webhook.go
index 26d0e9f3..acb95c72 100644
--- a/internal/webhook/v1/grpcroute_webhook.go
+++ b/internal/webhook/v1/grpcroute_webhook.go
@@ -42,7 +42,7 @@ func SetupGRPCRouteWebhookWithManager(mgr ctrl.Manager) error {
 		Complete()
 }
 
-// +kubebuilder:webhook:path=/validate-gateway-networking-k8s-io-v1-grpcroute,mutating=false,failurePolicy=fail,sideEffects=None,groups=gateway.networking.k8s.io,resources=grpcroutes,verbs=create;update,versions=v1,name=vgrpcroute-v1.kb.io,admissionReviewVersions=v1
+// +kubebuilder:webhook:path=/validate-gateway-networking-k8s-io-v1-grpcroute,mutating=false,failurePolicy=fail,sideEffects=None,groups=gateway.networking.k8s.io,resources=grpcroutes,verbs=create;update,versions=v1,name=vgrpcroute-v1.kb.io,admissionReviewVersions=v1,failurePolicy=Ignore
 
 type GRPCRouteCustomValidator struct {
 	Client  client.Client
diff --git a/internal/webhook/v1/httproute_webhook.go b/internal/webhook/v1/httproute_webhook.go
index 191cd9b6..7cf8fe99 100644
--- a/internal/webhook/v1/httproute_webhook.go
+++ b/internal/webhook/v1/httproute_webhook.go
@@ -42,7 +42,7 @@ func SetupHTTPRouteWebhookWithManager(mgr ctrl.Manager) error {
 		Complete()
 }
 
-// +kubebuilder:webhook:path=/validate-gateway-networking-k8s-io-v1-httproute,mutating=false,failurePolicy=fail,sideEffects=None,groups=gateway.networking.k8s.io,resources=httproutes,verbs=create;update,versions=v1,name=vhttproute-v1.kb.io,admissionReviewVersions=v1
+// +kubebuilder:webhook:path=/validate-gateway-networking-k8s-io-v1-httproute,mutating=false,failurePolicy=fail,sideEffects=None,groups=gateway.networking.k8s.io,resources=httproutes,verbs=create;update,versions=v1,name=vhttproute-v1.kb.io,admissionReviewVersions=v1,failurePolicy=Ignore
 
 type HTTPRouteCustomValidator struct {
 	Client  client.Client
diff --git a/internal/webhook/v1/ingress_webhook.go b/internal/webhook/v1/ingress_webhook.go
index 292e2043..3ba2af4e 100644
--- a/internal/webhook/v1/ingress_webhook.go
+++ b/internal/webhook/v1/ingress_webhook.go
@@ -44,7 +44,7 @@ func SetupIngressWebhookWithManager(mgr ctrl.Manager) error {
 
 // NOTE: The 'path' attribute must follow a specific pattern and should not be modified directly here.
 // Modifying the path for an invalid path can cause API server errors; failing to locate the webhook.
-// +kubebuilder:webhook:path=/validate-networking-k8s-io-v1-ingress,mutating=false,failurePolicy=fail,sideEffects=None,groups=networking.k8s.io,resources=ingresses,verbs=create;update,versions=v1,name=vingress-v1.kb.io,admissionReviewVersions=v1
+// +kubebuilder:webhook:path=/validate-networking-k8s-io-v1-ingress,mutating=false,failurePolicy=fail,sideEffects=None,groups=networking.k8s.io,resources=ingresses,verbs=create;update,versions=v1,name=vingress-v1.kb.io,admissionReviewVersions=v1,failurePolicy=Ignore
 
 // IngressCustomValidator struct is responsible for validating the Ingress resource
 // when it is created, updated, or deleted.
diff --git a/internal/webhook/v1/ingressclass_webhook.go b/internal/webhook/v1/ingressclass_webhook.go
index 06ae18e9..4d77fe8a 100644
--- a/internal/webhook/v1/ingressclass_webhook.go
+++ b/internal/webhook/v1/ingressclass_webhook.go
@@ -46,7 +46,7 @@ func SetupIngressClassWebhookWithManager(mgr ctrl.Manager) error {
 
 // NOTE: The 'path' attribute must follow a specific pattern and should not be modified directly here.
 // Modifying the path for an invalid path can cause API server errors; failing to locate the webhook.
-// +kubebuilder:webhook:path=/validate-networking-k8s-io-v1-ingressclass,mutating=false,failurePolicy=fail,sideEffects=None,groups=networking.k8s.io,resources=ingressclasses,verbs=create;update,versions=v1,name=vingressclass-v1.kb.io,admissionReviewVersions=v1
+// +kubebuilder:webhook:path=/validate-networking-k8s-io-v1-ingressclass,mutating=false,failurePolicy=fail,sideEffects=None,groups=networking.k8s.io,resources=ingressclasses,verbs=create;update,versions=v1,name=vingressclass-v1.kb.io,admissionReviewVersions=v1,failurePolicy=Ignore
 
 // IngressClassCustomValidator struct is responsible for validating the IngressClass resource
 // when it is created, updated, or deleted.
diff --git a/internal/webhook/v1/tcproute_webhook.go b/internal/webhook/v1/tcproute_webhook.go
index 7edc6a0c..39ef5af6 100644
--- a/internal/webhook/v1/tcproute_webhook.go
+++ b/internal/webhook/v1/tcproute_webhook.go
@@ -42,7 +42,7 @@ func SetupTCPRouteWebhookWithManager(mgr ctrl.Manager) error {
 		Complete()
 }
 
-// +kubebuilder:webhook:path=/validate-gateway-networking-k8s-io-v1alpha2-tcproute,mutating=false,failurePolicy=fail,sideEffects=None,groups=gateway.networking.k8s.io,resources=tcproutes,verbs=create;update,versions=v1alpha2,name=vtcproute-v1alpha2.kb.io,admissionReviewVersions=v1
+// +kubebuilder:webhook:path=/validate-gateway-networking-k8s-io-v1alpha2-tcproute,mutating=false,failurePolicy=fail,sideEffects=None,groups=gateway.networking.k8s.io,resources=tcproutes,verbs=create;update,versions=v1alpha2,name=vtcproute-v1alpha2.kb.io,admissionReviewVersions=v1,failurePolicy=Ignore
 
 type TCPRouteCustomValidator struct {
 	Client  client.Client
diff --git a/internal/webhook/v1/udproute_webhook.go b/internal/webhook/v1/udproute_webhook.go
index 23cfd81a..aeb40eab 100644
--- a/internal/webhook/v1/udproute_webhook.go
+++ b/internal/webhook/v1/udproute_webhook.go
@@ -42,7 +42,7 @@ func SetupUDPRouteWebhookWithManager(mgr ctrl.Manager) error {
 		Complete()
 }
 
-// +kubebuilder:webhook:path=/validate-gateway-networking-k8s-io-v1alpha2-udproute,mutating=false,failurePolicy=fail,sideEffects=None,groups=gateway.networking.k8s.io,resources=udproutes,verbs=create;update,versions=v1alpha2,name=vudproute-v1alpha2.kb.io,admissionReviewVersions=v1
+// +kubebuilder:webhook:path=/validate-gateway-networking-k8s-io-v1alpha2-udproute,mutating=false,failurePolicy=fail,sideEffects=None,groups=gateway.networking.k8s.io,resources=udproutes,verbs=create;update,versions=v1alpha2,name=vudproute-v1alpha2.kb.io,admissionReviewVersions=v1,failurePolicy=Ignore
 
 type UDPRouteCustomValidator struct {
 	Client  client.Client



Test patch:
diff --git a/test/e2e/gatewayapi/gateway.go b/test/e2e/gatewayapi/gateway.go
index f1e8ca3a..8c5557ef 100644
--- a/test/e2e/gatewayapi/gateway.go
+++ b/test/e2e/gatewayapi/gateway.go
@@ -26,6 +26,9 @@ import (
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
 	"github.com/stretchr/testify/assert"
+	k8stypes "k8s.io/apimachinery/pkg/types"
+	"k8s.io/utils/ptr"
+	gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
 
 	"github.com/apache/apisix-ingress-controller/test/e2e/framework"
 	"github.com/apache/apisix-ingress-controller/test/e2e/scaffold"
@@ -315,4 +318,225 @@ spec:
 			}).WithTimeout(20 * time.Second).ProbeEvery(time.Second).Should(Equal(framework.TestCert))
 		})
 	})
+
+	Context("Gateway Status", func() {
+		var gatewaySpec = `
+apiVersion: gateway.networking.k8s.io/v1
+kind: Gateway
+metadata:
+  name: %s
+spec:
+  gatewayClassName: %s
+  listeners:
+  - name: http
+    protocol: HTTP
+    port: 80
+  - name: tcp
+    protocol: TCP
+    port: 9000
+    allowedRoutes:
+      kinds:
+      - kind: TCPRoute
+  - name: udp
+    protocol: UDP
+    port: 80
+    allowedRoutes:
+      kinds:
+      - kind: UDPRoute
+  infrastructure:
+    parametersRef:
+      group: apisix.apache.org
+      kind: GatewayProxy
+      name: apisix-proxy-config
+`
+		var httprouteSpec = `
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: %s
+spec:
+  parentRefs:
+  - name: %s
+  hostnames:
+  - httpbin.org
+  rules:
+  - matches: 
+    - path:
+        type: Exact
+        value: /get
+    backendRefs:
+    - name: httpbin-service-e2e-test
+      port: 80
+`
+		var grpcrouteSpec = `
+apiVersion: gateway.networking.k8s.io/v1
+kind: GRPCRoute
+metadata:
+  name: %s
+spec:
+  parentRefs:
+  - name: %s
+  rules:
+  - backendRefs:
+    - name: grpc-infra-backend-v1
+      port: 8080
+`
+		var tcpRouteSpec = `
+apiVersion: gateway.networking.k8s.io/v1alpha2
+kind: TCPRoute
+metadata:
+  name: %s
+spec:
+  parentRefs:
+  - name: %s
+    sectionName: tcp
+  rules:
+  - backendRefs:
+    - name: httpbin-service-e2e-test
+      port: 80
+`
+		var udpRoute = `
+apiVersion: gateway.networking.k8s.io/v1alpha2
+kind: UDPRoute
+metadata:
+  name: %s
+spec:
+  parentRefs:
+  - name: %s
+    sectionName: udp
+  rules:
+  - backendRefs:
+    - name: %s
+      port: %d
+`
+		var (
+			dnsName string
+			dnsPort int32
+		)
+
+		BeforeEach(func() {
+			dnsSvc := s.NewCoreDNSService()
+			dnsName = dnsSvc.Name
+			dnsPort = dnsSvc.Spec.Ports[0].Port
+
+			By("deploy grpc backend")
+			s.DeployGRPCBackend()
+
+			By("create GatewayProxy")
+			Expect(s.CreateResourceFromString(s.GetGatewayProxySpec())).NotTo(HaveOccurred(), "creating GatewayProxy")
+
+			By("create GatewayClass")
+			Expect(s.CreateResourceFromString(s.GetGatewayClassYaml())).NotTo(HaveOccurred(), "creating GatewayClass")
+
+			s.RetryAssertion(func() string {
+				gcyaml, _ := s.GetResourceYaml("GatewayClass", s.Namespace())
+				return gcyaml
+			}).Should(
+				And(
+					ContainSubstring(`status: "True"`),
+					ContainSubstring("message: the gatewayclass has been accepted by the apisix-ingress-controller"),
+				),
+				"check GatewayClass condition",
+			)
+
+			By("create Gateway")
+			gateway := fmt.Sprintf(gatewaySpec, s.Namespace(), s.Namespace())
+			Expect(s.CreateResourceFromString(gateway)).NotTo(HaveOccurred(), "creating Gateway")
+
+			s.RetryAssertion(func() string {
+				gcyaml, _ := s.GetResourceYaml("Gateway", s.Namespace())
+				return gcyaml
+			}).Should(
+				And(
+					ContainSubstring(`status: "True"`),
+					ContainSubstring("message: the gateway has been accepted by the apisix-ingress-controller"),
+				),
+				"check Gateway condition status",
+			)
+		})
+		getStatusLintener := func(listenerName string) (*gatewayv1.ListenerStatus, error) {
+			var gateway gatewayv1.Gateway
+			if err := s.GetKubeClient().Get(context.Background(), k8stypes.NamespacedName{
+				Name:      s.Namespace(),
+				Namespace: s.Namespace(),
+			}, &gateway); err != nil {
+				return nil, err
+			}
+			for _, listener := range gateway.Status.Listeners {
+				if string(listener.Name) == listenerName {
+					return &listener, nil
+				}
+			}
+			return nil, fmt.Errorf("listener %s not found", listenerName)
+		}
+		expectListenerAttachedRoutes := func(listenerName string, accectedRoutes int) {
+			s.RetryAssertion(func() error {
+				listener, err := getStatusLintener(listenerName)
+				if err != nil {
+					return err
+				}
+				if listener.AttachedRoutes != int32(accectedRoutes) {
+					return fmt.Errorf("expected listener %s attached routes to be %d, got %d", listener.Name, accectedRoutes, listener.AttachedRoutes)
+				}
+				return nil
+			}).ShouldNot(HaveOccurred(), "check listener attached routes")
+		}
+		It("check attachedRoutes and supportedkinds to gateway status", func() {
+			By("check HTTPRoute/GRPCRoute attachedRoutes and supportedkinds to gateway status")
+			s.ResourceApplied("HTTPRoute", "httpbin", fmt.Sprintf(httprouteSpec, "httpbin", s.Namespace()), 1)
+			expectListenerAttachedRoutes("http", 1)
+			for i := 0; i < 10; i++ {
+				name := fmt.Sprintf("httpbin-%d", i)
+				s.ResourceApplied("HTTPRoute", name, fmt.Sprintf(httprouteSpec, name, s.Namespace()), 1)
+			}
+			expectListenerAttachedRoutes("http", 11)
+			for i := 0; i < 10; i++ {
+				name := fmt.Sprintf("grcproute-%d", i)
+				s.ResourceApplied("GRPCRoute", name, fmt.Sprintf(grpcrouteSpec, name, s.Namespace()), 1)
+			}
+			expectListenerAttachedRoutes("http", 21)
+
+			listenner, err := getStatusLintener("http")
+			Expect(err).NotTo(HaveOccurred(), "get http listener status")
+			Expect(listenner.SupportedKinds).To(HaveLen(2), "http listener supported kinds length")
+			Expect(listenner.SupportedKinds).To(ContainElements(
+				gatewayv1.RouteGroupKind{
+					Group: ptr.To(gatewayv1.Group(gatewayv1.GroupName)),
+					Kind:  "HTTPRoute",
+				},
+				gatewayv1.RouteGroupKind{
+					Group: ptr.To(gatewayv1.Group(gatewayv1.GroupName)),
+					Kind:  "GRPCRoute",
+				},
+			), "http listener supported kinds content")
+
+			By("check TCPRoute attachedRoutes and supportedkinds to gateway status")
+			name := "tcp-route"
+			s.ResourceApplied("TCPRoute", name, fmt.Sprintf(tcpRouteSpec, name, s.Namespace()), 1)
+			expectListenerAttachedRoutes("tcp", 1)
+			for i := 0; i < 10; i++ {
+				name := fmt.Sprintf("tcp-route-%d", i)
+				s.ResourceApplied("TCPRoute", name, fmt.Sprintf(tcpRouteSpec, name, s.Namespace()), 1)
+			}
+			expectListenerAttachedRoutes("tcp", 11)
+			getListener, err := getStatusLintener("tcp")
+			Expect(err).NotTo(HaveOccurred(), "get tcp listener status")
+			Expect(getListener.SupportedKinds).To(HaveLen(1), "tcp listener supported kinds length")
+			Expect(string(getListener.SupportedKinds[0].Kind)).To(Equal("TCPRoute"), "tcp listener supported kind content")
+
+			By("check UDPRoute attachedRoutes and supportedkinds to gateway status")
+			name = "udp-route"
+			s.ResourceApplied("UDPRoute", name, fmt.Sprintf(udpRoute, name, s.Namespace(), dnsName, dnsPort), 1)
+			expectListenerAttachedRoutes("udp", 1)
+			for i := 0; i < 10; i++ {
+				name := fmt.Sprintf("udp-route-%d", i)
+				s.ResourceApplied("UDPRoute", name, fmt.Sprintf(udpRoute, name, s.Namespace(), dnsName, dnsPort), 1)
+			}
+			expectListenerAttachedRoutes("udp", 11)
+			getListener, err = getStatusLintener("udp")
+			Expect(err).NotTo(HaveOccurred(), "get udp listener status")
+			Expect(getListener.SupportedKinds).To(HaveLen(1), "udp listener supported kinds length")
+			Expect(string(getListener.SupportedKinds[0].Kind)).To(Equal("UDPRoute"), "udp listener supported kind content")
+		})
+	})
 })
diff --git a/test/e2e/scaffold/k8s.go b/test/e2e/scaffold/k8s.go
index e99e4207..99fa57db 100644
--- a/test/e2e/scaffold/k8s.go
+++ b/test/e2e/scaffold/k8s.go
@@ -36,12 +36,16 @@ import (
 	"github.com/stretchr/testify/require"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/util/wait"
 	"k8s.io/client-go/kubernetes"
+	"k8s.io/client-go/tools/clientcmd"
+	client "sigs.k8s.io/controller-runtime/pkg/client"
 	gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
 	"sigs.k8s.io/gateway-api/apis/v1alpha2"
 
+	apiv2 "github.com/apache/apisix-ingress-controller/api/v2"
 	"github.com/apache/apisix-ingress-controller/test/e2e/framework"
 )
 
@@ -366,3 +370,18 @@ func (s *Scaffold) SetupWebhookResources() error {
 
 	return s.CreateResourceFromStringWithNamespace(buf.String(), "")
 }
+
+func (s *Scaffold) GetKubeClient() client.Client {
+	if s.client == nil {
+		scheme := runtime.NewScheme()
+		_ = apiv2.AddToScheme(scheme)
+		_ = corev1.AddToScheme(scheme)
+		_ = gatewayv1.Install(scheme)
+		_ = v1alpha2.Install(scheme)
+		cfg, err := clientcmd.BuildConfigFromFlags("", s.opts.Kubeconfig)
+		Expect(err).NotTo(HaveOccurred(), "building kubeconfig")
+		s.client, err = client.New(cfg, client.Options{Scheme: scheme})
+		Expect(err).NotTo(HaveOccurred(), "building controller-runtime client")
+	}
+	return s.client
+}
diff --git a/test/e2e/scaffold/scaffold.go b/test/e2e/scaffold/scaffold.go
index 432732ac..c23acd6a 100644
--- a/test/e2e/scaffold/scaffold.go
+++ b/test/e2e/scaffold/scaffold.go
@@ -34,6 +34,7 @@ import (
 	. "github.com/onsi/gomega"    //nolint:staticcheck
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	apiv2 "github.com/apache/apisix-ingress-controller/api/v2"
 	"github.com/apache/apisix-ingress-controller/test/e2e/framework"
@@ -74,6 +75,8 @@ type Scaffold struct {
 
 	runtimeOpts Options
 	Deployer    Deployer
+
+	client client.Client
 }
 
 type Tunnels struct {