Skip to content

Commit

Permalink
Merge pull request #2456 from dprotaso/gep-1911-h2c
Browse files Browse the repository at this point in the history
[GEP-1911] h2c backend protocol conformance
  • Loading branch information
k8s-ci-robot committed Oct 24, 2023
2 parents ebe9f31 + 0367478 commit e2912b0
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 16 deletions.
8 changes: 7 additions & 1 deletion conformance/base/manifests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
66 changes: 66 additions & 0 deletions conformance/tests/httproute-backend-protocol-h2c.go
Original file line number Diff line number Diff line change
@@ -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",
})
})
},
}
17 changes: 17 additions & 0 deletions conformance/tests/httproute-backend-protocol-h2c.yaml
Original file line number Diff line number Diff line change
@@ -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
6 changes: 5 additions & 1 deletion conformance/utils/http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}

Expand All @@ -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,
}
Expand Down
74 changes: 60 additions & 14 deletions conformance/utils/roundtripper/roundtripper.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"crypto/tls"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
"io"
"net"
Expand All @@ -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 {
Expand Down Expand Up @@ -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.
Expand All @@ -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"
Expand Down
4 changes: 4 additions & 0 deletions conformance/utils/suite/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
)

// -----------------------------------------------------------------------------
Expand Down

0 comments on commit e2912b0

Please sign in to comment.