Skip to content

Commit f07e734

Browse files
committed
h2c backend protocol conformance
1 parent 38125d1 commit f07e734

File tree

8 files changed

+186
-17
lines changed

8 files changed

+186
-17
lines changed

conformance/echo-basic/echo-basic.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ import (
2828
"strconv"
2929
"strings"
3030
"time"
31+
32+
"golang.org/x/net/http2"
33+
"golang.org/x/net/http2/h2c"
3134
)
3235

3336
// RequestAssertions contains information about the request and the Ingress
@@ -93,6 +96,7 @@ func main() {
9396
httpMux.HandleFunc("/health", healthHandler)
9497
httpMux.HandleFunc("/status/", statusHandler)
9598
httpMux.HandleFunc("/", echoHandler)
99+
httpMux.Handle("/h2c", h2c.NewHandler(http.HandlerFunc(h2cHandler), &http2.Server{}))
96100
httpHandler := &preserveSlashes{httpMux}
97101

98102
errchan := make(chan error)
@@ -152,6 +156,16 @@ func delayResponse(request *http.Request) error {
152156
return nil
153157
}
154158

159+
func h2cHandler(w http.ResponseWriter, r *http.Request) {
160+
if r.ProtoMajor != 2 {
161+
w.WriteHeader(http.StatusBadRequest)
162+
fmt.Fprint(w, "Expected h2(c) request")
163+
return
164+
}
165+
166+
echoHandler(w, r)
167+
}
168+
155169
func echoHandler(w http.ResponseWriter, r *http.Request) {
156170
fmt.Printf("Echoing back request made to %s to client (%s)\n", r.RequestURI, r.RemoteAddr)
157171

conformance/echo-basic/go.mod

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
module sigs.k8s.io/gateway-api/conformance/echo-basic
22

33
go 1.20
4+
5+
require golang.org/x/net v0.15.0
6+
7+
require golang.org/x/text v0.13.0 // indirect

conformance/echo-basic/go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
2+
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
3+
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
4+
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
Copyright 2023 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package tests
18+
19+
import (
20+
"context"
21+
"crypto/tls"
22+
"net"
23+
"testing"
24+
25+
"golang.org/x/net/http2"
26+
"k8s.io/apimachinery/pkg/types"
27+
"sigs.k8s.io/gateway-api/conformance/utils/http"
28+
"sigs.k8s.io/gateway-api/conformance/utils/kubernetes"
29+
"sigs.k8s.io/gateway-api/conformance/utils/suite"
30+
)
31+
32+
func init() {
33+
ConformanceTests = append(ConformanceTests, HTTPRouteBackendProtocolH2C)
34+
}
35+
36+
var HTTPRouteBackendProtocolH2C = suite.ConformanceTest{
37+
ShortName: "HTTPRouteBackendProtocolH2C",
38+
Description: "A HTTPRoute with a BackendRef that has an appProtocol kubernetes.io/h2c should be functional",
39+
Features: []suite.SupportedFeature{
40+
suite.SupportGateway,
41+
suite.SupportHTTPRoute,
42+
suite.SupportHTTPRouteBackendProtocolH2C,
43+
},
44+
Manifests: []string{
45+
"tests/httproute-backend-protocol-h2c.yaml",
46+
},
47+
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
48+
ns := "gateway-conformance-infra"
49+
routeNN := types.NamespacedName{Name: "backend-protocol-h2c", Namespace: ns}
50+
gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns}
51+
gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN)
52+
53+
t.Run("http2 prior knowledge request should reach backend", func(t *testing.T) {
54+
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, http.ExpectedResponse{
55+
Request: http.Request{
56+
Path: "/h2c",
57+
Transport: &http2.Transport{
58+
AllowHTTP: true,
59+
DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
60+
var d net.Dialer
61+
return d.DialContext(ctx, network, addr)
62+
},
63+
},
64+
},
65+
Response: http.Response{StatusCode: 200},
66+
Backend: "h2c-backend",
67+
Namespace: "gateway-conformance-web-backend",
68+
})
69+
})
70+
71+
t.Run("h2c upgrade request should reach backend", func(t *testing.T) {
72+
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, http.ExpectedResponse{
73+
Request: http.Request{
74+
Path: "/h2c",
75+
Headers: map[string]string{
76+
"HTTP2-Settings": "",
77+
"Connection": "Upgrade, HTTP2-Settings",
78+
}},
79+
Response: http.Response{StatusCode: 200},
80+
Backend: "h2c-backend",
81+
Namespace: "gateway-conformance-web-backend",
82+
})
83+
})
84+
},
85+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
apiVersion: gateway.networking.k8s.io/v1beta1
2+
kind: HTTPRoute
3+
metadata:
4+
name: backend-protocol-h2c
5+
namespace: gateway-conformance-infra
6+
spec:
7+
parentRefs:
8+
- name: same-namespace
9+
rules:
10+
- backendRefs:
11+
- name: h2c-backend
12+
port: 8080
13+
---
14+
apiVersion: v1
15+
kind: Service
16+
metadata:
17+
name: h2c-backend
18+
namespace: gateway-conformance-infra
19+
spec:
20+
selector:
21+
app: h2c-backend
22+
ports:
23+
- protocol: TCP
24+
appProtocol: kubernetes.io/h2c
25+
port: 8080
26+
targetPort: 8080
27+
---
28+
apiVersion: apps/v1
29+
kind: Deployment
30+
metadata:
31+
name: h2c-backend
32+
namespace: gateway-conformance-infra
33+
labels:
34+
app: h2c-backend
35+
spec:
36+
replicas: 1
37+
selector:
38+
matchLabels:
39+
app: h2c-backend
40+
template:
41+
metadata:
42+
labels:
43+
app: h2c-backend
44+
spec:
45+
containers:
46+
- name: h2c-backend
47+
image: gcr.io/k8s-staging-gateway-api/echo-basic:latest
48+
resources:
49+
requests:
50+
cpu: 10m

