Repository: kubernetes-sigs/gateway-api

Issue:
GEP: Backend Protocol Selection
/kind gep

This is an attempt to separate out the backend protocol selection from [GEP-1282](https://github.com/kubernetes-sigs/gateway-api/issues/1282). This is similar to how TLS use-cases are being split out into [GEP-1897](https://github.com/kubernetes-sigs/gateway-api/issues/1897)


<!-- Please only use this template for submitting enhancement requests -->

**What would you like to be added**:

The ability to express the protocols supported by backends.

**Why this is needed**:

Not all implementation support automatic protocol selection. Even in some cases protocols are disabled without an explicit opt-in (ie. websockets). Thus application developers need the ability specify the protocol of their applications.



Discussion:
@dprotaso: /assign @dprotaso @dprotaso: For those following GEP PR is out - https://github.com/kubernetes-sigs/gateway-api/pull/1979@shaneutt: Since the GEP already exists and _because_ it's a GEP it seems reasonable to consider this accepted:

/triage accepted

However, it doesn't seem clear to me that this is something we _must_ sort out prior to GA (though I invite arguments to the contrary) so at least for now we wont consider this a blocker for GA and it will have a lower priority than GA-oriented work:

/priority backlog@dprotaso: @shaneutt the GA backlog has #1282 -  you could say this GEP and #1897 are part the fallout of that original GEP
@shaneutt: ACK

Ultimately it's just not clear that this is something we _must_ have for GA :thinking: 

Since you're working on it anyway, let's go ahead and track it on the board. But we should probably take another look at what it's gonna take to implement and do some planning and prioritization then.@robscott: Leaving this open until this GEP either graduates to standard or is removed from the API.

/kind gep
/reopen@k8s-ci-robot: @robscott: Reopened this issue.

<details>

In response to [this](https://github.com/kubernetes-sigs/gateway-api/issues/1911#issuecomment-1836963386):

>Leaving this open until this GEP either graduates to standard or is removed from the API.
>
>/kind gep
>/reopen


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/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>@k8s-triage-robot: This issue has not been updated in over 1 year, and should be re-triaged.

You can:
- Confirm that this issue is still relevant with `/triage accepted` (org members only)
- Close this issue with `/close`

For more details on the triage process, see https://www.kubernetes.dev/docs/guide/issue-triage/

/remove-triage accepted@k8s-triage-robot: The Kubernetes project currently lacks enough contributors to adequately respond to all issues.

This bot triages un-triaged issues according to the following rules:
- After 90d of inactivity, `lifecycle/stale` is applied
- After 30d of inactivity since `lifecycle/stale` was applied, `lifecycle/rotten` is applied
- After 30d of inactivity since `lifecycle/rotten` was applied, the issue is closed

You can:
- Mark this issue as fresh with `/remove-lifecycle stale`
- Close this issue with `/close`
- Offer to help out with [Issue Triage][1]

Please send feedback to sig-contributor-experience at [kubernetes/community](https://github.com/kubernetes/community).

/lifecycle stale

[1]: https://www.kubernetes.dev/docs/guide/issue-triage/@aviallon: /close
Implemented by https://github.com/kubernetes-sigs/gateway-api/pull/1979@k8s-ci-robot: @aviallon: Closing this issue.

<details>

In response to [this](https://github.com/kubernetes-sigs/gateway-api/issues/1911#issuecomment-2748596744):

>/close
>Implemented by https://github.com/kubernetes-sigs/gateway-api/pull/1979


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>

Solution patch:
diff --git a/OWNERS_ALIASES b/OWNERS_ALIASES
index dff14182..576d99c7 100644
--- a/OWNERS_ALIASES
+++ b/OWNERS_ALIASES
@@ -4,8 +4,8 @@
 aliases:
   # Reference: https://github.com/kubernetes/org/blob/main/OWNERS_ALIASES
   sig-network-leads:
-    - caseydavenport
-    - dcbw
+    - danwinship
+    - shaneutt
     - thockin
 
   # Reference: https://github.com/kubernetes/org/blob/main/config/kubernetes-sigs/sig-network/teams.yaml
diff --git a/README.md b/README.md
index 868e6458..6452ff2f 100644
--- a/README.md
+++ b/README.md
@@ -60,6 +60,6 @@ Participation in the Kubernetes community is governed by the
 [cm]: https://gateway-api.sigs.k8s.io/contributing/community
 [slack]: https://kubernetes.slack.com/messages/sig-network-gateway-api
 [getting-started]: https://gateway-api.sigs.k8s.io/v1alpha2/guides
-[spec]: https://gateway-api.sigs.k8s.io/v1alpha2/references/spec
+[spec]: https://gateway-api.sigs.k8s.io/v1alpha2/reference/spec
 [concepts]: https://gateway-api.sigs.k8s.io/concepts/api-overview
 [security-model]: https://gateway-api.sigs.k8s.io/concepts/security-model
diff --git a/apis/v1/gateway_types.go b/apis/v1/gateway_types.go
index 7101a94c..20cca8ec 100644
--- a/apis/v1/gateway_types.go
+++ b/apis/v1/gateway_types.go
@@ -227,7 +227,7 @@ type GatewaySpec struct {
 	//
 	// <gateway:experimental>
 	// +optional
-	Infrastructure GatewayInfrastructure `json:"infrastructure,omitempty"`
+	Infrastructure *GatewayInfrastructure `json:"infrastructure,omitempty"`
 }
 
 // Listener embodies the concept of a logical endpoint where a Gateway accepts
@@ -636,6 +636,8 @@ type GatewayInfrastructure struct {
 	// An implementation may chose to add additional implementation-specific labels as they see fit.
 	//
 	// Support: Extended
+	//
+	// +optional
 	// +kubebuilder:validation:MaxProperties=8
 	Labels map[AnnotationKey]AnnotationValue `json:"labels,omitempty"`
 
@@ -647,6 +649,8 @@ type GatewayInfrastructure struct {
 	// An implementation may chose to add additional implementation-specific annotations as they see fit.
 	//
 	// Support: Extended
+	//
+	// +optional
 	// +kubebuilder:validation:MaxProperties=8
 	Annotations map[AnnotationKey]AnnotationValue `json:"annotations,omitempty"`
 }
diff --git a/apis/v1/zz_generated.deepcopy.go b/apis/v1/zz_generated.deepcopy.go
index 25fadfa8..8d047a28 100644
--- a/apis/v1/zz_generated.deepcopy.go
+++ b/apis/v1/zz_generated.deepcopy.go
@@ -366,7 +366,11 @@ func (in *GatewaySpec) DeepCopyInto(out *GatewaySpec) {
 			(*in)[i].DeepCopyInto(&(*out)[i])
 		}
 	}
-	in.Infrastructure.DeepCopyInto(&out.Infrastructure)
+	if in.Infrastructure != nil {
+		in, out := &in.Infrastructure, &out.Infrastructure
+		*out = new(GatewayInfrastructure)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewaySpec.
diff --git a/apis/v1alpha2/backendtlspolicy_types.go b/apis/v1alpha2/backendtlspolicy_types.go
index bb8b696d..ee8c2771 100644
--- a/apis/v1alpha2/backendtlspolicy_types.go
+++ b/apis/v1alpha2/backendtlspolicy_types.go
@@ -75,8 +75,8 @@ type BackendTLSPolicySpec struct {
 }
 
 // BackendTLSPolicyConfig contains backend TLS policy configuration.
-// +kubebuilder:validation:XValidation:message="must not contain both CertRefs and WellKnownCACerts",rule="(has(self.caCertRefs) && size(self.caCertRefs) > 0 && has(self.wellKnownCACerts) && self.wellKnownCACerts != \"\")"
-// +kubebuilder:validation:XValidation:message="must specify either CertRefs or WellKnownCACerts",rule="!(has(self.caCertRefs) && size(self.caCertRefs) > 0 || has(self.wellKnownCACerts) && self.wellKnownCACerts != \"\")"
+// +kubebuilder:validation:XValidation:message="must not contain both CACertRefs and WellKnownCACerts",rule="!(has(self.caCertRefs) && size(self.caCertRefs) > 0 && has(self.wellKnownCACerts) && self.wellKnownCACerts != \"\")"
+// +kubebuilder:validation:XValidation:message="must specify either CACertRefs or WellKnownCACerts",rule="(has(self.caCertRefs) && size(self.caCertRefs) > 0 || has(self.wellKnownCACerts) && self.wellKnownCACerts != \"\")"
 type BackendTLSPolicyConfig struct {
 	// CACertRefs contains one or more references to Kubernetes objects that
 	// contain a PEM-encoded TLS CA certificate bundle, which is used to
diff --git a/apis/v1alpha2/validation/gateway.go b/apis/v1alpha2/validation/gateway.go
index 2341171e..23c09668 100644
--- a/apis/v1alpha2/validation/gateway.go
+++ b/apis/v1alpha2/validation/gateway.go
@@ -26,7 +26,7 @@ import (
 // ValidateGateway validates gw according to the Gateway API specification.
 // For additional details of the Gateway spec, refer to:
 //
-//	https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.Gateway
+//	https://gateway-api.sigs.k8s.io/v1alpha2/reference/spec/#gateway.networking.k8s.io/v1alpha2.Gateway
 //
 // Validation that is not possible with CRD annotations may be added here in the future.
 // See https://github.com/kubernetes-sigs/gateway-api/issues/868 for more information.
diff --git a/apis/v1alpha2/validation/gatewayclass.go b/apis/v1alpha2/validation/gatewayclass.go
index 188d8a37..bdd35fb2 100644
--- a/apis/v1alpha2/validation/gatewayclass.go
+++ b/apis/v1alpha2/validation/gatewayclass.go
@@ -24,7 +24,7 @@ import (
 
 // ValidateGatewayClassUpdate validates an update to oldClass according to the
 // Gateway API specification. For additional details of the GatewayClass spec, refer to:
-// https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.GatewayClass
+// https://gateway-api.sigs.k8s.io/v1alpha2/reference/spec/#gateway.networking.k8s.io/v1alpha2.GatewayClass
 func ValidateGatewayClassUpdate(oldClass, newClass *gatewayv1a2.GatewayClass) field.ErrorList {
 	if oldClass == nil || newClass == nil {
 		return nil
diff --git a/apis/v1alpha2/validation/grpcroute.go b/apis/v1alpha2/validation/grpcroute.go
index d7e452d4..971caea7 100644
--- a/apis/v1alpha2/validation/grpcroute.go
+++ b/apis/v1alpha2/validation/grpcroute.go
@@ -42,7 +42,7 @@ var (
 
 // ValidateGRPCRoute validates GRPCRoute according to the Gateway API specification.
 // For additional details of the GRPCRoute spec, refer to:
-// https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.GRPCRoute
+// https://gateway-api.sigs.k8s.io/v1alpha2/reference/spec/#gateway.networking.k8s.io/v1alpha2.GRPCRoute
 func ValidateGRPCRoute(route *gatewayv1a2.GRPCRoute) field.ErrorList {
 	return validateGRPCRouteSpec(&route.Spec, field.NewPath("spec"))
 }
diff --git a/apis/v1alpha2/validation/httproute.go b/apis/v1alpha2/validation/httproute.go
index cc2f748b..c86805a7 100644
--- a/apis/v1alpha2/validation/httproute.go
+++ b/apis/v1alpha2/validation/httproute.go
@@ -25,7 +25,7 @@ import (
 
 // ValidateHTTPRoute validates HTTPRoute according to the Gateway API specification.
 // For additional details of the HTTPRoute spec, refer to:
-// https://gateway-api.sigs.k8s.io/v1beta1/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRoute
+// https://gateway-api.sigs.k8s.io/v1beta1/reference/spec/#gateway.networking.k8s.io/v1beta1.HTTPRoute
 func ValidateHTTPRoute(route *gatewayv1a2.HTTPRoute) field.ErrorList {
 	return gatewayv1b1validation.ValidateHTTPRouteSpec(&route.Spec, field.NewPath("spec"))
 }
diff --git a/apis/v1alpha2/validation/tcproute.go b/apis/v1alpha2/validation/tcproute.go
index 6e94748b..db03ac55 100644
--- a/apis/v1alpha2/validation/tcproute.go
+++ b/apis/v1alpha2/validation/tcproute.go
@@ -24,7 +24,7 @@ import (
 
 // ValidateTCPRoute validates TCPRoute according to the Gateway API specification.
 // For additional details of the TCPRoute spec, refer to:
-// https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.TCPRoute
+// https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1alpha2.TCPRoute
 func ValidateTCPRoute(route *gatewayv1a2.TCPRoute) field.ErrorList {
 	return validateTCPRouteSpec(&route.Spec, field.NewPath("spec"))
 }
diff --git a/apis/v1alpha2/validation/tlsroute.go b/apis/v1alpha2/validation/tlsroute.go
index b6d086a7..76127d4a 100644
--- a/apis/v1alpha2/validation/tlsroute.go
+++ b/apis/v1alpha2/validation/tlsroute.go
@@ -24,7 +24,7 @@ import (
 
 // ValidateTLSRoute validates TLSRoute according to the Gateway API specification.
 // For additional details of the TLSRoute spec, refer to:
-// https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.TLSRoute
+// https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1alpha2.TLSRoute
 func ValidateTLSRoute(route *gatewayv1a2.TLSRoute) field.ErrorList {
 	return validateTLSRouteSpec(&route.Spec, field.NewPath("spec"))
 }
diff --git a/apis/v1alpha2/validation/udproute.go b/apis/v1alpha2/validation/udproute.go
index 6a060afb..321d6a40 100644
--- a/apis/v1alpha2/validation/udproute.go
+++ b/apis/v1alpha2/validation/udproute.go
@@ -24,7 +24,7 @@ import (
 
 // ValidateUDPRoute validates UDPRoute according to the Gateway API specification.
 // For additional details of the UDPRoute spec, refer to:
-// https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.UDPRoute
+// https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1alpha2.UDPRoute
 func ValidateUDPRoute(route *gatewayv1a2.UDPRoute) field.ErrorList {
 	return validateUDPRouteSpec(&route.Spec, field.NewPath("spec"))
 }
diff --git a/apis/v1beta1/validation/gateway.go b/apis/v1beta1/validation/gateway.go
index cdc27930..7c0a69a8 100644
--- a/apis/v1beta1/validation/gateway.go
+++ b/apis/v1beta1/validation/gateway.go
@@ -53,7 +53,7 @@ var (
 // ValidateGateway validates gw according to the Gateway API specification.
 // For additional details of the Gateway spec, refer to:
 //
-//	https://gateway-api.sigs.k8s.io/v1beta1/references/spec/#gateway.networking.k8s.io/v1beta1.Gateway
+//	https://gateway-api.sigs.k8s.io/v1beta1/reference/spec/#gateway.networking.k8s.io/v1beta1.Gateway
 //
 // Validation that is not possible with CRD annotations may be added here in the future.
 // See https://github.com/kubernetes-sigs/gateway-api/issues/868 for more information.
diff --git a/apis/v1beta1/validation/gatewayclass.go b/apis/v1beta1/validation/gatewayclass.go
index bf71bd3d..22079d97 100644
--- a/apis/v1beta1/validation/gatewayclass.go
+++ b/apis/v1beta1/validation/gatewayclass.go
@@ -24,7 +24,7 @@ import (
 
 // ValidateGatewayClassUpdate validates an update to oldClass according to the
 // Gateway API specification. For additional details of the GatewayClass spec, refer to:
-// https://gateway-api.sigs.k8s.io/v1beta1/references/spec/#gateway.networking.k8s.io/v1beta1.GatewayClass
+// https://gateway-api.sigs.k8s.io/v1beta1/reference/spec/#gateway.networking.k8s.io/v1beta1.GatewayClass
 func ValidateGatewayClassUpdate(oldClass, newClass *gatewayv1b1.GatewayClass) field.ErrorList {
 	if oldClass == nil || newClass == nil {
 		return nil
diff --git a/apis/v1beta1/validation/httproute.go b/apis/v1beta1/validation/httproute.go
index d5cba86e..2ea69a6b 100644
--- a/apis/v1beta1/validation/httproute.go
+++ b/apis/v1beta1/validation/httproute.go
@@ -47,7 +47,7 @@ var (
 
 // ValidateHTTPRoute validates HTTPRoute according to the Gateway API specification.
 // For additional details of the HTTPRoute spec, refer to:
-// https://gateway-api.sigs.k8s.io/v1beta1/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRoute
+// https://gateway-api.sigs.k8s.io/v1beta1/reference/spec/#gateway.networking.k8s.io/v1beta1.HTTPRoute
 func ValidateHTTPRoute(route *gatewayv1b1.HTTPRoute) field.ErrorList {
 	return ValidateHTTPRouteSpec(&route.Spec, field.NewPath("spec"))
 }
diff --git a/config/crd/experimental/gateway.networking.k8s.io_backendtlspolicies.yaml b/config/crd/experimental/gateway.networking.k8s.io_backendtlspolicies.yaml
index e58f0405..4350edf4 100644
--- a/config/crd/experimental/gateway.networking.k8s.io_backendtlspolicies.yaml
+++ b/config/crd/experimental/gateway.networking.k8s.io_backendtlspolicies.yaml
@@ -178,12 +178,12 @@ spec:
                 - hostname
                 type: object
                 x-kubernetes-validations:
-                - message: must not contain both CertRefs and WellKnownCACerts
-                  rule: (has(self.caCertRefs) && size(self.caCertRefs) > 0 && has(self.wellKnownCACerts)
-                    && self.wellKnownCACerts != "")
-                - message: must specify either CertRefs or WellKnownCACerts
-                  rule: '!(has(self.caCertRefs) && size(self.caCertRefs) > 0 || has(self.wellKnownCACerts)
+                - message: must not contain both CACertRefs and WellKnownCACerts
+                  rule: '!(has(self.caCertRefs) && size(self.caCertRefs) > 0 && has(self.wellKnownCACerts)
                     && self.wellKnownCACerts != "")'
+                - message: must specify either CACertRefs or WellKnownCACerts
+                  rule: (has(self.caCertRefs) && size(self.caCertRefs) > 0 || has(self.wellKnownCACerts)
+                    && self.wellKnownCACerts != "")
             required:
             - targetRef
             - tls
diff --git a/conformance/base/manifests.yaml b/conformance/base/manifests.yaml
index f3b571ee..361797c6 100644
--- a/conformance/base/manifests.yaml
+++ b/conformance/base/manifests.yaml
@@ -91,9 +91,15 @@ spec:
   selector:
     app: infra-backend-v1
   ports:
-  - protocol: TCP
+  - name: first-port
+    protocol: TCP
     port: 8080
     targetPort: 3000
+  - name: second-port
+    protocol: TCP
+    appProtocol: kubernetes.io/h2c
+    port: 8081
+    targetPort: 3001
 ---
 apiVersion: apps/v1
 kind: Deployment
diff --git a/conformance/utils/http/http.go b/conformance/utils/http/http.go
index 784696d3..d63cb5fc 100644
--- a/conformance/utils/http/http.go
+++ b/conformance/utils/http/http.go
@@ -116,6 +116,10 @@ func MakeRequest(t *testing.T, expected *ExpectedResponse, gwAddr, protocol, sch
 		expected.Response.StatusCode = 200
 	}
 
+	if expected.Request.Protocol == "" {
+		expected.Request.Protocol = protocol
+	}
+
 	path, query, _ := strings.Cut(expected.Request.Path, "?")
 	reqURL := url.URL{Scheme: scheme, Host: CalculateHost(t, gwAddr, scheme), Path: path, RawQuery: query}
 
@@ -125,7 +129,7 @@ func MakeRequest(t *testing.T, expected *ExpectedResponse, gwAddr, protocol, sch
 		Method:           expected.Request.Method,
 		Host:             expected.Request.Host,
 		URL:              reqURL,
-		Protocol:         protocol,
+		Protocol:         expected.Request.Protocol,
 		Headers:          map[string][]string{},
 		UnfollowRedirect: expected.Request.UnfollowRedirect,
 	}
diff --git a/conformance/utils/roundtripper/roundtripper.go b/conformance/utils/roundtripper/roundtripper.go
index efeeab3e..b2d21487 100644
--- a/conformance/utils/roundtripper/roundtripper.go
+++ b/conformance/utils/roundtripper/roundtripper.go
@@ -21,6 +21,7 @@ import (
 	"crypto/tls"
 	"crypto/x509"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"io"
 	"net"
@@ -29,9 +30,15 @@ import (
 	"net/url"
 	"regexp"
 
+	"golang.org/x/net/http2"
+
 	"sigs.k8s.io/gateway-api/conformance/utils/config"
 )
 
+const (
+	H2CPriorKnowledgeProtocol = "H2C_PRIOR_KNOWLEDGE"
+)
+
 // RoundTripper is an interface used to make requests within conformance tests.
 // This can be overridden with custom implementations whenever necessary.
 type RoundTripper interface {
@@ -104,19 +111,7 @@ type DefaultRoundTripper struct {
 	CustomDialContext func(context.Context, string, string) (net.Conn, error)
 }
 
-// CaptureRoundTrip makes a request with the provided parameters and returns the
-// captured request and response from echoserver. An error will be returned if
-// there is an error running the function but not if an HTTP error status code
-// is received.
-func (d *DefaultRoundTripper) CaptureRoundTrip(request Request) (*CapturedRequest, *CapturedResponse, error) {
-	client := &http.Client{}
-
-	if request.UnfollowRedirect {
-		client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
-			return http.ErrUseLastResponse
-		}
-	}
-
+func (d *DefaultRoundTripper) httpTransport(request Request) (http.RoundTripper, error) {
 	transport := &http.Transport{
 		DialContext: d.CustomDialContext,
 		// We disable keep-alives so that we don't leak established TCP connections.
@@ -131,10 +126,61 @@ func (d *DefaultRoundTripper) CaptureRoundTrip(request Request) (*CapturedReques
 	if request.Server != "" && len(request.CertPem) != 0 && len(request.KeyPem) != 0 {
 		tlsConfig, err := tlsClientConfig(request.Server, request.CertPem, request.KeyPem)
 		if err != nil {
-			return nil, nil, err
+			return nil, err
 		}
 		transport.TLSClientConfig = tlsConfig
 	}
+
+	return transport, nil
+}
+
+func (d *DefaultRoundTripper) h2cPriorKnowledgeTransport(request Request) (http.RoundTripper, error) {
+	if request.Server != "" && len(request.CertPem) != 0 && len(request.KeyPem) != 0 {
+		return nil, errors.New("request has configured cert and key but h2 prior knowledge is not encrypted")
+	}
+
+	transport := &http2.Transport{
+		AllowHTTP: true,
+		DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
+			var d net.Dialer
+			return d.DialContext(ctx, network, addr)
+		},
+	}
+
+	return transport, nil
+}
+
+// CaptureRoundTrip makes a request with the provided parameters and returns the
+// captured request and response from echoserver. An error will be returned if
+// there is an error running the function but not if an HTTP error status code
+// is received.
+func (d *DefaultRoundTripper) CaptureRoundTrip(request Request) (*CapturedRequest, *CapturedResponse, error) {
+	var transport http.RoundTripper
+	var err error
+
+	switch request.Protocol {
+	case H2CPriorKnowledgeProtocol:
+		transport, err = d.h2cPriorKnowledgeTransport(request)
+	default:
+		transport, err = d.httpTransport(request)
+	}
+
+	if err != nil {
+		return nil, nil, err
+	}
+
+	return d.defaultRoundTrip(request, transport)
+}
+
+func (d *DefaultRoundTripper) defaultRoundTrip(request Request, transport http.RoundTripper) (*CapturedRequest, *CapturedResponse, error) {
+	client := &http.Client{}
+
+	if request.UnfollowRedirect {
+		client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
+			return http.ErrUseLastResponse
+		}
+	}
+
 	client.Transport = transport
 
 	method := "GET"
diff --git a/conformance/utils/suite/features.go b/conformance/utils/suite/features.go
index cf687c33..063920f6 100644
--- a/conformance/utils/suite/features.go
+++ b/conformance/utils/suite/features.go
@@ -133,6 +133,9 @@ const (
 
 	// This option indicates support for HTTPRoute backendRequest timeouts (extended conformance).
 	SupportHTTPRouteBackendTimeout SupportedFeature = "HTTPRouteBackendTimeout"
+
+	// This option indicates support for HTTPRoute with a backendref with an appProtocol 'kubernetes.io/h2c'
+	SupportHTTPRouteBackendProtocolH2C SupportedFeature = "HTTPRouteBackendProtocolH2C"
 )
 
 // HTTPRouteExtendedFeatures includes all the supported features for HTTPRoute
@@ -167,6 +170,7 @@ const (
 // Implementations have the flexibility to opt-in for either specific features or the entire set.
 var HTTPRouteExperimentalFeatures = sets.New(
 	SupportHTTPRouteDestinationPortMatching,
+	SupportHTTPRouteBackendProtocolH2C,
 )
 
 // -----------------------------------------------------------------------------
diff --git a/geps/gep-1323.md b/geps/gep-1323.md
index 3baa6cee..04c3726c 100644
--- a/geps/gep-1323.md
+++ b/geps/gep-1323.md
@@ -60,7 +60,7 @@ type HTTPHeaderModifier struct {
 
 Given the fact that this functionality is offered by only a few projects that currently implement Gateway API when using their own traffic routing CRDs, it’s better to support `ResponseHeaderModifier` as an _Extended_ feature, unlike `RequestHeaderModifier` which is a _Core_ feature. This will also not increase the difficulty of implementing Gateway API for any future ingress or service mesh.
 
-This feature can be further extended via [Policy Attachment](../references/policy-attachment.md). The mechanism and use cases of this may be explored in a future GEP.
+This feature can be further extended via [Policy Attachment](../reference/policy-attachment.md). The mechanism and use cases of this may be explored in a future GEP.
 
 ## Usage
 Adding support for this unlocks a lot of real world use cases. Let’s review a couple of them:
diff --git a/geps/gep-1364.md b/geps/gep-1364.md
index d3e02704..c9dd83c9 100644
--- a/geps/gep-1364.md
+++ b/geps/gep-1364.md
@@ -451,4 +451,4 @@ needs updating.)
 [1362]: https://github.com/kubernetes-sigs/gateway-api/issues/1362
 
 [typstatus]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties
-[httpreqredirect]: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io%2fv1beta1.HTTPRequestRedirectFilter
\ No newline at end of file
+[httpreqredirect]: https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io%2fv1beta1.HTTPRequestRedirectFilter
\ No newline at end of file
diff --git a/geps/gep-1426.md b/geps/gep-1426.md
index 5d221cfa..eeae9ca4 100644
--- a/geps/gep-1426.md
+++ b/geps/gep-1426.md
@@ -33,7 +33,7 @@ This GEP uses the [roles and personas](https://gateway-api.sigs.k8s.io/concepts/
     * Redirecting calls from arbitrary custom domains to an in-cluster service.
 * Defining how multiple `Services` or `EndpointSlices` representing instances of a single "logical" service should present an identity for AuthN/AuthZ or be associated with each other beyond routing rules.
 * Defining how AuthZ should be implemented to secure East/West traffic between services.
-* Defining how [Policy Attachment](https://gateway-api.sigs.k8s.io/references/policy-attachment/) would bind to `xRoute`, services or a mesh.
+* Defining how [Policy Attachment](https://gateway-api.sigs.k8s.io/reference/policy-attachment/) would bind to `xRoute`, services or a mesh.
 * Defining how `Routes` configured for East/West service mesh traffic management might integrate with North/South `Gateways`.
     * This is a bit tricky in that it's effectively a form of delegation as described in [GEP-1058: Route Inclusion and Delegation](https://github.com/kubernetes-sigs/gateway-api/pull/1085), and is planned to be explored in a future GEP.
 * Handling East/West traffic outside the cluster (VMs, etc).
@@ -182,7 +182,7 @@ If `port` is not set, the implementation MUST associate the route with all ports
 
 GAMMA implementations SHOULD NOT infer any functionality from the `hostnames` field on `xRoute`s (currently, `TLSRoute`, `HTTPRoute`, and `GRPCRoute` have this field) due to current under-specification and reserved potential for future usage or API changes.
 
-For the use case of filtering incoming traffic from selected HTTP hostnames, it is recommended to guide users toward configuring [`HTTPHeaderMatch`](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io%2fv1beta1.HTTPHeaderMatch) rules for the [`Host`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Host) header. Functionality to be explored in future GEPs may include supporting concurrent usage of an `xRoute` traffic configuration for multiple North/South `Gateways` and East/West mesh use cases or redirection of egress traffic to an in-cluster `Service`.
+For the use case of filtering incoming traffic from selected HTTP hostnames, it is recommended to guide users toward configuring [`HTTPHeaderMatch`](https://gateway-api.sigs.k8s.io/v1alpha2/reference/spec/#gateway.networking.k8s.io%2fv1beta1.HTTPHeaderMatch) rules for the [`Host`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Host) header. Functionality to be explored in future GEPs may include supporting concurrent usage of an `xRoute` traffic configuration for multiple North/South `Gateways` and East/West mesh use cases or redirection of egress traffic to an in-cluster `Service`.
 
 ### Namespace boundaries
 
@@ -240,7 +240,7 @@ A controller could create a matching selector-less `Service` (i.e. no endpoints)
 
 Ownership/trust would remain based on naming pattern: `serviceName.namespace.svc.[USER_DOMAIN]`
 
-Separate `HttpService`, `TlsService` and `TcpService` resources could have the benefit of allowing us to define protocol specific elements to the spec along with an embedded `CommonServiceSpec`, similar to [`CommonRouteSpec`](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1beta1.CommonRouteSpec), and keep similar patterns as `Service`.
+Separate `HttpService`, `TlsService` and `TcpService` resources could have the benefit of allowing us to define protocol specific elements to the spec along with an embedded `CommonServiceSpec`, similar to [`CommonRouteSpec`](https://gateway-api.sigs.k8s.io/v1alpha2/reference/spec/#gateway.networking.k8s.io/v1beta1.CommonRouteSpec), and keep similar patterns as `Service`.
 
 ##### Drawbacks
 
@@ -298,7 +298,7 @@ spec:
     name: cool-mesh
 ```
 
-It is currently undefined how this approach may interact with either explicitly configured [`hostnames`](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteSpec) or implicit "transparent proxy" routing for Kubernetes `Services` to determine how traffic should be intercepted and redirected.
+It is currently undefined how this approach may interact with either explicitly configured [`hostnames`](https://gateway-api.sigs.k8s.io/v1alpha2/reference/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteSpec) or implicit "transparent proxy" routing for Kubernetes `Services` to determine how traffic should be intercepted and redirected.
 
 This approach is not entirely abandoned, as it could supplement the proposed approach if explicit attachment to a specific mesh is deemed necessary. Additionally, this approach may offer a future option for attaching an `HTTPRoute` to a mesh, but not a specific service (e.g. to implement mesh-wide egress functionality for all requests to a specific hostname).
 
@@ -319,7 +319,7 @@ spec:
 
 * Would require separate `HTTPRoute` resources to explicitly define _different_ traffic routing rules for the same service on different meshes.
 
-#### Nested `services` and `hostnames` fields in [`ParentReference`](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1beta1.ParentReference)
+#### Nested `services` and `hostnames` fields in [`ParentReference`](https://gateway-api.sigs.k8s.io/v1alpha2/reference/spec/#gateway.networking.k8s.io/v1beta1.ParentReference)
 
 In core conformance, the `services` would only be valid for `Mesh` types, and `hostnames` field only for `Gateway`. Mesh implementations could still use a `Host` header match if they wanted limit rules to specific hostnames.
 
@@ -359,7 +359,7 @@ This is done by configuring the `parentRef`, to point to the `istio` `Mesh`. Thi
 
 ### New field on `HTTPRoute` for `Service` binding
 
-A new field `serviceBinding` would be added to `HTTPRoute` to attach to the `Service`. Alternatively, this could be a new field in [`HTTPRouteMatch`](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteMatch). As with the proposed implementation, this approach could be combined with a `Mesh` resource or similar as the `parentRef`, which would just define that the route would be applied to a mesh.
+A new field `serviceBinding` would be added to `HTTPRoute` to attach to the `Service`. Alternatively, this could be a new field in [`HTTPRouteMatch`](https://gateway-api.sigs.k8s.io/v1alpha2/reference/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteMatch). As with the proposed implementation, this approach could be combined with a `Mesh` resource or similar as the `parentRef`, which would just define that the route would be applied to a mesh.
 
 ```
 spec:
diff --git a/geps/gep-1619.md b/geps/gep-1619.md
index 2b770eca..d3b2137c 100644
--- a/geps/gep-1619.md
+++ b/geps/gep-1619.md
@@ -460,9 +460,9 @@ route in any given implementation.
 ### Metaresource Policy Design
 
 In order to apply session persistence configuration to both a service and a route, we will implement it as a
-metaresource policy, as outlined in [Policy Attachment](https://gateway-api.sigs.k8s.io/references/policy-attachment/#direct-policy-attachment).
+metaresource policy, as outlined in [Policy Attachment](https://gateway-api.sigs.k8s.io/reference/policy-attachment/#direct-policy-attachment).
 The metaresource is named `SessionPersistencePolicy` and is only responsible for configuring session persistence for
-services or routes. It is defined as a [Direct Policy Attachment](https://gateway-api.sigs.k8s.io/v1alpha2/references/policy-attachment/#direct-policy-attachment)
+services or routes. It is defined as a [Direct Policy Attachment](https://gateway-api.sigs.k8s.io/v1alpha2/reference/policy-attachment/#direct-policy-attachment)
 without defaults or overrides, applied to the targeted service, HTTPRoute, or GRPCRoute.
 
 Attaching the `SessionPersistencePolicy` metaresource to a service will be a core support level feature, while attaching
@@ -473,7 +473,7 @@ for more details).
 
 ### API Attachment Points
 
-The `SessionPersistencePolicy` metaresource can target a service, route, and a route rule section (e.g. [HTTPRouteRule](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io%2fv1beta1.HTTPRouteRule)).
+The `SessionPersistencePolicy` metaresource can target a service, route, and a route rule section (e.g. [HTTPRouteRule](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io%2fv1beta1.HTTPRouteRule)).
 The core functionality of attaching it to any of these is generally identical. The distinction lies in where the
 configuration gets propagated: when attached to a route or a route rule, it can define session persistence for multiple
 services, whereas attaching it to a service defines it for a single service. Enabling session persistence doesn't
@@ -916,6 +916,6 @@ Though session persistence is a ubiquitous name, session affinity is more incons
 - [Kube-Proxy Session Affinity](https://kubernetes.io/docs/reference/networking/virtual-ips/#session-affinity)
 - [GEP-713: Metaresources and PolicyAttachment](/geps/gep-713/)
 - [RFC6265](https://www.rfc-editor.org/rfc/rfc6265)
-- [Policy Attachment](https://gateway-api.sigs.k8s.io/references/policy-attachment/#direct-policy-attachment)
+- [Policy Attachment](https://gateway-api.sigs.k8s.io/reference/policy-attachment/#direct-policy-attachment)
 - [Envoy Session Persistence Design Doc](https://docs.google.com/document/d/1IU4b76AgOXijNa4sew1gfBfSiOMbZNiEt5Dhis8QpYg/edit#heading=h.sobqsca7i45e)
 - [Envoy Session Persistence Issue](https://github.com/envoyproxy/envoy/issues/16698)
\ No newline at end of file
diff --git a/geps/gep-1867.md b/geps/gep-1867.md
index c8a1a5de..67e327ac 100644
--- a/geps/gep-1867.md
+++ b/geps/gep-1867.md
@@ -57,7 +57,7 @@ While there is value out of providing class-wide options as defaults, there is a
 
 Some parallels in existing APIs:
 
-[Policy Attachment](https://gateway-api.sigs.k8s.io/references/policy-attachment) offers a hierarchy of defaults and overrides, allowing attachment to GatewayClass and Gateway.
+[Policy Attachment](https://gateway-api.sigs.k8s.io/reference/policy-attachment) offers a hierarchy of defaults and overrides, allowing attachment to GatewayClass and Gateway.
 This is similar to our needs here, but representing infrastructure configuration as a "Policy" is a bit problematic, and the existing mechanisms have no hierarchy.
 
 In core Kubernetes, Pods declare their requirements (for example, CPU requests) inline in the Pod resource; there is not a `ResourceClass` API that abstracts these further.
diff --git a/geps/gep-1897.md b/geps/gep-1897.md
index b9fb00a9..8a6b0aeb 100644
--- a/geps/gep-1897.md
+++ b/geps/gep-1897.md
@@ -35,8 +35,8 @@ and CA certificates.
 These are worthy goals, but deserve a different GEP for proper attention.  This GEP is concerned entirely with the
 controlplane, i.e. the hop between gateway and backend.
 
-1. [TCPRoute](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.TCPRoute) and
-[GRPCRoute](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.GRPCRoute) use cases
+1. [TCPRoute](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1alpha2.TCPRoute) and
+[GRPCRoute](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1alpha2.GRPCRoute) use cases
 are not addressed here, because at this point in time these two route types are not graduated to beta.
 2. Mutual TLS (mTLS) use cases are intentionally out of scope for this GEP for two reasons.  First, the design of Gateway
 API is backend-attached and does not currently support mutual authentication, and also because this GEP does not
@@ -598,7 +598,7 @@ the implementation would be required to fully implement the policy or mark the b
 
 [GEP-713: Metaresources and PolicyAttachment](https://gateway-api.sigs.k8s.io/geps/gep-713/)
 
-[Policy Attachment](https://gateway-api.sigs.k8s.io/references/policy-attachment/#direct-policy-attachment)
+[Policy Attachment](https://gateway-api.sigs.k8s.io/reference/policy-attachment/#direct-policy-attachment)
 
 [Gateway API TLS](https://gateway-api.sigs.k8s.io/v1alpha2/guides/tls/)
 
diff --git a/gwctl/pkg/common/clients.go b/gwctl/pkg/common/clients.go
index 3530b50f..186e57e9 100644
--- a/gwctl/pkg/common/clients.go
+++ b/gwctl/pkg/common/clients.go
@@ -30,6 +30,7 @@ import (
 	"k8s.io/client-go/tools/clientcmd"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
+	gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
 	gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
 	gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
 )
@@ -52,6 +53,7 @@ func NewK8sClients(kubeconfig string) (*K8sClients, error) {
 	}
 	gatewayv1alpha2.AddToScheme(client.Scheme())
 	gatewayv1beta1.AddToScheme(client.Scheme())
+	gatewayv1.AddToScheme(client.Scheme())
 
 	dc := dynamic.NewForConfigOrDie(restConfig)
 
@@ -66,6 +68,7 @@ func MustClientsForTest(t *testing.T, initRuntimeObjects ...runtime.Object) *K8s
 	scheme := scheme.Scheme
 	gatewayv1alpha2.AddToScheme(scheme)
 	gatewayv1beta1.AddToScheme(scheme)
+	gatewayv1.AddToScheme(scheme)
 	apiextensionsv1.AddToScheme(scheme)
 
 	fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(initRuntimeObjects...).Build()
diff --git a/hack/make-docs.sh b/hack/make-docs.sh
index 02f54811..c803cbb7 100755
--- a/hack/make-docs.sh
+++ b/hack/make-docs.sh
@@ -37,5 +37,5 @@ mkdocs build
 # Generate v1alpha2 API docs
 ./hack/api-docs/generate.sh site/spec.html
 # Add them to spec page originally generated by mkdocs
-run::sed -e '/REPLACE_WITH_GENERATED_CONTENT/{r site/spec.html' -e 'd;}' site/references/spec/index.html
-run::sed -e '/REPLACE_WITH_GENERATED_CONTENT/{r site/spec.html' -e 'd;}' site/v1alpha2/references/spec/index.html
+run::sed -e '/REPLACE_WITH_GENERATED_CONTENT/{r site/spec.html' -e 'd;}' site/reference/spec/index.html
+run::sed -e '/REPLACE_WITH_GENERATED_CONTENT/{r site/spec.html' -e 'd;}' site/v1alpha2/reference/spec/index.html
diff --git a/hack/update-conformance-image-refs.sh b/hack/update-conformance-image-refs.sh
index c03953f6..0bf9b3de 100755
--- a/hack/update-conformance-image-refs.sh
+++ b/hack/update-conformance-image-refs.sh
@@ -29,8 +29,8 @@ esac
 }
 
 IMAGES=(
- gcr.io/k8s-staging-gateway-api/echo-advanced
- gcr.io/k8s-staging-gateway-api/echo-basic
+  "${REGISTRY}/echo-advanced"
+  "${REGISTRY}/echo-basic"
 )
 
 # Export so we can use run::sed in subshells
@@ -46,10 +46,11 @@ for IMAGE in ${IMAGES[@]}; do
     | sort -rV \
     | head -n1 > "$TAG_FILE"
 
+  export REPO=${IMAGE#"${REGISTRY}"}
   export IMAGE_TAG=$(cat "$TAG_FILE")
   export IMAGE
   echo "Found tag $IMAGE_TAG - updating manifests..."
-  find . -type f -name "*.yaml" -exec bash -c 'run::sed -e "s,image:.*${IMAGE}.*$,image: ${IMAGE}:${IMAGE_TAG},g" "$0"' {} \;
+  find . -type f -name "*.yaml" -exec bash -c 'run::sed -e "s,image:.*${REPO}.*$,image: ${IMAGE}:${IMAGE_TAG},g" "$0"' {} \;
 done
 
 
diff --git a/mkdocs.yml b/mkdocs.yml
index a53da326..405a1cd3 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -29,6 +29,9 @@ plugins:
         'guides/getting-started.md': 'guides/index.md'
         'contributing/community.md': 'contributing/index.md'
         'contributing/gamma.md': 'concepts/gamma.md#contributing'
+        'references/implementers-guide.md': 'reference/implementers-guide.md'
+        'references/spec.md': 'reference/spec.md'
+        'references/policy-attachment.md': 'reference/policy-attachment.md'
   - mermaid2
 markdown_extensions:
   - admonition
@@ -77,15 +80,15 @@ nav:
     - gRPC Routing: guides/grpc-routing.md
     - Migrating from Ingress: guides/migrating-from-ingress.md
   - Reference:
-    - Implementer's Guide: references/implementers-guide.md
+    - Implementer's Guide: reference/implementers-guide.md
     - API Types:
         GatewayClass: api-types/gatewayclass.md
         Gateway: api-types/gateway.md
         HTTPRoute: api-types/httproute.md
         ReferenceGrant: api-types/referencegrant.md
         GRPCRoute: api-types/grpcroute.md
-    - API specification: references/spec.md
-    - Policy Attachment: references/policy-attachment.md
+    - API specification: reference/spec.md
+    - Policy Attachment: reference/policy-attachment.md
     - Enhancement Proposals:
       - Overview: geps/overview.md
       - Declined:
diff --git a/requirements.txt b/requirements.txt
index 7b1e369e..90180ca2 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -11,12 +11,12 @@ mkdocs==1.5.3
 mkdocs-awesome-pages-plugin==2.9.2
 mkdocs-macros-plugin==1.0.4
 mkdocs-material==9.4.6
-mkdocs-material-extensions==1.2
+mkdocs-material-extensions==1.3
 mkdocs-redirects==1.2.1
 mkdocs-mermaid2-plugin==1.1.1
 pep562==1.1
 Pygments==2.16.1
-pymdown-extensions==10.3
+pymdown-extensions==10.3.1
 PyYAML==6.0.1
 six==1.16.0
 tornado==6.3.3
diff --git a/site-src/api-types/gatewayclass.md b/site-src/api-types/gatewayclass.md
index b4576923..4fa585bf 100644
--- a/site-src/api-types/gatewayclass.md
+++ b/site-src/api-types/gatewayclass.md
@@ -139,5 +139,5 @@ example.net/gateway/v2.1 // Use version 2.1
 example.net/gateway      // Use the default version
 ```
 
-[gatewayclass]: /references/spec/#gateway.networking.k8s.io/v1beta1.GatewayClass
+[gatewayclass]: /reference/spec/#gateway.networking.k8s.io/v1beta1.GatewayClass
 [ingress-class-api]: https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class
diff --git a/site-src/api-types/grpcroute.md b/site-src/api-types/grpcroute.md
index a795e840..cefd11ae 100644
--- a/site-src/api-types/grpcroute.md
+++ b/site-src/api-types/grpcroute.md
@@ -263,12 +263,12 @@ only one Route rule may match each request. For more information on how conflict
 resolution applies to merging, refer to the [API specification][grpcrouterule].
 
 
-[grpcroute]: /references/spec/#gateway.networking.k8s.io/v1alpha2.GRPCPRoute
-[grpcrouterule]: /references/spec/#gateway.networking.k8s.io/v1alpha2.GRPCRouteRule
-[hostname]: /references/spec/#gateway.networking.k8s.io/v1beta1.Hostname
+[grpcroute]: /reference/spec/#gateway.networking.k8s.io/v1alpha2.GRPCPRoute
+[grpcrouterule]: /reference/spec/#gateway.networking.k8s.io/v1alpha2.GRPCRouteRule
+[hostname]: /reference/spec/#gateway.networking.k8s.io/v1beta1.Hostname
 [rfc-3986]: https://tools.ietf.org/html/rfc3986
-[matches]: /references/spec/#gateway.networking.k8s.io/v1alpha2.GRPCRouteMatch
-[filters]: /references/spec/#gateway.networking.k8s.io/v1alpha2.GRPCRouteFilter
-[backendRef]: /references/spec/#gateway.networking.k8s.io/v1alpha2.GRPCBackendRef
-[parentRef]: /references/spec/#gateway.networking.k8s.io/v1beta1.ParentRef
+[matches]: /reference/spec/#gateway.networking.k8s.io/v1alpha2.GRPCRouteMatch
+[filters]: /reference/spec/#gateway.networking.k8s.io/v1alpha2.GRPCRouteFilter
+[backendRef]: /reference/spec/#gateway.networking.k8s.io/v1alpha2.GRPCBackendRef
+[parentRef]: /reference/spec/#gateway.networking.k8s.io/v1beta1.ParentRef
 
diff --git a/site-src/api-types/httproute.md b/site-src/api-types/httproute.md
index 88a0b86c..3f670ed8 100644
--- a/site-src/api-types/httproute.md
+++ b/site-src/api-types/httproute.md
@@ -206,12 +206,12 @@ only one Route rule may match each request. For more information on how conflict
 resolution applies to merging, refer to the [API specification][httprouterule].
 
 
-[httproute]: /references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRoute
-[httprouterule]: /references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteRule
-[hostname]: /references/spec/#gateway.networking.k8s.io/v1beta1.Hostname
+[httproute]: /reference/spec/#gateway.networking.k8s.io/v1beta1.HTTPRoute
+[httprouterule]: /reference/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteRule
+[hostname]: /reference/spec/#gateway.networking.k8s.io/v1beta1.Hostname
 [rfc-3986]: https://tools.ietf.org/html/rfc3986
-[matches]: /references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteMatch
-[filters]: /references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteFilter
-[backendRef]: /references/spec/#gateway.networking.k8s.io/v1beta1.HTTPBackendRef
-[parentRef]: /references/spec/#gateway.networking.k8s.io/v1beta1.ParentRef
+[matches]: /reference/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteMatch
+[filters]: /reference/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteFilter
+[backendRef]: /reference/spec/#gateway.networking.k8s.io/v1beta1.HTTPBackendRef
+[parentRef]: /reference/spec/#gateway.networking.k8s.io/v1beta1.ParentRef
 
diff --git a/site-src/api-types/referencegrant.md b/site-src/api-types/referencegrant.md
index e9718b3a..118fd390 100644
--- a/site-src/api-types/referencegrant.md
+++ b/site-src/api-types/referencegrant.md
@@ -86,7 +86,7 @@ While the API is simplistic in nature, it comes with a few notable decisions:
    other. This makes it impossible for them to conflict with each other.
 
 Please see the [API
-Specification](/references/spec#gateway.networking.k8s.io/v1alpha2.ReferenceGrant)
+Specification](/reference/spec#gateway.networking.k8s.io/v1alpha2.ReferenceGrant)
 for more details on how specific ReferenceGrant fields are interpreted.
 
 ## Implementation Guidelines
diff --git a/site-src/faq.md b/site-src/faq.md
index 27121d59..7abfa5f9 100644
--- a/site-src/faq.md
+++ b/site-src/faq.md
@@ -27,7 +27,7 @@
     A: There are a few mechanisms available
     for extending the API with implementation-specific capabilities:
 
-    * The [Policy Attachment](https://gateway-api.sigs.k8s.io/references/policy-attachment/)
+    * The [Policy Attachment](https://gateway-api.sigs.k8s.io/reference/policy-attachment/)
       model allows you to decorate Gateway API objects with implementation-specific CRDs. A
       policy or configuration object could match the Gateway API object either
       by name or by using an explicit object reference.
diff --git a/site-src/guides/grpc-routing.md b/site-src/guides/grpc-routing.md
index bc501d02..d16004fc 100644
--- a/site-src/guides/grpc-routing.md
+++ b/site-src/guides/grpc-routing.md
@@ -80,6 +80,6 @@ been considered.
 {% include 'experimental/v1alpha2/grpc-routing/reflection-grpcroute.yaml' %}
 ```
 
-[gateway]: /references/spec/#gateway.networking.k8s.io/v1beta1.Gateway
-[spec]: /references/spec/#gateway.networking.k8s.io%2fv1alpha2.GRPCRouteSpec
+[gateway]: /reference/spec/#gateway.networking.k8s.io/v1beta1.Gateway
+[spec]: /reference/spec/#gateway.networking.k8s.io%2fv1alpha2.GRPCRouteSpec
 [svc]:https://kubernetes.io/docs/concepts/services-networking/service/
diff --git a/site-src/guides/http-redirect-rewrite.md b/site-src/guides/http-redirect-rewrite.md
index 0f0e41eb..c5faee44 100644
--- a/site-src/guides/http-redirect-rewrite.md
+++ b/site-src/guides/http-redirect-rewrite.md
@@ -12,7 +12,7 @@ use both filter types at once.
 
 Redirects return HTTP 3XX responses to a client, instructing it to retrieve a
 different resource. [`RequestRedirect` rule
-filters](/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRequestRedirectFilter)
+filters](/reference/spec/#gateway.networking.k8s.io/v1beta1.HTTPRequestRedirectFilter)
 instruct Gateways to emit a redirect response to requests matching a filtered
 HTTPRoute rule.
 
@@ -61,7 +61,7 @@ https://redirect.example/paprika/teaspoon` response headers.
 
 Rewrites modify components of a client request before proxying it upstream. A
 [`URLRewrite`
-filter](/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPURLRewriteFilter)
+filter](/reference/spec/#gateway.networking.k8s.io/v1beta1.HTTPURLRewriteFilter)
 can change the upstream request hostname and/or path. For example, the
 following HTTPRoute will accept a request for
 `https://rewrite.example/cardamom` and send it upstream to `example-svc` with
diff --git a/site-src/guides/http-routing.md b/site-src/guides/http-routing.md
index e2f7083b..0a2533f0 100644
--- a/site-src/guides/http-routing.md
+++ b/site-src/guides/http-routing.md
@@ -57,6 +57,6 @@ missing or not `canary` then it'll be forwarded to `bar-svc`.
 {% include 'standard/http-routing/bar-httproute.yaml' %}
 ```
 
-[gateway]: /references/spec/#gateway.networking.k8s.io/v1beta1.Gateway
-[spec]: /references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteSpec
+[gateway]: /reference/spec/#gateway.networking.k8s.io/v1beta1.Gateway
+[spec]: /reference/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteSpec
 [svc]:https://kubernetes.io/docs/concepts/services-networking/service/
diff --git a/site-src/guides/migrating-from-ingress.md b/site-src/guides/migrating-from-ingress.md
index 01951b6b..d78c2798 100644
--- a/site-src/guides/migrating-from-ingress.md
+++ b/site-src/guides/migrating-from-ingress.md
@@ -147,12 +147,12 @@ The Gateway API has the following primary extension points:
 * *External references.* A feature (field) of a Gateway API resource can
   reference a custom resource specific to the Gateway implementation that
   configures that feature. For example:
-    * [HTTPRouteFilter](/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteFilter)
+    * [HTTPRouteFilter](/reference/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteFilter)
       can reference an external resource via the `extensionRef` field, thus
       configuring an implementation-specific filter.
-    * [BackendObjectReference](/references/spec/#gateway.networking.k8s.io/v1beta1.BackendObjectReference)
+    * [BackendObjectReference](/reference/spec/#gateway.networking.k8s.io/v1beta1.BackendObjectReference)
       supports resources other than Services.
-    * [SecretObjectReference](/references/spec/#gateway.networking.k8s.io/v1beta1.SecretObjectReference)
+    * [SecretObjectReference](/reference/spec/#gateway.networking.k8s.io/v1beta1.SecretObjectReference)
       supports resources other than Secrets.
 * *Custom implementations*. For some features, it is left up to an
   implementation to define how to support them. Those features correspond to the
@@ -160,11 +160,11 @@ The Gateway API has the following primary extension points:
   (custom)  [conformance level](/concepts/conformance/#2-support-levels). For
   example:
     * The `RegularExpression` type of
-      the [HTTPPathMatch](/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPPathMatch).
+      the [HTTPPathMatch](/reference/spec/#gateway.networking.k8s.io/v1beta1.HTTPPathMatch).
 * *Policies.* A Gateway implementation can define custom resources called
   Policies for exposing data plane features like authentication. The Gateway API
   does not prescribe the details of those resources. However, it prescribes a
-  standard UX. See the [Policy attachment guide](/references/policy-attachment/)
+  standard UX. See the [Policy attachment guide](/reference/policy-attachment/)
   for more details. In contrast with the *external references* above, a Gateway
   API resource does not reference a Policy. Instead, a Policy must reference a
   Gateway API resource.
@@ -194,7 +194,7 @@ resource will get dedicated entry points.
 In the Gateway API, entry points must be explicitly defined in
 a [Gateway](/api-types/gateway/) resource. For example, if you want the data
 plane to handle HTTP traffic on port 80, you need to define
-a [listener](/references/spec/#gateway.networking.k8s.io/v1beta1.Listener) for
+a [listener](/reference/spec/#gateway.networking.k8s.io/v1beta1.Listener) for
 that traffic. Typically, a Gateway implementation provides a dedicated data
 plane for each Gateway resource.
 
@@ -207,7 +207,7 @@ the [TLS section](https://kubernetes.io/docs/concepts/services-networking/ingres
 where the TLS certificate and key are stored in a Secret.
 
 In the Gateway API, TLS termination is a property of
-the [Gateway listener](/references/spec/#gateway.networking.k8s.io/v1beta1.Listener),
+the [Gateway listener](/reference/spec/#gateway.networking.k8s.io/v1beta1.Listener),
 and similarly to the Ingress, a TLS certificate and key are also stored in a
 Secret.
 
@@ -218,21 +218,21 @@ application admin own TLS termination.
 
 The [path-based routing rules](https://kubernetes.io/docs/concepts/services-networking/ingress/#path-types)
 of the Ingress resource map directly to
-the [routing rules](/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteRule)
+the [routing rules](/reference/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteRule)
 of the [HTTPRoute](/api-types/httproute/).
 
 The [host-header-based routing rules](https://kubernetes.io/docs/concepts/services-networking/ingress/#name-based-virtual-hosting)
 map to
-the [hostnames](/references/spec/#gateway.networking.k8s.io/v1beta1.Hostname) of
+the [hostnames](/reference/spec/#gateway.networking.k8s.io/v1beta1.Hostname) of
 the HTTPRoute. However, note that in the Ingress, each hostname has separate
 routing rules, while in the HTTPRoute the routing rules apply to all hostnames.
 
 > The Ingress API uses the term host while the Gateway API uses the hostname.
 > This guide will use the Gateway API term to refer to the Ingress host.
 
-> The `hostnames` of an HTTPRoute must match the `hostname` of the [Gateway listener](/references/spec/#gateway.networking.k8s.io/v1beta1.Listener).
+> The `hostnames` of an HTTPRoute must match the `hostname` of the [Gateway listener](/reference/spec/#gateway.networking.k8s.io/v1beta1.Listener).
 > Otherwise, the listener will ignore the routing rules for the unmatched
-> hostnames. See the [HTTPRoute documentation](/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteSpec).
+> hostnames. See the [HTTPRoute documentation](/reference/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteSpec).
 
 HTTPRoutes are owned by the application developer.
 
@@ -270,7 +270,7 @@ An Ingress resource must specify
 a [class](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class)
 to select which Ingress controller to use. An HTTPRoute must specify which
 Gateway (or Gateways) to attach to via
-a [parentRef](/references/spec/#gateway.networking.k8s.io/v1beta1.ParentRef).
+a [parentRef](/reference/spec/#gateway.networking.k8s.io/v1beta1.ParentRef).
 
 ### Implementation-Specific Ingress Features (Annotations)
 
diff --git a/site-src/guides/multiple-ns.md b/site-src/guides/multiple-ns.md
index e845beb2..9e3522fa 100644
--- a/site-src/guides/multiple-ns.md
+++ b/site-src/guides/multiple-ns.md
@@ -155,7 +155,7 @@ Gateway that these Routes want to attach to.
 
 After these three Routes are deployed, they will all be attached to the
 `shared-gateway` Gateway. The Gateway merges these Routes into a single flat
-list of routing rules. [Routing precedence](/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteRule)
+list of routing rules. [Routing precedence](/reference/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteRule)
 between these routing rules is determined by most specific match and
 conflicts are handled according to [conflict
 resolution](/concepts/guidelines#conflicts). This provides predictable and
diff --git a/site-src/guides/tcp.md b/site-src/guides/tcp.md
index 3ee6b982..94e5c2b5 100644
--- a/site-src/guides/tcp.md
+++ b/site-src/guides/tcp.md
@@ -49,8 +49,8 @@ In this way each `TCPRoute` "attaches" itself to a different port on the
 `Gateway` so that the service `my-foo-service` is taking traffic for port `8080`
 from outside the cluster and `my-bar-service` takes the port `8090` traffic.
 
-[tcproute]:/references/spec/#gateway.networking.k8s.io/v1alpha2.TCPRoute
+[tcproute]:/reference/spec/#gateway.networking.k8s.io/v1alpha2.TCPRoute
 [tcp]:https://datatracker.ietf.org/doc/html/rfc793
-[httproute]:/references/spec/#gateway.networking.k8s.io/v1alpha2.HTTPRoute
-[gateway]:/references/spec/#gateway.networking.k8s.io/v1alpha2.Gateway
+[httproute]:/reference/spec/#gateway.networking.k8s.io/v1alpha2.HTTPRoute
+[gateway]:/reference/spec/#gateway.networking.k8s.io/v1alpha2.Gateway
 [svc]:https://kubernetes.io/docs/concepts/services-networking/service/
diff --git a/site-src/guides/traffic-splitting.md b/site-src/guides/traffic-splitting.md
index fa960d9d..ec6ec14a 100644
--- a/site-src/guides/traffic-splitting.md
+++ b/site-src/guides/traffic-splitting.md
@@ -41,7 +41,7 @@ production user traffic for `foo.example.com`. The following HTTPRoute has no
 recieve 100% of the traffic matched by each of their route rules. A canary
 route rule is used (matching the header `traffic=test`) to send synthetic test
 traffic before splitting any production user traffic to `foo-v2`.
-[Routing precedence](/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteRule)
+[Routing precedence](/reference/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteRule)
 ensures that all traffic with the matching host and header 
 (the most specific match) will be sent to `foo-v2`.
 
diff --git a/site-src/index.md b/site-src/index.md
index d763face..ffbb36b0 100644
--- a/site-src/index.md
+++ b/site-src/index.md
@@ -89,7 +89,7 @@ you the necessary background:
 - [User guides](/guides)
 - [Gateway controller implementations](/implementations#gateways)
 - [Service Mesh implementations](/implementations#meshes)
-- [API reference spec](/references/spec)
+- [API reference spec](/reference/spec)
 - [Community links](/contributing/community) and [developer guide](/contributing/devguide)
 
 ## Gateway API concepts
diff --git a/site-src/references/images/policy-hierarchy.png b/site-src/reference/images/policy-hierarchy.png
similarity index 100%
rename from site-src/references/images/policy-hierarchy.png
rename to site-src/reference/images/policy-hierarchy.png
diff --git a/site-src/references/images/referencepolicy.png b/site-src/reference/images/referencepolicy.png
similarity index 100%
rename from site-src/references/images/referencepolicy.png
rename to site-src/reference/images/referencepolicy.png
diff --git a/site-src/references/implementers-guide.md b/site-src/reference/implementers-guide.md
similarity index 100%
rename from site-src/references/implementers-guide.md
rename to site-src/reference/implementers-guide.md
diff --git a/site-src/references/policy-attachment.md b/site-src/reference/policy-attachment.md
similarity index 100%
rename from site-src/references/policy-attachment.md
rename to site-src/reference/policy-attachment.md
diff --git a/site-src/references/spec.md b/site-src/reference/spec.md
similarity index 100%
rename from site-src/references/spec.md
rename to site-src/reference/spec.md
diff --git a/site-src/v1alpha2/reference b/site-src/v1alpha2/reference
new file mode 120000
index 00000000..4f0dd2b0
--- /dev/null
+++ b/site-src/v1alpha2/reference
@@ -0,0 +1 @@
+../reference
\ No newline at end of file
diff --git a/site-src/v1alpha2/references b/site-src/v1alpha2/references
deleted file mode 120000
index 3dc6b5c6..00000000
--- a/site-src/v1alpha2/references
+++ /dev/null
@@ -1 +0,0 @@
-../references
\ No newline at end of file


Test patch:
diff --git a/conformance/conformance_test.go b/conformance/conformance_test.go
index 13241c62..bbd65634 100644
--- a/conformance/conformance_test.go
+++ b/conformance/conformance_test.go
@@ -20,6 +20,7 @@ package conformance_test
 import (
 	"testing"
 
+	v1 "sigs.k8s.io/gateway-api/apis/v1"
 	"sigs.k8s.io/gateway-api/apis/v1alpha2"
 	"sigs.k8s.io/gateway-api/apis/v1beta1"
 	"sigs.k8s.io/gateway-api/conformance/tests"
@@ -48,6 +49,7 @@ func TestConformance(t *testing.T) {
 
 	v1alpha2.AddToScheme(client.Scheme())
 	v1beta1.AddToScheme(client.Scheme())
+	v1.AddToScheme(client.Scheme())
 
 	supportedFeatures := suite.ParseSupportedFeatures(*flags.SupportedFeatures)
 	exemptFeatures := suite.ParseSupportedFeatures(*flags.ExemptFeatures)
diff --git a/conformance/experimental_conformance_test.go b/conformance/experimental_conformance_test.go
index 12b6ea82..4d4de988 100644
--- a/conformance/experimental_conformance_test.go
+++ b/conformance/experimental_conformance_test.go
@@ -27,6 +27,7 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client/config"
 	"sigs.k8s.io/yaml"
 
+	v1 "sigs.k8s.io/gateway-api/apis/v1"
 	"sigs.k8s.io/gateway-api/apis/v1alpha2"
 	"sigs.k8s.io/gateway-api/apis/v1beta1"
 	confv1a1 "sigs.k8s.io/gateway-api/conformance/apis/v1alpha1"
@@ -65,6 +66,7 @@ func TestExperimentalConformance(t *testing.T) {
 
 	v1alpha2.AddToScheme(mgrClient.Scheme())
 	v1beta1.AddToScheme(mgrClient.Scheme())
+	v1.AddToScheme(mgrClient.Scheme())
 
 	// standard conformance flags
 	supportedFeatures = suite.ParseSupportedFeatures(*flags.SupportedFeatures)
diff --git a/conformance/tests/gateway-secret-invalid-reference-grant.yaml b/conformance/tests/gateway-secret-invalid-reference-grant.yaml
index 1918d322..c57c8fb7 100644
--- a/conformance/tests/gateway-secret-invalid-reference-grant.yaml
+++ b/conformance/tests/gateway-secret-invalid-reference-grant.yaml
@@ -19,7 +19,7 @@ spec:
             name: certificate
             namespace: gateway-conformance-web-backend
 ---
-apiVersion: gateway.networking.k8s.io/v1
+apiVersion: gateway.networking.k8s.io/v1beta1
 kind: ReferenceGrant
 metadata:
   name: reference-grant-wrong-namespace
@@ -34,7 +34,7 @@ spec:
       kind: Secret
       name: certificate
 ---
-apiVersion: gateway.networking.k8s.io/v1
+apiVersion: gateway.networking.k8s.io/v1beta1
 kind: ReferenceGrant
 metadata:
   name: reference-grant-wrong-from-group
@@ -49,7 +49,7 @@ spec:
       kind: Secret
       name: certificate
 ---
-apiVersion: gateway.networking.k8s.io/v1
+apiVersion: gateway.networking.k8s.io/v1beta1
 kind: ReferenceGrant
 metadata:
   name: reference-grant-wrong-from-kind
@@ -64,7 +64,7 @@ spec:
       kind: Secret
       name: certificate
 ---
-apiVersion: gateway.networking.k8s.io/v1
+apiVersion: gateway.networking.k8s.io/v1beta1
 kind: ReferenceGrant
 metadata:
   name: reference-grant-wrong-from-namespace
@@ -79,7 +79,7 @@ spec:
       kind: Secret
       name: certificate
 ---
-apiVersion: gateway.networking.k8s.io/v1
+apiVersion: gateway.networking.k8s.io/v1beta1
 kind: ReferenceGrant
 metadata:
   name: reference-grant-wrong-to-group
@@ -94,7 +94,7 @@ spec:
       kind: Secret
       name: not-the-certificate-youre-looking-for
 ---
-apiVersion: gateway.networking.k8s.io/v1
+apiVersion: gateway.networking.k8s.io/v1beta1
 kind: ReferenceGrant
 metadata:
   name: reference-grant-wrong-to-kind
@@ -109,7 +109,7 @@ spec:
       kind: Service
       name: certificate
 ---
-apiVersion: gateway.networking.k8s.io/v1
+apiVersion: gateway.networking.k8s.io/v1beta1
 kind: ReferenceGrant
 metadata:
   name: reference-grant-wrong-to-name
diff --git a/conformance/tests/httproute-backend-protocol-h2c.go b/conformance/tests/httproute-backend-protocol-h2c.go
new file mode 100644
index 00000000..2a4baedc
--- /dev/null
+++ b/conformance/tests/httproute-backend-protocol-h2c.go
@@ -0,0 +1,66 @@
+/*
+Copyright 2023 The Kubernetes Authors.
+
+Licensed 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 tests
+
+import (
+	"testing"
+
+	"k8s.io/apimachinery/pkg/types"
+
+	"sigs.k8s.io/gateway-api/conformance/utils/http"
+	"sigs.k8s.io/gateway-api/conformance/utils/kubernetes"
+	"sigs.k8s.io/gateway-api/conformance/utils/roundtripper"
+	"sigs.k8s.io/gateway-api/conformance/utils/suite"
+)
+
+func init() {
+	ConformanceTests = append(ConformanceTests, HTTPRouteBackendProtocolH2C)
+}
+
+var HTTPRouteBackendProtocolH2C = suite.ConformanceTest{
+	ShortName:   "HTTPRouteBackendProtocolH2C",
+	Description: "A HTTPRoute with a BackendRef that has an appProtocol kubernetes.io/h2c should be functional",
+	Features: []suite.SupportedFeature{
+		suite.SupportGateway,
+		suite.SupportHTTPRoute,
+		suite.SupportHTTPRouteBackendProtocolH2C,
+	},
+	Manifests: []string{
+		"tests/httproute-backend-protocol-h2c.yaml",
+	},
+	Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
+		ns := "gateway-conformance-infra"
+		routeNN := types.NamespacedName{Name: "backend-protocol-h2c", Namespace: ns}
+		gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns}
+		gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN)
+
+		// We are not testing the h2c HTTP upgrade mechanism as it is deprecated
+		// See: https://datatracker.ietf.org/doc/html/rfc9113#versioning
+
+		t.Run("http2 prior knowledge request should reach backend", func(t *testing.T) {
+			http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, http.ExpectedResponse{
+				Request: http.Request{
+					Path:     "/",
+					Protocol: roundtripper.H2CPriorKnowledgeProtocol,
+				},
+				Response:  http.Response{StatusCode: 200},
+				Backend:   "infra-backend-v1",
+				Namespace: "gateway-conformance-infra",
+			})
+		})
+	},
+}
diff --git a/conformance/tests/httproute-backend-protocol-h2c.yaml b/conformance/tests/httproute-backend-protocol-h2c.yaml
new file mode 100644
index 00000000..7b20c347
--- /dev/null
+++ b/conformance/tests/httproute-backend-protocol-h2c.yaml
@@ -0,0 +1,17 @@
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: backend-protocol-h2c
+  namespace: gateway-conformance-infra
+spec:
+  parentRefs:
+  - name: same-namespace
+  rules:
+  - backendRefs:
+  # This points to a Service with the following ServicePort
+  # - protocol: TCP
+  #   appProtocol: kubernetes.io/h2c
+  #   port: 8081
+  #   targetPort: 3001
+    - name: infra-backend-v1
+      port: 8081
diff --git a/pkg/test/cel/backendtlspolicy_test.go b/pkg/test/cel/backendtlspolicy_test.go
new file mode 100644
index 00000000..f5644729
--- /dev/null
+++ b/pkg/test/cel/backendtlspolicy_test.go
@@ -0,0 +1,149 @@
+//go:build experimental
+// +build experimental
+
+/*
+Copyright 2023 The Kubernetes Authors.
+
+Licensed 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 main
+
+import (
+	"context"
+	"fmt"
+	"strings"
+	"testing"
+	"time"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	gatewayv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
+	"sigs.k8s.io/gateway-api/apis/v1beta1"
+)
+
+func TestBackendTLSPolicyConfig(t *testing.T) {
+	tests := []struct {
+		name        string
+		wantErrors  []string
+		routeConfig gatewayv1a2.BackendTLSPolicyConfig
+	}{
+		{
+			name: "valid BackendTLSPolicyConfig with WellKnownCACerts",
+			routeConfig: gatewayv1a2.BackendTLSPolicyConfig{
+				WellKnownCACerts: ptrTo(gatewayv1a2.WellKnownCACertType("System")),
+				Hostname:         "foo.example.com",
+			},
+			wantErrors: []string{},
+		},
+		{
+			name: "valid BackendTLSPolicyConfig with CACertRefs",
+			routeConfig: gatewayv1a2.BackendTLSPolicyConfig{
+				CACertRefs: []v1beta1.LocalObjectReference{
+					{
+						Group: "group",
+						Kind:  "kind",
+						Name:  "name",
+					},
+				},
+				Hostname: "foo.example.com",
+			},
+			wantErrors: []string{},
+		},
+		{
+			name:        "invalid BackendTLSPolicyConfig with missing fields",
+			routeConfig: gatewayv1a2.BackendTLSPolicyConfig{},
+			wantErrors:  []string{"spec.tls.hostname in body should be at least 1 chars long", "must specify either CACertRefs or WellKnownCACerts"},
+		},
+		{
+			name: "invalid BackendTLSPolicyConfig with both CACertRefs and WellKnownCACerts",
+			routeConfig: gatewayv1a2.BackendTLSPolicyConfig{
+				CACertRefs: []v1beta1.LocalObjectReference{
+					{
+						Group: "group",
+						Kind:  "kind",
+						Name:  "name",
+					},
+				},
+				WellKnownCACerts: ptrTo(gatewayv1a2.WellKnownCACertType("System")),
+				Hostname:         "foo.example.com",
+			},
+
+			wantErrors: []string{"must not contain both CACertRefs and WellKnownCACerts"},
+		},
+		{
+			name: "invalid BackendTLSPolicyConfig with Unsupported value for WellKnownCACerts",
+			routeConfig: gatewayv1a2.BackendTLSPolicyConfig{
+				WellKnownCACerts: ptrTo(gatewayv1a2.WellKnownCACertType("bar")),
+				Hostname:         "foo.example.com",
+			},
+			wantErrors: []string{"supported values: \"System\""},
+		},
+		{
+			name: "invalid BackendTLSPolicyConfig with empty Hostname field",
+			routeConfig: gatewayv1a2.BackendTLSPolicyConfig{
+				CACertRefs: []v1beta1.LocalObjectReference{
+					{
+						Group: "group",
+						Kind:  "kind",
+						Name:  "name",
+					},
+				},
+				Hostname: "",
+			},
+			wantErrors: []string{"spec.tls.hostname in body should be at least 1 chars long"},
+		},
+	}
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			route := &gatewayv1a2.BackendTLSPolicy{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      fmt.Sprintf("foo-%v", time.Now().UnixNano()),
+					Namespace: metav1.NamespaceDefault,
+				},
+				Spec: gatewayv1a2.BackendTLSPolicySpec{
+					TargetRef: gatewayv1a2.PolicyTargetReferenceWithSectionName{
+						PolicyTargetReference: gatewayv1a2.PolicyTargetReference{
+							Group:     "group",
+							Kind:      "kind",
+							Name:      "name",
+							Namespace: ptrTo(gatewayv1a2.Namespace("ns")),
+						},
+					},
+					TLS: tc.routeConfig,
+				},
+			}
+			validateBackendTLSPolicy(t, route, tc.wantErrors)
+		})
+	}
+}
+
+func validateBackendTLSPolicy(t *testing.T, route *gatewayv1a2.BackendTLSPolicy, wantErrors []string) {
+	t.Helper()
+
+	ctx := context.Background()
+	err := k8sClient.Create(ctx, route)
+
+	if (len(wantErrors) != 0) != (err != nil) {
+		t.Fatalf("Unexpected response while creating BackendTLSPolicy %q; got err=\n%v\n;want error=%v", fmt.Sprintf("%v/%v", route.Namespace, route.Name), err, wantErrors)
+	}
+
+	var missingErrorStrings []string
+	for _, wantError := range wantErrors {
+		if !strings.Contains(strings.ToLower(err.Error()), strings.ToLower(wantError)) {
+			missingErrorStrings = append(missingErrorStrings, wantError)
+		}
+	}
+	if len(missingErrorStrings) != 0 {
+		t.Errorf("Unexpected response while creating BackendTLSPolicy %q; got err=\n%v\n;missing strings within error=%q", fmt.Sprintf("%v/%v", route.Namespace, route.Name), err, missingErrorStrings)
+	}
+}