diff --git a/build/kind.yaml b/build/kind.yaml index d6302a000d..7bc2ef7bd3 100644 --- a/build/kind.yaml +++ b/build/kind.yaml @@ -16,3 +16,6 @@ nodes: - containerPort: 443 hostPort: 443 protocol: TCP + - containerPort: 443 + hostPort: 443 + protocol: UDP diff --git a/charts/ingress-nginx/README.md b/charts/ingress-nginx/README.md index 0b1fcdcef1..1d1dd5f934 100644 --- a/charts/ingress-nginx/README.md +++ b/charts/ingress-nginx/README.md @@ -316,8 +316,9 @@ metadata: | controller.configAnnotations | object | `{}` | Annotations to be added to the controller config configuration configmap. | | controller.configMapNamespace | string | `""` | Allows customization of the configmap / nginx-configmap namespace; defaults to $(POD_NAMESPACE) | | controller.containerName | string | `"controller"` | Configures the controller container name | -| controller.containerPort | object | `{"http":80,"https":443}` | Configures the ports that the nginx-controller listens on | +| controller.containerPort | object | `{"http":80,"https":443}` | Configures the TCP ports that the nginx-controller listens on | | controller.containerSecurityContext | object | `{}` | Security context for controller containers | +| controller.containerUdpPort | object | `{}` | Configures the UDP ports that the nginx-controller listens on | | controller.customTemplate.configMapKey | string | `""` | | | controller.customTemplate.configMapName | string | `""` | | | controller.disableLeaderElection | bool | `false` | This configuration disable Nginx Controller Leader Election | @@ -342,6 +343,7 @@ metadata: | controller.hostPort.enabled | bool | `false` | Enable 'hostPort' or not | | controller.hostPort.ports.http | int | `80` | 'hostPort' http port | | controller.hostPort.ports.https | int | `443` | 'hostPort' https port | +| controller.hostPort.ports.quic | int | `443` | 'hostPort' quic port | | controller.hostname | object | `{}` | Optionally customize the pod hostname. | | controller.image.allowPrivilegeEscalation | bool | `false` | | | controller.image.chroot | bool | `false` | | @@ -451,6 +453,7 @@ metadata: | controller.service.clusterIPs | list | `[]` | Pre-defined cluster internal IP addresses of the external controller service. Take care of collisions with existing services. This value is immutable. Set once, it can not be changed without deleting and re-creating the service. Ref: https://kubernetes.io/docs/concepts/services-networking/service/#choosing-your-own-ip-address | | controller.service.enableHttp | bool | `true` | Enable the HTTP listener on both controller services or not. | | controller.service.enableHttps | bool | `true` | Enable the HTTPS listener on both controller services or not. | +| controller.service.enableQuic | bool | `false` | Enable the QUIC listener on both controller services or not. | | controller.service.enabled | bool | `true` | Enable controller services or not. This does not influence the creation of either the admission webhook or the metrics service. | | controller.service.external.enabled | bool | `true` | Enable the external controller service or not. Useful for internal-only deployments. | | controller.service.external.labels | object | `{}` | Labels to be added to the external controller service. | @@ -471,6 +474,7 @@ metadata: | controller.service.internal.loadBalancerSourceRanges | list | `[]` | Restrict access to the internal controller service. Values must be CIDRs. Allows any source address by default. | | controller.service.internal.nodePorts.http | string | `""` | Node port allocated for the internal HTTP listener. If left empty, the service controller allocates one from the configured node port range. | | controller.service.internal.nodePorts.https | string | `""` | Node port allocated for the internal HTTPS listener. If left empty, the service controller allocates one from the configured node port range. | +| controller.service.internal.nodePorts.quic | string | `""` | Node port allocated for the internal QUIC listener. If left empty, the service controller allocates one from the configured node port range. | | controller.service.internal.nodePorts.tcp | object | `{}` | Node port mapping for internal TCP listeners. If left empty, the service controller allocates them from the configured node port range. Example: tcp: 8080: 30080 | | controller.service.internal.nodePorts.udp | object | `{}` | Node port mapping for internal UDP listeners. If left empty, the service controller allocates them from the configured node port range. Example: udp: 53: 30053 | | controller.service.internal.ports | object | `{}` | | @@ -486,13 +490,16 @@ metadata: | controller.service.loadBalancerSourceRanges | list | `[]` | Restrict access to the external controller service. Values must be CIDRs. Allows any source address by default. | | controller.service.nodePorts.http | string | `""` | Node port allocated for the external HTTP listener. If left empty, the service controller allocates one from the configured node port range. | | controller.service.nodePorts.https | string | `""` | Node port allocated for the external HTTPS listener. If left empty, the service controller allocates one from the configured node port range. | +| controller.service.nodePorts.quic | string | `""` | Node port allocated for the external QUIC listener. If left empty, the service controller allocates one from the configured node port range. | | controller.service.nodePorts.tcp | object | `{}` | Node port mapping for external TCP listeners. If left empty, the service controller allocates them from the configured node port range. Example: tcp: 8080: 30080 | | controller.service.nodePorts.udp | object | `{}` | Node port mapping for external UDP listeners. If left empty, the service controller allocates them from the configured node port range. Example: udp: 53: 30053 | | controller.service.ports.http | int | `80` | Port the external HTTP listener is published with. | | controller.service.ports.https | int | `443` | Port the external HTTPS listener is published with. | +| controller.service.ports.quic | int | `443` | Port the external QUIC listener is published with. | | controller.service.sessionAffinity | string | `""` | Session affinity of the external controller service. Must be either "None" or "ClientIP" if set. Defaults to "None". Ref: https://kubernetes.io/docs/reference/networking/virtual-ips/#session-affinity | | controller.service.targetPorts.http | string | `"http"` | Port of the ingress controller the external HTTP listener is mapped to. | | controller.service.targetPorts.https | string | `"https"` | Port of the ingress controller the external HTTPS listener is mapped to. | +| controller.service.targetPorts.quic | string | `"quic"` | Port of the ingress controller the external QUIC listener is mapped to. | | controller.service.trafficDistribution | string | `""` | Traffic distribution policy of the external controller service. Set to "PreferClose" to route traffic to endpoints that are topologically closer to the client. Ref: https://kubernetes.io/docs/concepts/services-networking/service/#traffic-distribution | | controller.service.type | string | `"LoadBalancer"` | Type of the external controller service. Ref: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types | | controller.shareProcessNamespace | bool | `false` | | diff --git a/charts/ingress-nginx/templates/controller-daemonset.yaml b/charts/ingress-nginx/templates/controller-daemonset.yaml index a9a3dee399..9b91a78527 100644 --- a/charts/ingress-nginx/templates/controller-daemonset.yaml +++ b/charts/ingress-nginx/templates/controller-daemonset.yaml @@ -121,6 +121,14 @@ spec: hostPort: {{ index $.Values.controller.hostPort.ports $key | default $value }} {{- end }} {{- end }} + {{- range $key, $value := .Values.controller.containerUdpPort }} + - name: {{ $key }} + containerPort: {{ $value }} + protocol: UDP + {{- if $.Values.controller.hostPort.enabled }} + hostPort: {{ index $.Values.controller.hostPort.ports $key | default $value }} + {{- end }} + {{- end }} {{- if .Values.controller.metrics.enabled }} - name: {{ .Values.controller.metrics.portName }} containerPort: {{ .Values.controller.metrics.port }} diff --git a/charts/ingress-nginx/templates/controller-deployment.yaml b/charts/ingress-nginx/templates/controller-deployment.yaml index 224694d1b3..6a9802d2de 100644 --- a/charts/ingress-nginx/templates/controller-deployment.yaml +++ b/charts/ingress-nginx/templates/controller-deployment.yaml @@ -127,6 +127,14 @@ spec: hostPort: {{ index $.Values.controller.hostPort.ports $key | default $value }} {{- end }} {{- end }} + {{- range $key, $value := .Values.controller.containerUdpPort }} + - name: {{ $key }} + containerPort: {{ $value }} + protocol: UDP + {{- if $.Values.controller.hostPort.enabled }} + hostPort: {{ index $.Values.controller.hostPort.ports $key | default $value }} + {{- end }} + {{- end }} {{- if .Values.controller.metrics.enabled }} - name: {{ .Values.controller.metrics.portName }} containerPort: {{ .Values.controller.metrics.port }} diff --git a/charts/ingress-nginx/templates/controller-networkpolicy.yaml b/charts/ingress-nginx/templates/controller-networkpolicy.yaml index e68f9916de..c02a9e96aa 100644 --- a/charts/ingress-nginx/templates/controller-networkpolicy.yaml +++ b/charts/ingress-nginx/templates/controller-networkpolicy.yaml @@ -24,6 +24,10 @@ spec: - protocol: TCP port: {{ $value }} {{- end }} + {{- range $key, $value := .Values.controller.containerUdpPort }} + - protocol: UDP + port: {{ $value }} + {{- end }} {{- if .Values.controller.metrics.enabled }} - protocol: TCP port: {{ .Values.controller.metrics.port }} diff --git a/charts/ingress-nginx/templates/controller-service-internal.yaml b/charts/ingress-nginx/templates/controller-service-internal.yaml index 8d369526da..0d1a9667b0 100644 --- a/charts/ingress-nginx/templates/controller-service-internal.yaml +++ b/charts/ingress-nginx/templates/controller-service-internal.yaml @@ -88,6 +88,18 @@ spec: nodePort: {{ .Values.controller.service.internal.nodePorts.https }} {{- end }} {{- end }} + {{- if .Values.controller.service.enableQuic }} + - name: quic + port: {{ .Values.controller.service.internal.ports.quic | default .Values.controller.service.ports.quic }} + protocol: UDP + targetPort: {{ .Values.controller.service.internal.targetPorts.quic | default .Values.controller.service.targetPorts.quic }} + {{- if and (semverCompare ">=1.20.0-0" .Capabilities.KubeVersion.Version) (.Values.controller.service.internal.appProtocol) }} + appProtocol: https + {{- end }} + {{- if (and $setNodePorts (not (empty .Values.controller.service.internal.nodePorts.quic))) }} + nodePort: {{ .Values.controller.service.internal.nodePorts.quic }} + {{- end }} + {{- end }} {{- range $key, $value := .Values.tcp }} - name: {{ if $.Values.portNamePrefix }}{{ $.Values.portNamePrefix }}-{{ end }}{{ $key }}-tcp port: {{ $key }} diff --git a/charts/ingress-nginx/templates/controller-service.yaml b/charts/ingress-nginx/templates/controller-service.yaml index 36d2e48847..da26d860a1 100644 --- a/charts/ingress-nginx/templates/controller-service.yaml +++ b/charts/ingress-nginx/templates/controller-service.yaml @@ -88,6 +88,18 @@ spec: nodePort: {{ .Values.controller.service.nodePorts.https }} {{- end }} {{- end }} + {{- if .Values.controller.service.enableQuic }} + - name: quic + port: {{ .Values.controller.service.ports.quic }} + protocol: UDP + targetPort: {{ .Values.controller.service.targetPorts.quic }} + {{- if and (semverCompare ">=1.20.0-0" .Capabilities.KubeVersion.Version) (.Values.controller.service.appProtocol) }} + appProtocol: https + {{- end }} + {{- if (and $setNodePorts (not (empty .Values.controller.service.nodePorts.quic))) }} + nodePort: {{ .Values.controller.service.nodePorts.quic }} + {{- end }} + {{- end }} {{- range $key, $value := .Values.tcp }} - name: {{ if $.Values.portNamePrefix }}{{ $.Values.portNamePrefix }}-{{ end }}{{ $key }}-tcp port: {{ $key }} diff --git a/charts/ingress-nginx/tests/controller-daemonset_test.yaml b/charts/ingress-nginx/tests/controller-daemonset_test.yaml index 9f79a3b23d..12009324cf 100644 --- a/charts/ingress-nginx/tests/controller-daemonset_test.yaml +++ b/charts/ingress-nginx/tests/controller-daemonset_test.yaml @@ -208,3 +208,15 @@ tests: - equal: path: spec.template.spec.runtimeClassName value: myClass + + - it: should create a DaemonSet with a custom UDP container port if `controller.containerUdpPort.quic` is set + set: + controller.kind: DaemonSet + controller.containerUdpPort.quic: 1234 + asserts: + - contains: + path: spec.template.spec.containers[0].ports + content: + name: quic + containerPort: 1234 + protocol: UDP diff --git a/charts/ingress-nginx/tests/controller-deployment_test.yaml b/charts/ingress-nginx/tests/controller-deployment_test.yaml index 37b6908853..cd9639eb78 100644 --- a/charts/ingress-nginx/tests/controller-deployment_test.yaml +++ b/charts/ingress-nginx/tests/controller-deployment_test.yaml @@ -231,3 +231,14 @@ tests: - equal: path: spec.template.spec.runtimeClassName value: myClass + + - it: should create a Deployment with a custom UDP container port if `controller.containerUdpPort.quic` is set + set: + controller.containerUdpPort.quic: 1234 + asserts: + - contains: + path: spec.template.spec.containers[0].ports + content: + name: quic + containerPort: 1234 + protocol: UDP diff --git a/charts/ingress-nginx/tests/controller-networkpolicy_test.yaml b/charts/ingress-nginx/tests/controller-networkpolicy_test.yaml index 5de12e9c4b..f33f9d7af1 100644 --- a/charts/ingress-nginx/tests/controller-networkpolicy_test.yaml +++ b/charts/ingress-nginx/tests/controller-networkpolicy_test.yaml @@ -21,3 +21,14 @@ tests: - equal: path: metadata.name value: RELEASE-NAME-ingress-nginx-controller + + - it: should create a NetworkPolicy with a custom UDP port if `controller.containerUdpPort.quic` is set + set: + controller.networkPolicy.enabled: true + controller.containerUdpPort.quic: 1234 + asserts: + - contains: + path: spec.ingress[0].ports + content: + protocol: UDP + port: 1234 diff --git a/charts/ingress-nginx/tests/controller-service-internal_test.yaml b/charts/ingress-nginx/tests/controller-service-internal_test.yaml index a44d974817..b2a17ff2fd 100644 --- a/charts/ingress-nginx/tests/controller-service-internal_test.yaml +++ b/charts/ingress-nginx/tests/controller-service-internal_test.yaml @@ -73,3 +73,57 @@ tests: - equal: path: metadata.labels["external-dns.alpha.kubernetes.io/hostname"] value: internal.example.com + + - it: should create a Service with a default UDP port if `controller.service.enableQuic` is set + set: + controller.service.internal.enabled: true + controller.service.internal.annotations: + test.annotation: "true" + controller.service.enableQuic: true + asserts: + - contains: + path: spec.ports + content: + name: quic + port: 443 + protocol: UDP + targetPort: quic + appProtocol: https + + - it: should create a Service with a custom internal UDP port if `controller.service.enableQuic` is set + set: + controller.service.internal.enabled: true + controller.service.internal.annotations: + test.annotation: "true" + controller.service.enableQuic: true + controller.service.ports.quic: 1234 + controller.service.targetPorts.quic: 5678 + controller.service.internal.ports.quic: 4321 + controller.service.internal.targetPorts.quic: 8765 + asserts: + - contains: + path: spec.ports + content: + name: quic + port: 4321 + protocol: UDP + targetPort: 8765 + appProtocol: https + + - it: should create a Service with a custom service UDP port if `controller.service.enableQuic` is set + set: + controller.service.internal.enabled: true + controller.service.internal.annotations: + test.annotation: "true" + controller.service.enableQuic: true + controller.service.ports.quic: 1234 + controller.service.targetPorts.quic: 5678 + asserts: + - contains: + path: spec.ports + content: + name: quic + port: 1234 + protocol: UDP + targetPort: 5678 + appProtocol: https diff --git a/charts/ingress-nginx/tests/controller-service_test.yaml b/charts/ingress-nginx/tests/controller-service_test.yaml index f3e8cf0302..1e96cce12f 100644 --- a/charts/ingress-nginx/tests/controller-service_test.yaml +++ b/charts/ingress-nginx/tests/controller-service_test.yaml @@ -72,3 +72,31 @@ tests: - equal: path: metadata.labels["external-dns.alpha.kubernetes.io/hostname"] value: external.example.com + + - it: should create a Service with a default UDP port if `controller.service.enableQuic` is set + set: + controller.service.enableQuic: true + asserts: + - contains: + path: spec.ports + content: + name: quic + port: 443 + protocol: UDP + targetPort: quic + appProtocol: https + + - it: should create a Service with a custom UDP port if `controller.service.enableQuic` is set + set: + controller.service.enableQuic: true + controller.service.ports.quic: 1234 + controller.service.targetPorts.quic: 5678 + asserts: + - contains: + path: spec.ports + content: + name: quic + port: 1234 + protocol: UDP + targetPort: 5678 + appProtocol: https diff --git a/charts/ingress-nginx/values.yaml b/charts/ingress-nginx/values.yaml index d747d30d92..0f84e0b2e2 100644 --- a/charts/ingress-nginx/values.yaml +++ b/charts/ingress-nginx/values.yaml @@ -47,10 +47,13 @@ controller: readOnlyRootFilesystem: false # -- Configures the controller container name containerName: controller - # -- Configures the ports that the nginx-controller listens on + # -- Configures the TCP ports that the nginx-controller listens on containerPort: http: 80 https: 443 + # -- Configures the UDP ports that the nginx-controller listens on + containerUdpPort: {} + # quic: 443 # -- Global configuration passed to the ConfigMap consumed by the controller. Values may contain Helm templates. # Ref.: https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/ config: {} @@ -115,6 +118,8 @@ controller: http: 80 # -- 'hostPort' https port https: 443 + # -- 'hostPort' quic port + quic: 443 # NetworkPolicy for controller component. networkPolicy: # -- Enable 'networkPolicy' or not @@ -546,16 +551,22 @@ controller: enableHttp: true # -- Enable the HTTPS listener on both controller services or not. enableHttps: true + # -- Enable the QUIC listener on both controller services or not. + enableQuic: false ports: # -- Port the external HTTP listener is published with. http: 80 # -- Port the external HTTPS listener is published with. https: 443 + # -- Port the external QUIC listener is published with. + quic: 443 targetPorts: # -- Port of the ingress controller the external HTTP listener is mapped to. http: http # -- Port of the ingress controller the external HTTPS listener is mapped to. https: https + # -- Port of the ingress controller the external QUIC listener is mapped to. + quic: quic # -- Declare the app protocol of the external HTTP and HTTPS listeners or not. Supersedes provider-specific annotations for declaring the backend protocol. # Ref: https://kubernetes.io/docs/concepts/services-networking/service/#application-protocol appProtocol: true @@ -564,6 +575,8 @@ controller: http: "" # -- Node port allocated for the external HTTPS listener. If left empty, the service controller allocates one from the configured node port range. https: "" + # -- Node port allocated for the external QUIC listener. If left empty, the service controller allocates one from the configured node port range. + quic: "" # -- Node port mapping for external TCP listeners. If left empty, the service controller allocates them from the configured node port range. # Example: # tcp: @@ -638,6 +651,9 @@ controller: # -- Port the internal HTTPS listener is published with. # Defaults to the value of `controller.service.ports.https`. # https: 443 + # -- Port the internal QUIC listener is published with. + # Defaults to the value of `controller.service.ports.quic`. + # quic: 443 targetPorts: {} # -- Port of the ingress controller the internal HTTP listener is mapped to. @@ -646,6 +662,9 @@ controller: # -- Port of the ingress controller the internal HTTPS listener is mapped to. # Defaults to the value of `controller.service.targetPorts.https`. # https: https + # -- Port of the ingress controller the internal QUIC listener is mapped to. + # Defaults to the value of `controller.service.targetPorts.quic`. + # quic: quic # -- Declare the app protocol of the internal HTTP and HTTPS listeners or not. Supersedes provider-specific annotations for declaring the backend protocol. # Ref: https://kubernetes.io/docs/concepts/services-networking/service/#application-protocol @@ -655,6 +674,8 @@ controller: http: "" # -- Node port allocated for the internal HTTPS listener. If left empty, the service controller allocates one from the configured node port range. https: "" + # -- Node port allocated for the internal QUIC listener. If left empty, the service controller allocates one from the configured node port range. + quic: "" # -- Node port mapping for internal TCP listeners. If left empty, the service controller allocates them from the configured node port range. # Example: # tcp: diff --git a/cmd/nginx/main_test.go b/cmd/nginx/main_test.go index 13f1e9eecb..03c9e12615 100644 --- a/cmd/nginx/main_test.go +++ b/cmd/nginx/main_test.go @@ -98,7 +98,7 @@ func TestHandleSigterm(t *testing.T) { os.Args = oldArgs }() - os.Args = []string{"cmd", "--default-backend-service", "ingress-nginx/default-backend-http", "--http-port", "0", "--https-port", "0"} + os.Args = []string{"cmd", "--default-backend-service", "ingress-nginx/default-backend-http", "--http-port", "0", "--https-port", "0", "--quic-port", "0"} _, conf, err := ingressflags.ParseFlags() if err != nil { t.Errorf("Unexpected error creating NGINX controller: %v", err) diff --git a/docs/user-guide/cli-arguments.md b/docs/user-guide/cli-arguments.md index a33e75159b..a36fd0dddc 100644 --- a/docs/user-guide/cli-arguments.md +++ b/docs/user-guide/cli-arguments.md @@ -27,6 +27,7 @@ They are set in the container spec of the `ingress-nginx-controller` Deployment | `--enable-metrics` | Enables the collection of NGINX metrics. (Default: false) | | `--enable-ssl-chain-completion` | Autocomplete SSL certificate chains with missing intermediate CA certificates. Certificates uploaded to Kubernetes must have the "Authority Information Access" X.509 v3 extension for this to succeed. (default false)| | `--enable-ssl-passthrough` | Enable SSL Passthrough. (default false) | +| `--enable-quic` | Enable QUIC. (default false) | | `--disable-leader-election` | Disable Leader Election on Nginx Controller. (default false) | | `--enable-topology-aware-routing` | Enable topology aware routing feature, needs service object annotation service.kubernetes.io/topology-mode sets to auto. (default false) | | `--exclude-socket-metrics` | Set of socket request metrics to exclude which won't be exported nor being calculated. The possible socket request metrics to exclude are documented in the monitoring guide e.g. 'nginx_ingress_controller_request_duration_seconds,nginx_ingress_controller_response_size'| @@ -36,6 +37,7 @@ They are set in the container spec of the `ingress-nginx-controller` Deployment | `--healthz-host` | Address to bind the healthz endpoint. | | `--http-port` | Port to use for servicing HTTP traffic. (default 80) | | `--https-port` | Port to use for servicing HTTPS traffic. (default 443) | +| `--quic-port` | Port to use for servicing QUIC traffic. (default 443) | | `--ingress-class` | Name of the ingress class this controller satisfies. The class of an Ingress object is set using the field IngressClassName in Kubernetes clusters version v1.18.0 or higher or the annotation "kubernetes.io/ingress.class" (deprecated). If this parameter is not set, or set to the default value of "nginx", it will handle ingresses with either an empty or "nginx" class name. | | `--ingress-class-by-name` | Define if Ingress Controller should watch for Ingress Class by Name together with Controller Class. (default false). | | `--internal-logger-address` | Address to be used when binding internal syslogger. (default 127.0.0.1:11514) | diff --git a/docs/user-guide/nginx-configuration/configmap.md b/docs/user-guide/nginx-configuration/configmap.md index 5b57fc5a6f..71c19bafba 100644 --- a/docs/user-guide/nginx-configuration/configmap.md +++ b/docs/user-guide/nginx-configuration/configmap.md @@ -59,6 +59,13 @@ The following table shows a configuration option's name, type, and the default v | [http2-max-header-size](#http2-max-header-size) | string | "" | DEPRECATED in favour of [large_client_header_buffers](#large-client-header-buffers) | | [http2-max-requests](#http2-max-requests) | int | 0 | DEPRECATED in favour of [keepalive_requests](#keepalive-requests) | | [http2-max-concurrent-streams](#http2-max-concurrent-streams) | int | 128 | | +| [http3-hq](#http3-hq) | bool | "false" | | +| [http3-max-concurrent-streams](#http3-max-concurrent-streams) | int | 128 | | +| [http3-stream-buffer-size](#http3-stream-buffer-size) | string | "64k" | | +| [quic-active-connection-id-limit](#quic-active-connection-id-limit) | int | 2 | | +| [quic-bpf](#quic-bpf) | bool | "false" | | +| [quic-gso](#quic-gso) | bool | "false" | | +| [quic-retry](#quic-retry) | bool | "false" | | | [hsts](#hsts) | bool | "true" | | | [hsts-include-subdomains](#hsts-include-subdomains) | bool | "true" | | | [hsts-max-age](#hsts-max-age) | string | "31536000" | | @@ -441,6 +448,55 @@ Sets the maximum number of concurrent HTTP/2 streams in a connection. _References:_ [https://nginx.org/en/docs/http/ngx_http_v2_module.html#http2_max_concurrent_streams](https://nginx.org/en/docs/http/ngx_http_v2_module.html#http2_max_concurrent_streams) +## http3-hq + +Enables HTTP/0.9 protocol negotiation used in [QUIC interoperability tests](https://github.com/marten-seemann/quic-interop-runner). + +_References:_ +[https://nginx.org/en/docs/http/ngx_http_v3_module.html#http3_hq](https://nginx.org/en/docs/http/ngx_http_v3_module.html#http3_hq) + +## http3-max-concurrent-streams + +Sets the maximum number of concurrent HTTP/3 request streams in a connection. + +_References:_ +[https://nginx.org/en/docs/http/ngx_http_v3_module.html#http3_max_concurrent_streams](https://nginx.org/en/docs/http/ngx_http_v3_module.html#http3_max_concurrent_streams) + +## http3-stream-buffer-size + +Sets the size of the buffer used for reading and writing of the QUIC streams. + +_References:_ +[https://nginx.org/en/docs/http/ngx_http_v3_module.html#http3_stream_buffer_size](https://nginx.org/en/docs/http/ngx_http_v3_module.html#http3_stream_buffer_size) + +## quic-active-connection-id-limit + +Sets the QUIC `active_connection_id_limit` transport parameter value. This is the maximum number of client connection IDs which can be stored on the server. + +_References:_ +[https://nginx.org/en/docs/http/ngx_http_v3_module.html#quic_active_connection_id_limit](https://nginx.org/en/docs/http/ngx_http_v3_module.html#quic_active_connection_id_limit) + +## quic-bpf + +Enables routing of QUIC packets using [eBPF](https://ebpf.io/). When enabled, this allows supporting QUIC connection migration. + +_References:_ +[https://nginx.org/en/docs/http/ngx_http_v3_module.html#quic_bpf](https://nginx.org/en/docs/http/ngx_http_v3_module.html#quic_bpf) + +## quic-gso + +Enables sending in optimized batch mode using segmentation offloading. + +_References:_ +[https://nginx.org/en/docs/http/ngx_http_v3_module.html#quic_gso](https://nginx.org/en/docs/http/ngx_http_v3_module.html#quic_gso) + +## quic-retry + +Enables the [QUIC Address Validation](https://datatracker.ietf.org/doc/html/rfc9000#name-address-validation) feature. This includes sending a new token in a `Retry` packet or a `NEW_TOKEN` frame and validating a token received in the `Initial` packet. + +_References:_ +[https://nginx.org/en/docs/http/ngx_http_v3_module.html#quic_retry](https://nginx.org/en/docs/http/ngx_http_v3_module.html#quic_retry) + ## hsts Enables or disables the header HSTS in servers running SSL. diff --git a/internal/ingress/controller/config/config.go b/internal/ingress/controller/config/config.go index 15adaab026..5fd875a779 100644 --- a/internal/ingress/controller/config/config.go +++ b/internal/ingress/controller/config/config.go @@ -244,6 +244,34 @@ type Configuration struct { // Sets the maximum number of concurrent HTTP/2 streams in a connection. HTTP2MaxConcurrentStreams int `json:"http2-max-concurrent-streams,omitempty"` + // https://nginx.org/en/docs/http/ngx_http_v3_module.html#http3_hq + // Enables HTTP/0.9 protocol negotiation used in QUIC interoperability tests. + HTTP3HQ bool `json:"http3-hq,omitempty"` + + // https://nginx.org/en/docs/http/ngx_http_v3_module.html#http3_max_concurrent_streams + // Sets the maximum number of concurrent HTTP/3 request streams in a connection. + HTTP3MaxConcurrentStreams int `json:"http3-max-concurrent-streams,omitempty"` + + // https://nginx.org/en/docs/http/ngx_http_v3_module.html#http3_stream_buffer_size + // Sets the size of the buffer used for reading and writing of the QUIC streams. + HTTP3StreamBufferSize string `json:"http3-stream-buffer-size,omitempty"` + + // https://nginx.org/en/docs/http/ngx_http_v3_module.html#quic_active_connection_id_limit + // Sets the QUIC active_connection_id_limit transport parameter value. This is the maximum number of client connection IDs which can be stored on the server. + QUICActiveConnectionIDLimit int `json:"quic-active-connection-id-limit,omitempty"` + + // https://nginx.org/en/docs/http/ngx_http_v3_module.html#quic_bpf + // Enables routing of QUIC packets using eBPF. When enabled, this allows supporting QUIC connection migration. + QUICBPF bool `json:"quic-bpf,omitempty"` + + // https://nginx.org/en/docs/http/ngx_http_v3_module.html#quic_gso + // Enables sending in optimized batch mode using segmentation offloading. + QUICGSO bool `json:"quic-gso,omitempty"` + + // https://nginx.org/en/docs/http/ngx_http_v3_module.html#quic_retry + // Enables the QUIC Address Validation feature. This includes sending a new token in a Retry packet or a NEW_TOKEN frame and validating a token received in the Initial packet. + QUICRetry bool `json:"quic-retry,omitempty"` + // Enables or disables the header HSTS in servers running SSL HSTS bool `json:"hsts,omitempty"` @@ -797,6 +825,13 @@ func NewDefault() Configuration { HTTP2MaxHeaderSize: "", HTTP2MaxRequests: 0, HTTP2MaxConcurrentStreams: 128, + HTTP3HQ: false, + HTTP3MaxConcurrentStreams: 128, + HTTP3StreamBufferSize: "64k", + QUICActiveConnectionIDLimit: 2, + QUICBPF: false, + QUICGSO: false, + QUICRetry: false, HTTPRedirectCode: 308, HSTS: true, HSTSIncludeSubdomains: true, @@ -936,6 +971,7 @@ type TemplateConfig struct { Cfg Configuration `json:"Cfg"` IsIPV6Enabled bool `json:"IsIPV6Enabled"` IsSSLPassthroughEnabled bool `json:"IsSSLPassthroughEnabled"` + IsQUICEnabled bool `json:"IsQUICEnabled"` NginxStatusIpv4Whitelist []string `json:"NginxStatusIpv4Whitelist"` NginxStatusIpv6Whitelist []string `json:"NginxStatusIpv6Whitelist"` RedirectServers interface{} `json:"RedirectServers"` @@ -956,6 +992,7 @@ type TemplateConfig struct { type ListenPorts struct { HTTP int `json:"HTTP"` HTTPS int `json:"HTTPS"` + QUIC int `json:"QUIC"` Health int `json:"Health"` Default int `json:"Default"` SSLProxy int `json:"SSLProxy"` diff --git a/internal/ingress/controller/controller.go b/internal/ingress/controller/controller.go index 0894c0dfc1..24e61e6190 100644 --- a/internal/ingress/controller/controller.go +++ b/internal/ingress/controller/controller.go @@ -101,6 +101,8 @@ type Configuration struct { EnableSSLPassthrough bool + EnableQUIC bool + DisableLeaderElection bool EnableProfiling bool @@ -471,6 +473,7 @@ func (n *NGINXController) getStreamServices(configmapName string, proto apiv1.Pr rp := []int{ n.cfg.ListenPorts.HTTP, n.cfg.ListenPorts.HTTPS, + n.cfg.ListenPorts.QUIC, n.cfg.ListenPorts.SSLProxy, n.cfg.ListenPorts.Health, n.cfg.ListenPorts.Default, diff --git a/internal/ingress/controller/nginx.go b/internal/ingress/controller/nginx.go index 20fad5afb8..05ef0b555a 100644 --- a/internal/ingress/controller/nginx.go +++ b/internal/ingress/controller/nginx.go @@ -620,6 +620,7 @@ func (n *NGINXController) generateTemplate(cfg ngx_config.Configuration, ingress NginxStatusIpv6Whitelist: cfg.NginxStatusIpv6Whitelist, RedirectServers: utilingress.BuildRedirects(ingressCfg.Servers), IsSSLPassthroughEnabled: n.cfg.EnableSSLPassthrough, + IsQUICEnabled: n.cfg.EnableQUIC, ListenPorts: n.cfg.ListenPorts, EnableMetrics: n.cfg.EnableMetrics, MaxmindEditionFiles: n.cfg.MaxmindEditionFiles, diff --git a/internal/ingress/controller/template/template.go b/internal/ingress/controller/template/template.go index 873248bb22..fc55233c6c 100644 --- a/internal/ingress/controller/template/template.go +++ b/internal/ingress/controller/template/template.go @@ -1384,7 +1384,7 @@ func buildHTTPListener(t, s interface{}) string { addrV4 = tc.Cfg.BindAddressIpv4 } - co := commonListenOptions(&tc, hostname) + co := commonListenOptions(&tc, hostname, true) out = append(out, httpListener(addrV4, co, &tc)...) @@ -1417,14 +1417,18 @@ func buildHTTPSListener(t, s interface{}) string { return "" } - co := commonListenOptions(&tc, hostname) + httpsCo := commonListenOptions(&tc, hostname, true) + quicCo := commonListenOptions(&tc, hostname, false) addrV4 := []string{""} if len(tc.Cfg.BindAddressIpv4) > 0 { addrV4 = tc.Cfg.BindAddressIpv4 } - out = append(out, httpsListener(addrV4, co, &tc)...) + out = append(out, httpsListener(addrV4, httpsCo, &tc)...) + if tc.IsQUICEnabled { + out = append(out, quicListener(addrV4, quicCo, &tc)...) + } if !tc.IsIPV6Enabled { return strings.Join(out, "\n") @@ -1435,12 +1439,15 @@ func buildHTTPSListener(t, s interface{}) string { addrV6 = tc.Cfg.BindAddressIpv6 } - out = append(out, httpsListener(addrV6, co, &tc)...) + out = append(out, httpsListener(addrV6, httpsCo, &tc)...) + if tc.IsQUICEnabled { + out = append(out, quicListener(addrV6, quicCo, &tc)...) + } return strings.Join(out, "\n") } -func commonListenOptions(template *config.TemplateConfig, hostname string) string { +func commonListenOptions(template *config.TemplateConfig, hostname string, withBacklog bool) string { var out []string if template.Cfg.UseProxyProtocol { @@ -1459,7 +1466,9 @@ func commonListenOptions(template *config.TemplateConfig, hostname string) strin out = append(out, "reuseport") } - out = append(out, fmt.Sprintf("backlog=%v", template.BacklogSize)) + if withBacklog { + out = append(out, fmt.Sprintf("backlog=%v", template.BacklogSize)) + } return strings.Join(out, " ") } @@ -1513,6 +1522,25 @@ func httpsListener(addresses []string, co string, tc *config.TemplateConfig) []s return out } +func quicListener(addresses []string, co string, tc *config.TemplateConfig) []string { + out := make([]string, 0) + for _, address := range addresses { + lo := []string{"listen"} + + if address == "" { + lo = append(lo, fmt.Sprintf("%v", tc.ListenPorts.QUIC)) + } else { + lo = append(lo, fmt.Sprintf("%v:%v", address, tc.ListenPorts.QUIC)) + } + + lo = append(lo, co, "quic;") + + out = append(out, strings.Join(lo, " ")) + } + + return out +} + func buildOpentelemetryForLocation(isOTEnabled, isOTTrustSet bool, location *ingress.Location) string { isOTEnabledInLoc := location.Opentelemetry.Enabled isOTSetInLoc := location.Opentelemetry.Set diff --git a/internal/net/net.go b/internal/net/net.go index 70dd156ca1..c8ee9b50f6 100644 --- a/internal/net/net.go +++ b/internal/net/net.go @@ -39,6 +39,17 @@ func IsPortAvailable(p int) bool { return err == nil } +// IsUDPPortAvailable checks if a UDP port is available or not +func IsUDPPortAvailable(p int) bool { + ln, err := _net.ListenPacket("udp", fmt.Sprintf(":%v", p)) + defer func() { + if ln != nil { + ln.Close() + } + }() + return err == nil +} + // IsIPv6Enabled checks if IPV6 is enabled or not and we have // at least one configured in the pod func IsIPv6Enabled() bool { diff --git a/internal/net/net_test.go b/internal/net/net_test.go index d943ab28b7..ed363f411a 100644 --- a/internal/net/net_test.go +++ b/internal/net/net_test.go @@ -53,8 +53,33 @@ func TestIsPortAvailable(t *testing.T) { } defer ln.Close() - p := ln.Addr().(*net.TCPAddr).Port + addr, ok := ln.Addr().(*net.TCPAddr) + if !ok { + t.Fatalf("unexpected type: %T", ln.Addr()) + } + p := addr.Port if IsPortAvailable(p) { t.Fatalf("expected port %v to not be available", p) } } + +func TestIsUDPPortAvailable(t *testing.T) { + if !IsUDPPortAvailable(0) { + t.Fatal("expected port 0 to be available (random port) but returned false") + } + + ln, err := net.ListenPacket("udp", ":0") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + defer ln.Close() + + addr, ok := ln.LocalAddr().(*net.UDPAddr) + if !ok { + t.Fatalf("unexpected type: %T", ln.LocalAddr()) + } + p := addr.Port + if IsUDPPortAvailable(p) { + t.Fatalf("expected port %v to not be available", p) + } +} diff --git a/pkg/flags/flags.go b/pkg/flags/flags.go index 376484649b..785a4f4f26 100644 --- a/pkg/flags/flags.go +++ b/pkg/flags/flags.go @@ -150,6 +150,9 @@ Requires the update-status parameter.`) enableSSLPassthrough = flags.Bool("enable-ssl-passthrough", false, `Enable SSL Passthrough.`) + enableQUIC = flags.Bool("enable-quic", false, + `Enable QUIC.`) + disableLeaderElection = flags.Bool("disable-leader-election", false, `Disable Leader Election on NGINX Controller.`) @@ -193,6 +196,7 @@ Requires the update-status parameter.`) httpPort = flags.Int("http-port", 80, `Port to use for servicing HTTP traffic.`) httpsPort = flags.Int("https-port", 443, `Port to use for servicing HTTPS traffic.`) + quicPort = flags.Int("quic-port", 443, `Port to use for servicing QUIC traffic.`) sslProxyPort = flags.Int("ssl-passthrough-proxy-port", 442, `Port to use internally for SSL Passthrough.`) defServerPort = flags.Int("default-server-port", 8181, `Port to use for exposing the default server (catch-all).`) @@ -299,10 +303,18 @@ https://blog.maxmind.com/2019/12/significant-changes-to-accessing-and-using-geol return false, nil, fmt.Errorf("port %v is already in use. Please check the flag --ssl-passthrough-proxy-port", *sslProxyPort) } + if *enableQUIC && !ing_net.IsUDPPortAvailable(*quicPort) { + return false, nil, fmt.Errorf("port %v is already in use. Please check the flag --quic-port", *quicPort) + } + if *publishSvc != "" && *publishStatusAddress != "" { return false, nil, fmt.Errorf("flags --publish-service and --publish-status-address are mutually exclusive") } + if *enableSSLPassthrough && *enableQUIC { + return false, nil, fmt.Errorf("flags --enable-ssl-passthrough and --enable-quic are mutually exclusive") + } + nginx.HealthPath = *defHealthzURL if *defHealthCheckTimeout > 0 { @@ -356,6 +368,7 @@ https://blog.maxmind.com/2019/12/significant-changes-to-accessing-and-using-geol MonitorMaxBatchSize: *monitorMaxBatchSize, DisableServiceExternalName: *disableServiceExternalName, EnableSSLPassthrough: *enableSSLPassthrough, + EnableQUIC: *enableQUIC, DisableLeaderElection: *disableLeaderElection, ResyncPeriod: *resyncPeriod, DefaultService: *defaultSvc, @@ -382,6 +395,7 @@ https://blog.maxmind.com/2019/12/significant-changes-to-accessing-and-using-geol Health: *healthzPort, HTTP: *httpPort, HTTPS: *httpsPort, + QUIC: *quicPort, SSLProxy: *sslProxyPort, }, IngressClassConfiguration: &ingressclass.Configuration{ diff --git a/pkg/flags/flags_test.go b/pkg/flags/flags_test.go index fdf153021a..294a2f3f2b 100644 --- a/pkg/flags/flags_test.go +++ b/pkg/flags/flags_test.go @@ -39,6 +39,7 @@ func TestDefaults(t *testing.T) { "--default-backend-service", "namespace/test", "--http-port", "0", "--https-port", "0", + "--quic-port", "0", } showVersion, conf, err := ParseFlags() @@ -64,7 +65,7 @@ func TestFlagConflict(t *testing.T) { oldArgs := os.Args defer func() { os.Args = oldArgs }() - os.Args = []string{"cmd", "--publish-service", "namespace/test", "--http-port", "0", "--https-port", "0", "--publish-status-address", "1.1.1.1"} + os.Args = []string{"cmd", "--publish-service", "namespace/test", "--http-port", "0", "--https-port", "0", "--quic-port", "0", "--publish-status-address", "1.1.1.1"} _, _, err := ParseFlags() if err == nil { @@ -77,7 +78,7 @@ func TestMaxmindEdition(t *testing.T) { oldArgs := os.Args defer func() { os.Args = oldArgs }() - os.Args = []string{"cmd", "--publish-service", "namespace/test", "--http-port", "0", "--https-port", "0", "--maxmind-license-key", "0000000", "--maxmind-edition-ids", "GeoLite2-City, TestCheck"} + os.Args = []string{"cmd", "--publish-service", "namespace/test", "--http-port", "0", "--https-port", "0", "--quic-port", "0", "--maxmind-license-key", "0000000", "--maxmind-edition-ids", "GeoLite2-City, TestCheck"} _, _, err := ParseFlags() if err == nil { @@ -90,7 +91,7 @@ func TestMaxmindMirror(t *testing.T) { oldArgs := os.Args defer func() { os.Args = oldArgs }() - os.Args = []string{"cmd", "--publish-service", "namespace/test", "--http-port", "0", "--https-port", "0", "--maxmind-mirror", "http://geoip.local", "--maxmind-license-key", "0000000", "--maxmind-edition-ids", "GeoLite2-City, TestCheck"} + os.Args = []string{"cmd", "--publish-service", "namespace/test", "--http-port", "0", "--https-port", "0", "--quic-port", "0", "--maxmind-mirror", "http://geoip.local", "--maxmind-license-key", "0000000", "--maxmind-edition-ids", "GeoLite2-City, TestCheck"} _, _, err := ParseFlags() if err == nil { @@ -103,7 +104,7 @@ func TestMaxmindRetryDownload(t *testing.T) { oldArgs := os.Args defer func() { os.Args = oldArgs }() - os.Args = []string{"cmd", "--publish-service", "namespace/test", "--http-port", "0", "--https-port", "0", "--maxmind-mirror", "http://127.0.0.1", "--maxmind-license-key", "0000000", "--maxmind-edition-ids", "GeoLite2-City", "--maxmind-retries-timeout", "1s", "--maxmind-retries-count", "3"} + os.Args = []string{"cmd", "--publish-service", "namespace/test", "--http-port", "0", "--https-port", "0", "--quic-port", "0", "--maxmind-mirror", "http://127.0.0.1", "--maxmind-license-key", "0000000", "--maxmind-edition-ids", "GeoLite2-City", "--maxmind-retries-timeout", "1s", "--maxmind-retries-count", "3"} _, _, err := ParseFlags() if err == nil { @@ -116,7 +117,7 @@ func TestDisableLeaderElectionFlag(t *testing.T) { oldArgs := os.Args defer func() { os.Args = oldArgs }() - os.Args = []string{"cmd", "--disable-leader-election", "--http-port", "80", "--https-port", "443"} + os.Args = []string{"cmd", "--disable-leader-election", "--http-port", "80", "--https-port", "443", "--quic-port", "443"} _, conf, err := ParseFlags() if err != nil { @@ -133,7 +134,7 @@ func TestIfLeaderElectionDisabledFlagIsFalse(t *testing.T) { oldArgs := os.Args defer func() { os.Args = oldArgs }() - os.Args = []string{"cmd", "--http-port", "80", "--https-port", "443"} + os.Args = []string{"cmd", "--http-port", "80", "--https-port", "443", "--quic-port", "443"} _, conf, err := ParseFlags() if err != nil { @@ -150,7 +151,7 @@ func TestLeaderElectionTTLDefaultValue(t *testing.T) { oldArgs := os.Args defer func() { os.Args = oldArgs }() - os.Args = []string{"cmd", "--http-port", "80", "--https-port", "443"} + os.Args = []string{"cmd", "--http-port", "80", "--https-port", "443", "--quic-port", "443"} _, conf, err := ParseFlags() if err != nil { @@ -167,7 +168,7 @@ func TestLeaderElectionTTLParseValueInSeconds(t *testing.T) { oldArgs := os.Args defer func() { os.Args = oldArgs }() - os.Args = []string{"cmd", "--http-port", "80", "--https-port", "443", "--election-ttl", "10s"} + os.Args = []string{"cmd", "--http-port", "80", "--https-port", "443", "--quic-port", "443", "--election-ttl", "10s"} _, conf, err := ParseFlags() if err != nil { @@ -184,7 +185,7 @@ func TestLeaderElectionTTLParseValueInMinutes(t *testing.T) { oldArgs := os.Args defer func() { os.Args = oldArgs }() - os.Args = []string{"cmd", "--http-port", "80", "--https-port", "443", "--election-ttl", "10m"} + os.Args = []string{"cmd", "--http-port", "80", "--https-port", "443", "--quic-port", "443", "--election-ttl", "10m"} _, conf, err := ParseFlags() if err != nil { @@ -201,7 +202,7 @@ func TestLeaderElectionTTLParseValueInHours(t *testing.T) { oldArgs := os.Args defer func() { os.Args = oldArgs }() - os.Args = []string{"cmd", "--http-port", "80", "--https-port", "443", "--election-ttl", "1h"} + os.Args = []string{"cmd", "--http-port", "80", "--https-port", "443", "--quic-port", "443", "--election-ttl", "1h"} _, conf, err := ParseFlags() if err != nil { diff --git a/rootfs/etc/nginx/template/nginx.tmpl b/rootfs/etc/nginx/template/nginx.tmpl index c618bd2d4f..3921e561a2 100644 --- a/rootfs/etc/nginx/template/nginx.tmpl +++ b/rootfs/etc/nginx/template/nginx.tmpl @@ -2,6 +2,7 @@ {{ $servers := .Servers }} {{ $cfg := .Cfg }} {{ $IsIPV6Enabled := .IsIPV6Enabled }} +{{ $IsQUICEnabled := .IsQUICEnabled }} {{ $healthzURI := .HealthzURI }} {{ $backends := .Backends }} {{ $proxyHeaders := .ProxySetHeaders }} @@ -46,6 +47,10 @@ worker_rlimit_nofile {{ $cfg.MaxWorkerOpenFiles }}; {{/* avoid waiting too long during a reload */}} worker_shutdown_timeout {{ $cfg.WorkerShutdownTimeout }} ; +{{ if $IsQUICEnabled }} +quic_bpf {{ if $cfg.QUICBPF }}on{{ else }}off{{ end }}; +{{ end }} + {{ if not (empty $cfg.MainSnippet) }} {{ $cfg.MainSnippet }} {{ end }} @@ -288,6 +293,15 @@ http { http2_max_concurrent_streams {{ $cfg.HTTP2MaxConcurrentStreams }}; + {{ if $IsQUICEnabled }} + http3_hq {{ if $cfg.HTTP3HQ }}on{{ else }}off{{ end }}; + http3_max_concurrent_streams {{ $cfg.HTTP3MaxConcurrentStreams }}; + http3_stream_buffer_size {{ $cfg.HTTP3StreamBufferSize }}; + quic_active_connection_id_limit {{ $cfg.QUICActiveConnectionIDLimit }}; + quic_gso {{ if $cfg.QUICGSO }}on{{ else }}off{{ end }}; + quic_retry {{ if $cfg.QUICRetry }}on{{ else }}off{{ end }}; + {{ end }} + types_hash_max_size 2048; server_names_hash_max_size {{ $cfg.ServerNameHashMaxSize }}; server_names_hash_bucket_size {{ $cfg.ServerNameHashBucketSize }}; diff --git a/test/e2e-image/namespace-overlays/admission/values.yaml b/test/e2e-image/namespace-overlays/admission/values.yaml index d423217dbb..47955b5a80 100644 --- a/test/e2e-image/namespace-overlays/admission/values.yaml +++ b/test/e2e-image/namespace-overlays/admission/values.yaml @@ -14,6 +14,7 @@ controller: extraArgs: http-port: "1080" https-port: "1443" + quic-port: "18443" # e2e tests do not require information about ingress status update-status: "false" diff --git a/test/e2e-image/namespace-overlays/forwarded-port-headers/values.yaml b/test/e2e-image/namespace-overlays/forwarded-port-headers/values.yaml index 68b4a074ce..696d732df6 100644 --- a/test/e2e-image/namespace-overlays/forwarded-port-headers/values.yaml +++ b/test/e2e-image/namespace-overlays/forwarded-port-headers/values.yaml @@ -14,6 +14,7 @@ controller: extraArgs: http-port: "1080" https-port: "1443" + quic-port: "18443" # e2e tests do not require information about ingress status update-status: "false" ingressClassResource: diff --git a/test/e2e-image/namespace-overlays/namespace-selector/values.yaml b/test/e2e-image/namespace-overlays/namespace-selector/values.yaml index 2c8957f663..55ec3ed6d9 100644 --- a/test/e2e-image/namespace-overlays/namespace-selector/values.yaml +++ b/test/e2e-image/namespace-overlays/namespace-selector/values.yaml @@ -14,6 +14,7 @@ controller: extraArgs: http-port: "1080" https-port: "1443" + quic-port: "18443" # e2e tests do not require information about ingress status update-status: "false" ingressClassResource: diff --git a/test/e2e-image/namespace-overlays/validations/values.yaml b/test/e2e-image/namespace-overlays/validations/values.yaml index d423217dbb..47955b5a80 100644 --- a/test/e2e-image/namespace-overlays/validations/values.yaml +++ b/test/e2e-image/namespace-overlays/validations/values.yaml @@ -14,6 +14,7 @@ controller: extraArgs: http-port: "1080" https-port: "1443" + quic-port: "18443" # e2e tests do not require information about ingress status update-status: "false" diff --git a/test/e2e/settings/http3.go b/test/e2e/settings/http3.go new file mode 100644 index 0000000000..48aff729f0 --- /dev/null +++ b/test/e2e/settings/http3.go @@ -0,0 +1,178 @@ +/* +Copyright 2025 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 settings + +import ( + "context" + "strings" + + "github.com/onsi/ginkgo/v2" + "github.com/stretchr/testify/assert" + appsv1 "k8s.io/api/apps/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "k8s.io/ingress-nginx/test/e2e/framework" +) + +var _ = framework.DescribeSetting("http3", func() { + f := framework.NewDefaultFramework("http3") + host := "http3.com" + + ginkgo.BeforeEach(func() { + err := f.UpdateIngressControllerDeployment(func(deployment *appsv1.Deployment) error { + args := deployment.Spec.Template.Spec.Containers[0].Args + args = append(args, "--enable-quic") + deployment.Spec.Template.Spec.Containers[0].Args = args + _, err := f.KubeClientSet.AppsV1().Deployments(f.Namespace).Update(context.TODO(), deployment, metav1.UpdateOptions{}) + return err + }) + assert.Nil(ginkgo.GinkgoT(), err, "updating ingress controller deployment flags") + annotations := map[string]string{} + + ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations) + f.EnsureIngress(ing) + + f.WaitForNginxServer("_", + func(server string) bool { + return strings.Contains(server, "listen 443 default_server reuseport quic;") + }) + + f.WaitForNginxServer(host, + func(server string) bool { + return strings.Contains(server, "listen 443 quic;") + }) + }) + + ginkgo.It("should enable HTTP/3 on a custom port", func() { + err := f.UpdateIngressControllerDeployment(func(deployment *appsv1.Deployment) error { + args := deployment.Spec.Template.Spec.Containers[0].Args + args = append(args, "--quic-port=4321") + deployment.Spec.Template.Spec.Containers[0].Args = args + _, err := f.KubeClientSet.AppsV1().Deployments(f.Namespace).Update(context.TODO(), deployment, metav1.UpdateOptions{}) + return err + }) + assert.Nil(ginkgo.GinkgoT(), err, "updating ingress controller deployment flags") + + f.WaitForNginxServer("_", + func(server string) bool { + return strings.Contains(server, "listen 4321 default_server reuseport quic;") + }) + + f.WaitForNginxServer(host, + func(server string) bool { + return strings.Contains(server, "listen 4321 quic;") + }) + }) + + ginkgo.It("should have default http3_hq value", func() { + f.WaitForNginxConfiguration(func(cfg string) bool { + return strings.Contains(cfg, "http3_hq off;") + }) + }) + + ginkgo.It("should set http3_hq value", func() { + f.UpdateNginxConfigMapData("http3-hq", "true") + + f.WaitForNginxConfiguration(func(cfg string) bool { + return strings.Contains(cfg, "http3_hq on;") + }) + }) + + ginkgo.It("should have default http3_max_concurrent_streams value", func() { + f.WaitForNginxConfiguration(func(cfg string) bool { + return strings.Contains(cfg, "http3_max_concurrent_streams 128;") + }) + }) + + ginkgo.It("should set http3_max_concurrent_streams value", func() { + f.UpdateNginxConfigMapData("http3-max-concurrent-streams", "256") + + f.WaitForNginxConfiguration(func(cfg string) bool { + return strings.Contains(cfg, "http3_max_concurrent_streams 256;") + }) + }) + + ginkgo.It("should have default http3_stream_buffer_size value", func() { + f.WaitForNginxConfiguration(func(cfg string) bool { + return strings.Contains(cfg, "http3_stream_buffer_size 64k;") + }) + }) + + ginkgo.It("should set http3_stream_buffer_size value", func() { + f.UpdateNginxConfigMapData("http3-stream-buffer-size", "128k") + + f.WaitForNginxConfiguration(func(cfg string) bool { + return strings.Contains(cfg, "http3_stream_buffer_size 128k;") + }) + }) + + ginkgo.It("should have default quic_active_connection_id_limit value", func() { + f.WaitForNginxConfiguration(func(cfg string) bool { + return strings.Contains(cfg, "quic_active_connection_id_limit 2;") + }) + }) + + ginkgo.It("should set quic_active_connection_id_limit value", func() { + f.UpdateNginxConfigMapData("quic-active-connection-id-limit", "16") + + f.WaitForNginxConfiguration(func(cfg string) bool { + return strings.Contains(cfg, "quic_active_connection_id_limit 16;") + }) + }) + + ginkgo.It("should have default quic_bpf value", func() { + f.WaitForNginxConfiguration(func(cfg string) bool { + return strings.Contains(cfg, "quic_bpf off;") + }) + }) + + ginkgo.It("should set quic_bpf value", func() { + f.UpdateNginxConfigMapData("quic-bpf", "true") + + f.WaitForNginxConfiguration(func(cfg string) bool { + return strings.Contains(cfg, "quic_bpf on;") + }) + }) + + ginkgo.It("should have default quic_gso value", func() { + f.WaitForNginxConfiguration(func(cfg string) bool { + return strings.Contains(cfg, "quic_gso off;") + }) + }) + + ginkgo.It("should set quic_gso value", func() { + f.UpdateNginxConfigMapData("quic-gso", "true") + + f.WaitForNginxConfiguration(func(cfg string) bool { + return strings.Contains(cfg, "quic_gso on;") + }) + }) + + ginkgo.It("should have default quic_retry value", func() { + f.WaitForNginxConfiguration(func(cfg string) bool { + return strings.Contains(cfg, "quic_retry off;") + }) + }) + + ginkgo.It("should set quic_retry value", func() { + f.UpdateNginxConfigMapData("quic-retry", "true") + + f.WaitForNginxConfiguration(func(cfg string) bool { + return strings.Contains(cfg, "quic_retry on;") + }) + }) +})