conformance/utils/http/http.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package http
1919
import (
2020
"fmt"
2121
"net"
22+
"net/http"
2223
"net/url"
2324
"strings"
2425
"testing"
@@ -69,6 +70,7 @@ type Request struct {
6970
Headers map[string]string
7071
UnfollowRedirect bool
7172
Protocol string
73+
Transport http.RoundTripper
7274
}
7375

7476
// ExpectedRequest defines expected properties of a request that reaches a backend.
@@ -128,6 +130,7 @@ func MakeRequest(t *testing.T, expected *ExpectedResponse, gwAddr, protocol, sch
128130
Protocol: protocol,
129131
Headers: map[string][]string{},
130132
UnfollowRedirect: expected.Request.UnfollowRedirect,
133+
Transport: expected.Request.Transport,
131134
}
132135

133136
if expected.Request.Headers != nil {

conformance/utils/roundtripper/roundtripper.go

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ type Request struct {
4949
CertPem []byte
5050
KeyPem []byte
5151
Server string
52+
Transport http.RoundTripper
5253
}
5354

5455
// String returns a printable version of Request for logging. Note that the
@@ -117,25 +118,29 @@ func (d *DefaultRoundTripper) CaptureRoundTrip(request Request) (*CapturedReques
117118
}
118119
}
119120

120-
transport := &http.Transport{
121-
DialContext: d.CustomDialContext,
122-
// We disable keep-alives so that we don't leak established TCP connections.
123-
// Leaking TCP connections is bad because we could eventually hit the
124-
// threshold of maximum number of open TCP connections to a specific
125-
// destination. Keep-alives are not presently utilized so disabling this has
126-
// no adverse affect.
127-
//
128-
// Ref. https://github.com/kubernetes-sigs/gateway-api/issues/2357
129-
DisableKeepAlives: true,
130-
}
131-
if request.Server != "" && len(request.CertPem) != 0 && len(request.KeyPem) != 0 {
132-
tlsConfig, err := tlsClientConfig(request.Server, request.CertPem, request.KeyPem)
133-
if err != nil {
134-
return nil, nil, err
121+
if request.Transport != nil {
122+
client.Transport = request.Transport
123+
} else {
124+
transport := &http.Transport{
125+
DialContext: d.CustomDialContext,
126+
// We disable keep-alives so that we don't leak established TCP connections.
127+
// Leaking TCP connections is bad because we could eventually hit the
128+
// threshold of maximum number of open TCP connections to a specific
129+
// destination. Keep-alives are not presently utilized so disabling this has
130+
// no adverse affect.
131+
//
132+
// Ref. https://github.com/kubernetes-sigs/gateway-api/issues/2357
133+
DisableKeepAlives: true,
134+
}
135+
if request.Server != "" && len(request.CertPem) != 0 && len(request.KeyPem) != 0 {
136+
tlsConfig, err := tlsClientConfig(request.Server, request.CertPem, request.KeyPem)
137+
if err != nil {
138+
return nil, nil, err
139+
}
140+
transport.TLSClientConfig = tlsConfig
135141
}
136-
transport.TLSClientConfig = tlsConfig
142+
client.Transport = transport
137143
}
138-
client.Transport = transport
139144

140145
method := "GET"
141146
if request.Method != "" {

conformance/utils/suite/features.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@ const (
121121

122122
// This option indicates support for multiple RequestMirror filters within the same HTTPRoute rule (extended conformance).
123123
SupportHTTPRouteRequestMultipleMirrors SupportedFeature = "HTTPRouteRequestMultipleMirrors"
124+
125+
// This option indicates support for HTTPRoute with a backendref with an appProtoocol 'kubernetes.io/h2c'
126+
SupportHTTPRouteBackendProtocolH2C SupportedFeature = "HTTPRouteBackendProtocolH2C"
124127
)
125128

126129
// HTTPRouteExtendedFeatures includes all the supported features for HTTPRoute
@@ -137,6 +140,7 @@ var HTTPRouteExtendedFeatures = sets.New(
137140
SupportHTTPRoutePathRewrite,
138141
SupportHTTPRouteRequestMirror,
139142
SupportHTTPRouteRequestMultipleMirrors,
143+
SupportHTTPRouteBackendProtocolH2C,
140144
)
141145

142146
// -----------------------------------------------------------------------------

0 commit comments

Comments
 (0)