From a6da2b147279212f71a7f70ab95c372c77b72ccd Mon Sep 17 00:00:00 2001 From: Laszlo Janosi Date: Mon, 11 Jun 2018 11:25:18 +0000 Subject: [PATCH] K8s SCTP support implementation for the first pull request The requested Service Protocol is checked against the supported protocols of GCE Internal LB. The supported protocols are TCP and UDP. SCTP is not supported by OpenStack LBaaS. If SCTP is requested in a Service with type=LoadBalancer, the request is rejected. Comment style is also corrected. SCTP is not allowed for LoadBalancer Service and for HostPort. Kube-proxy can be configured not to start listening on the host port for SCTP: see the new SCTPUserSpaceNode parameter changed the vendor github.com/nokia/sctp to github.com/ishidawataru/sctp. I.e. from now on we use the upstream version. netexec.go compilation fixed. Various test cases fixed SCTP related conformance tests removed. Netexec's pod definition and Dockerfile are updated to expose the new SCTP port(8082) SCTP related e2e test cases are removed as the e2e test systems do not support SCTP sctp related firewall config is removed from cluster/gce/util.sh. Variable name sctp_addr is corrected to sctpAddr in pkg/proxy/ipvs/proxier.go cluster/gce/util.sh is copied from master --- api/openapi-spec/swagger.json | 10 +- api/swagger-spec/apps_v1.json | 2 +- api/swagger-spec/apps_v1beta1.json | 2 +- api/swagger-spec/apps_v1beta2.json | 2 +- api/swagger-spec/batch_v1.json | 2 +- api/swagger-spec/batch_v1beta1.json | 2 +- api/swagger-spec/batch_v2alpha1.json | 2 +- api/swagger-spec/extensions_v1beta1.json | 4 +- api/swagger-spec/networking.k8s.io_v1.json | 2 +- api/swagger-spec/v1.json | 6 +- cluster/gce/gci/configure-helper.sh | 6 +- cmd/kube-proxy/app/server.go | 2 +- cmd/kube-proxy/app/server_others.go | 2 + cmd/kube-proxy/app/server_test.go | 2 + .../testdata/conversion/master/internal.yaml | 1 + .../testdata/conversion/master/v1alpha2.yaml | 1 + .../testdata/conversion/master/v1alpha3.yaml | 1 + .../testdata/defaulting/master/defaulted.yaml | 1 + docs/api-reference/apps/v1/definitions.html | 2 +- .../apps/v1beta1/definitions.html | 2 +- .../apps/v1beta2/definitions.html | 2 +- docs/api-reference/batch/v1/definitions.html | 2 +- .../batch/v1beta1/definitions.html | 2 +- .../batch/v2alpha1/definitions.html | 2 +- .../extensions/v1beta1/definitions.html | 4 +- .../networking.k8s.io/v1/definitions.html | 2 +- docs/api-reference/v1/definitions.html | 6 +- pkg/apis/core/fuzzer/fuzzer.go | 2 +- pkg/apis/core/types.go | 6 +- pkg/apis/core/validation/validation.go | 23 +- pkg/apis/core/validation/validation_test.go | 6 +- pkg/apis/networking/types.go | 2 +- pkg/apis/networking/validation/validation.go | 4 +- .../networking/validation/validation_test.go | 9 + .../providers/azure/azure_loadbalancer.go | 13 +- .../gce/gce_loadbalancer_internal.go | 3 + .../providers/gce/gce_util_test.go | 6 +- .../endpoint/endpoints_controller_test.go | 41 ++ .../service/service_controller_test.go | 18 + pkg/kubectl/cmd/expose.go | 2 +- pkg/kubectl/cmd/expose_test.go | 137 +++++++ .../testcase-create-list-error/3.response | 4 +- .../edit/testcase-list-errors/3.response | 4 +- .../edit/testcase-list-errors/5.edited | 2 +- .../edit/testcase-list-errors/5.original | 2 +- .../edit/testcase-list-errors/6.response | 4 +- .../edit/testcase-list-errors/8.edited | 2 +- .../edit/testcase-list-errors/8.original | 2 +- pkg/kubectl/generate_test.go | 2 + pkg/kubectl/service_test.go | 349 ++++++++++++++++++ .../apis/cri/runtime/v1alpha2/api.pb.go | 11 +- .../apis/cri/runtime/v1alpha2/api.proto | 1 + pkg/kubelet/dockershim/docker_checkpoint.go | 1 + pkg/kubelet/dockershim/docker_sandbox.go | 2 + pkg/kubelet/dockershim/docker_service.go | 2 + pkg/kubelet/dockershim/helpers.go | 2 + .../network/hostport/hostport_manager_test.go | 14 + pkg/kubelet/envvars/envvars_test.go | 17 + pkg/kubelet/kuberuntime/helpers.go | 2 + pkg/kubemark/hollow_proxy.go | 1 + pkg/master/controller_test.go | 14 + pkg/printers/internalversion/printers_test.go | 4 + pkg/proxy/apis/kubeproxyconfig/types.go | 6 + .../apis/kubeproxyconfig/v1alpha1/types.go | 6 + .../v1alpha1/zz_generated.conversion.go | 2 + pkg/proxy/iptables/proxier.go | 29 +- pkg/proxy/iptables/proxier_test.go | 28 +- pkg/proxy/ipvs/ipset.go | 6 + pkg/proxy/ipvs/ipset_test.go | 20 + pkg/proxy/ipvs/proxier.go | 30 +- pkg/proxy/ipvs/proxier_test.go | 93 ++++- pkg/proxy/service_test.go | 9 + pkg/proxy/userspace/proxysocket.go | 2 + pkg/proxy/util/port_test.go | 2 + pkg/proxy/winkernel/proxier.go | 3 + pkg/proxy/winuserspace/proxysocket.go | 2 + pkg/util/ipset/ipset.go | 6 +- pkg/util/ipset/ipset_test.go | 30 +- pkg/util/ipset/types.go | 2 + pkg/util/ipvs/ipvs_linux.go | 4 + pkg/util/ipvs/ipvs_linux_test.go | 32 +- pkg/util/ipvs/ipvs_test.go | 40 ++ pkg/util/ipvs/testing/fake_test.go | 12 +- .../src/k8s.io/api/core/v1/generated.proto | 6 +- staging/src/k8s.io/api/core/v1/types.go | 8 +- .../core/v1/types_swagger_doc_generated.go | 6 +- .../api/extensions/v1beta1/generated.proto | 2 +- .../k8s.io/api/extensions/v1beta1/types.go | 2 +- .../v1beta1/types_swagger_doc_generated.go | 2 +- .../k8s.io/api/networking/v1/generated.proto | 2 +- staging/src/k8s.io/api/networking/v1/types.go | 2 +- .../v1/types_swagger_doc_generated.go | 2 +- 92 files changed, 1076 insertions(+), 108 deletions(-) diff --git a/api/openapi-spec/swagger.json b/api/openapi-spec/swagger.json index 5bd24a9b834..48ab6e57f3b 100644 --- a/api/openapi-spec/swagger.json +++ b/api/openapi-spec/swagger.json @@ -79022,7 +79022,7 @@ "type": "string" }, "protocol": { - "description": "Protocol for port. Must be UDP or TCP. Defaults to \"TCP\".", + "description": "Protocol for port. Must be UDP, TCP, or SCTP. Defaults to \"TCP\".", "type": "string" } } @@ -79269,7 +79269,7 @@ "format": "int32" }, "protocol": { - "description": "The IP protocol for this port. Must be UDP or TCP. Default is TCP.", + "description": "The IP protocol for this port. Must be UDP, TCP, or SCTP. Default is TCP.", "type": "string" } } @@ -82758,7 +82758,7 @@ "format": "int32" }, "protocol": { - "description": "The IP protocol for this port. Supports \"TCP\" and \"UDP\". Default is TCP.", + "description": "The IP protocol for this port. Supports \"TCP\", \"UDP\", and \"SCTP\". Default is TCP.", "type": "string" }, "targetPort": { @@ -84254,7 +84254,7 @@ "$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString" }, "protocol": { - "description": "Optional. The protocol (TCP or UDP) which traffic must match. If not specified, this field defaults to TCP.", + "description": "Optional. The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.", "type": "string" } } @@ -84925,7 +84925,7 @@ "$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString" }, "protocol": { - "description": "The protocol (TCP or UDP) which traffic must match. If not specified, this field defaults to TCP.", + "description": "The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.", "type": "string" } } diff --git a/api/swagger-spec/apps_v1.json b/api/swagger-spec/apps_v1.json index 4f3699ef7b4..802dbf0e9e2 100644 --- a/api/swagger-spec/apps_v1.json +++ b/api/swagger-spec/apps_v1.json @@ -8018,7 +8018,7 @@ }, "protocol": { "type": "string", - "description": "Protocol for port. Must be UDP or TCP. Defaults to \"TCP\"." + "description": "Protocol for port. Must be UDP, TCP, or SCTP. Defaults to \"TCP\"." }, "hostIP": { "type": "string", diff --git a/api/swagger-spec/apps_v1beta1.json b/api/swagger-spec/apps_v1beta1.json index 60f03d42332..8ab8e0fb47b 100644 --- a/api/swagger-spec/apps_v1beta1.json +++ b/api/swagger-spec/apps_v1beta1.json @@ -5626,7 +5626,7 @@ }, "protocol": { "type": "string", - "description": "Protocol for port. Must be UDP or TCP. Defaults to \"TCP\"." + "description": "Protocol for port. Must be UDP, TCP, or SCTP. Defaults to \"TCP\"." }, "hostIP": { "type": "string", diff --git a/api/swagger-spec/apps_v1beta2.json b/api/swagger-spec/apps_v1beta2.json index 963cda983e0..4ab9729312e 100644 --- a/api/swagger-spec/apps_v1beta2.json +++ b/api/swagger-spec/apps_v1beta2.json @@ -8018,7 +8018,7 @@ }, "protocol": { "type": "string", - "description": "Protocol for port. Must be UDP or TCP. Defaults to \"TCP\"." + "description": "Protocol for port. Must be UDP, TCP, or SCTP. Defaults to \"TCP\"." }, "hostIP": { "type": "string", diff --git a/api/swagger-spec/batch_v1.json b/api/swagger-spec/batch_v1.json index 3ad07b21fc0..f431fe6d0e7 100644 --- a/api/swagger-spec/batch_v1.json +++ b/api/swagger-spec/batch_v1.json @@ -2933,7 +2933,7 @@ }, "protocol": { "type": "string", - "description": "Protocol for port. Must be UDP or TCP. Defaults to \"TCP\"." + "description": "Protocol for port. Must be UDP, TCP, or SCTP. Defaults to \"TCP\"." }, "hostIP": { "type": "string", diff --git a/api/swagger-spec/batch_v1beta1.json b/api/swagger-spec/batch_v1beta1.json index 35d56151344..396bdf3505a 100644 --- a/api/swagger-spec/batch_v1beta1.json +++ b/api/swagger-spec/batch_v1beta1.json @@ -2988,7 +2988,7 @@ }, "protocol": { "type": "string", - "description": "Protocol for port. Must be UDP or TCP. Defaults to \"TCP\"." + "description": "Protocol for port. Must be UDP, TCP, or SCTP. Defaults to \"TCP\"." }, "hostIP": { "type": "string", diff --git a/api/swagger-spec/batch_v2alpha1.json b/api/swagger-spec/batch_v2alpha1.json index 74577265653..2859b0b3e33 100644 --- a/api/swagger-spec/batch_v2alpha1.json +++ b/api/swagger-spec/batch_v2alpha1.json @@ -2988,7 +2988,7 @@ }, "protocol": { "type": "string", - "description": "Protocol for port. Must be UDP or TCP. Defaults to \"TCP\"." + "description": "Protocol for port. Must be UDP, TCP, or SCTP. Defaults to \"TCP\"." }, "hostIP": { "type": "string", diff --git a/api/swagger-spec/extensions_v1beta1.json b/api/swagger-spec/extensions_v1beta1.json index dc263d21de2..0497ac5382a 100644 --- a/api/swagger-spec/extensions_v1beta1.json +++ b/api/swagger-spec/extensions_v1beta1.json @@ -8666,7 +8666,7 @@ }, "protocol": { "type": "string", - "description": "Protocol for port. Must be UDP or TCP. Defaults to \"TCP\"." + "description": "Protocol for port. Must be UDP, TCP, or SCTP. Defaults to \"TCP\"." }, "hostIP": { "type": "string", @@ -10280,7 +10280,7 @@ "properties": { "protocol": { "$ref": "v1.Protocol", - "description": "Optional. The protocol (TCP or UDP) which traffic must match. If not specified, this field defaults to TCP." + "description": "Optional. The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP." }, "port": { "type": "string", diff --git a/api/swagger-spec/networking.k8s.io_v1.json b/api/swagger-spec/networking.k8s.io_v1.json index be43b96b7ce..65f326637fd 100644 --- a/api/swagger-spec/networking.k8s.io_v1.json +++ b/api/swagger-spec/networking.k8s.io_v1.json @@ -1426,7 +1426,7 @@ "properties": { "protocol": { "$ref": "v1.Protocol", - "description": "The protocol (TCP or UDP) which traffic must match. If not specified, this field defaults to TCP." + "description": "The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP." }, "port": { "type": "string", diff --git a/api/swagger-spec/v1.json b/api/swagger-spec/v1.json index 4873b0841a5..d3bf5da50a1 100644 --- a/api/swagger-spec/v1.json +++ b/api/swagger-spec/v1.json @@ -18480,7 +18480,7 @@ }, "protocol": { "type": "string", - "description": "The IP protocol for this port. Must be UDP or TCP. Default is TCP." + "description": "The IP protocol for this port. Must be UDP, TCP, or SCTP. Default is TCP." } } }, @@ -21466,7 +21466,7 @@ }, "protocol": { "type": "string", - "description": "Protocol for port. Must be UDP or TCP. Defaults to \"TCP\"." + "description": "Protocol for port. Must be UDP, TCP, or SCTP. Defaults to \"TCP\"." }, "hostIP": { "type": "string", @@ -23091,7 +23091,7 @@ }, "protocol": { "type": "string", - "description": "The IP protocol for this port. Supports \"TCP\" and \"UDP\". Default is TCP." + "description": "The IP protocol for this port. Supports \"TCP\", \"UDP\", and \"SCTP\". Default is TCP." }, "port": { "type": "integer", diff --git a/cluster/gce/gci/configure-helper.sh b/cluster/gce/gci/configure-helper.sh index dc00dcafb6a..b6b5205c12e 100644 --- a/cluster/gce/gci/configure-helper.sh +++ b/cluster/gce/gci/configure-helper.sh @@ -51,18 +51,20 @@ function config-ip-firewall { sysctl -w net.ipv4.conf.all.route_localnet=1 # The GCI image has host firewall which drop most inbound/forwarded packets. - # We need to add rules to accept all TCP/UDP/ICMP packets. + # We need to add rules to accept all TCP/UDP/ICMP/SCTP packets. if iptables -w -L INPUT | grep "Chain INPUT (policy DROP)" > /dev/null; then echo "Add rules to accept all inbound TCP/UDP/ICMP packets" iptables -A INPUT -w -p TCP -j ACCEPT iptables -A INPUT -w -p UDP -j ACCEPT iptables -A INPUT -w -p ICMP -j ACCEPT + iptables -A INPUT -w -p SCTP -j ACCEPT fi if iptables -w -L FORWARD | grep "Chain FORWARD (policy DROP)" > /dev/null; then - echo "Add rules to accept all forwarded TCP/UDP/ICMP packets" + echo "Add rules to accept all forwarded TCP/UDP/ICMP/SCTP packets" iptables -A FORWARD -w -p TCP -j ACCEPT iptables -A FORWARD -w -p UDP -j ACCEPT iptables -A FORWARD -w -p ICMP -j ACCEPT + iptables -A FORWARD -w -p SCTP -j ACCEPT fi # Flush iptables nat table diff --git a/cmd/kube-proxy/app/server.go b/cmd/kube-proxy/app/server.go index e25d5fac04f..e9cca07d8da 100644 --- a/cmd/kube-proxy/app/server.go +++ b/cmd/kube-proxy/app/server.go @@ -338,7 +338,7 @@ func NewProxyCommand() *cobra.Command { Use: "kube-proxy", Long: `The Kubernetes network proxy runs on each node. This reflects services as defined in the Kubernetes API on each node and can do simple -TCP and UDP stream forwarding or round robin TCP and UDP forwarding across a set of backends. +TCP, UDP, and SCTP stream forwarding or round robin TCP, UDP, and SCTP forwarding across a set of backends. Service cluster IPs and ports are currently found through Docker-links-compatible environment variables specifying ports opened by the service proxy. There is an optional addon that provides cluster DNS for these cluster IPs. The user must create a service diff --git a/cmd/kube-proxy/app/server_others.go b/cmd/kube-proxy/app/server_others.go index a9460bcfab5..e31a301b71b 100644 --- a/cmd/kube-proxy/app/server_others.go +++ b/cmd/kube-proxy/app/server_others.go @@ -166,6 +166,7 @@ func newProxyServer( recorder, healthzUpdater, config.NodePortAddresses, + config.SCTPUserSpaceNode, ) if err != nil { return nil, fmt.Errorf("unable to create proxier: %v", err) @@ -205,6 +206,7 @@ func newProxyServer( healthzServer, config.IPVS.Scheduler, config.NodePortAddresses, + config.SCTPUserSpaceNode, ) if err != nil { return nil, fmt.Errorf("unable to create proxier: %v", err) diff --git a/cmd/kube-proxy/app/server_test.go b/cmd/kube-proxy/app/server_test.go index f3e8c75b0b5..5e95f3d5976 100644 --- a/cmd/kube-proxy/app/server_test.go +++ b/cmd/kube-proxy/app/server_test.go @@ -211,6 +211,7 @@ udpIdleTimeout: 123ms nodePortAddresses: - "10.20.30.40/16" - "fd00:1::0/64" +sctpUserSpaceNode: false ` testCases := []struct { @@ -325,6 +326,7 @@ nodePortAddresses: ResourceContainer: "/foo", UDPIdleTimeout: metav1.Duration{Duration: 123 * time.Millisecond}, NodePortAddresses: []string{"10.20.30.40/16", "fd00:1::0/64"}, + SCTPUserSpaceNode: false, } options := NewOptions() diff --git a/cmd/kubeadm/app/util/config/testdata/conversion/master/internal.yaml b/cmd/kubeadm/app/util/config/testdata/conversion/master/internal.yaml index bef9a604e35..75e3b4b1f5a 100644 --- a/cmd/kubeadm/app/util/config/testdata/conversion/master/internal.yaml +++ b/cmd/kubeadm/app/util/config/testdata/conversion/master/internal.yaml @@ -61,6 +61,7 @@ ComponentConfigs: OOMScoreAdj: -999 PortRange: "" ResourceContainer: /kube-proxy + SCTPUserSpaceNode: false UDPIdleTimeout: 250ms Kubelet: Address: 1.2.3.4 diff --git a/cmd/kubeadm/app/util/config/testdata/conversion/master/v1alpha2.yaml b/cmd/kubeadm/app/util/config/testdata/conversion/master/v1alpha2.yaml index 06fd21a2ce1..6fd6655ecd7 100644 --- a/cmd/kubeadm/app/util/config/testdata/conversion/master/v1alpha2.yaml +++ b/cmd/kubeadm/app/util/config/testdata/conversion/master/v1alpha2.yaml @@ -64,6 +64,7 @@ kubeProxy: oomScoreAdj: -999 portRange: "" resourceContainer: /kube-proxy + sctpUserSpaceNode: false udpIdleTimeout: 250ms kubeletConfiguration: baseConfig: diff --git a/cmd/kubeadm/app/util/config/testdata/conversion/master/v1alpha3.yaml b/cmd/kubeadm/app/util/config/testdata/conversion/master/v1alpha3.yaml index bf358a3565b..0a31490da23 100644 --- a/cmd/kubeadm/app/util/config/testdata/conversion/master/v1alpha3.yaml +++ b/cmd/kubeadm/app/util/config/testdata/conversion/master/v1alpha3.yaml @@ -80,6 +80,7 @@ nodePortAddresses: null oomScoreAdj: -999 portRange: "" resourceContainer: /kube-proxy +sctpUserSpaceNode: false udpIdleTimeout: 250ms --- address: 1.2.3.4 diff --git a/cmd/kubeadm/app/util/config/testdata/defaulting/master/defaulted.yaml b/cmd/kubeadm/app/util/config/testdata/defaulting/master/defaulted.yaml index b2cf29c0001..46f045a194b 100644 --- a/cmd/kubeadm/app/util/config/testdata/defaulting/master/defaulted.yaml +++ b/cmd/kubeadm/app/util/config/testdata/defaulting/master/defaulted.yaml @@ -75,6 +75,7 @@ nodePortAddresses: null oomScoreAdj: -999 portRange: "" resourceContainer: /kube-proxy +sctpUserSpaceNode: false udpIdleTimeout: 250ms --- address: 0.0.0.0 diff --git a/docs/api-reference/apps/v1/definitions.html b/docs/api-reference/apps/v1/definitions.html index 7f8660199ae..be4d20f8512 100755 --- a/docs/api-reference/apps/v1/definitions.html +++ b/docs/api-reference/apps/v1/definitions.html @@ -4085,7 +4085,7 @@ When an object is created, the system will populate this list with the current s

protocol

-

Protocol for port. Must be UDP or TCP. Defaults to "TCP".

+

Protocol for port. Must be UDP, TCP, or SCTP. Defaults to "TCP".

false

string

diff --git a/docs/api-reference/apps/v1beta1/definitions.html b/docs/api-reference/apps/v1beta1/definitions.html index aa421060d4e..e05a98e520a 100755 --- a/docs/api-reference/apps/v1beta1/definitions.html +++ b/docs/api-reference/apps/v1beta1/definitions.html @@ -4138,7 +4138,7 @@ The StatefulSet guarantees that a given network identity will always map to the

protocol

-

Protocol for port. Must be UDP or TCP. Defaults to "TCP".

+

Protocol for port. Must be UDP, TCP, or SCTP. Defaults to "TCP".

false

string

diff --git a/docs/api-reference/apps/v1beta2/definitions.html b/docs/api-reference/apps/v1beta2/definitions.html index a38472bd9c6..0e2dbd54723 100755 --- a/docs/api-reference/apps/v1beta2/definitions.html +++ b/docs/api-reference/apps/v1beta2/definitions.html @@ -4754,7 +4754,7 @@ The StatefulSet guarantees that a given network identity will always map to the

protocol

-

Protocol for port. Must be UDP or TCP. Defaults to "TCP".

+

Protocol for port. Must be UDP, TCP, or SCTP. Defaults to "TCP".

false

string

diff --git a/docs/api-reference/batch/v1/definitions.html b/docs/api-reference/batch/v1/definitions.html index ba884857ef3..607e8a2cee0 100755 --- a/docs/api-reference/batch/v1/definitions.html +++ b/docs/api-reference/batch/v1/definitions.html @@ -3418,7 +3418,7 @@ When an object is created, the system will populate this list with the current s

protocol

-

Protocol for port. Must be UDP or TCP. Defaults to "TCP".

+

Protocol for port. Must be UDP, TCP, or SCTP. Defaults to "TCP".

false

string

diff --git a/docs/api-reference/batch/v1beta1/definitions.html b/docs/api-reference/batch/v1beta1/definitions.html index b597490ce59..7769cfc01bc 100755 --- a/docs/api-reference/batch/v1beta1/definitions.html +++ b/docs/api-reference/batch/v1beta1/definitions.html @@ -3452,7 +3452,7 @@ When an object is created, the system will populate this list with the current s

protocol

-

Protocol for port. Must be UDP or TCP. Defaults to "TCP".

+

Protocol for port. Must be UDP, TCP, or SCTP. Defaults to "TCP".

false

string

diff --git a/docs/api-reference/batch/v2alpha1/definitions.html b/docs/api-reference/batch/v2alpha1/definitions.html index 14e34837f93..c2332d3f0f8 100755 --- a/docs/api-reference/batch/v2alpha1/definitions.html +++ b/docs/api-reference/batch/v2alpha1/definitions.html @@ -3425,7 +3425,7 @@ When an object is created, the system will populate this list with the current s

protocol

-

Protocol for port. Must be UDP or TCP. Defaults to "TCP".

+

Protocol for port. Must be UDP, TCP, or SCTP. Defaults to "TCP".

false

string

diff --git a/docs/api-reference/extensions/v1beta1/definitions.html b/docs/api-reference/extensions/v1beta1/definitions.html index 252677d9bf6..26b93b33887 100755 --- a/docs/api-reference/extensions/v1beta1/definitions.html +++ b/docs/api-reference/extensions/v1beta1/definitions.html @@ -4784,7 +4784,7 @@ When an object is created, the system will populate this list with the current s

protocol

-

Protocol for port. Must be UDP or TCP. Defaults to "TCP".

+

Protocol for port. Must be UDP, TCP, or SCTP. Defaults to "TCP".

false

string

@@ -8058,7 +8058,7 @@ If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Po

protocol

-

Optional. The protocol (TCP or UDP) which traffic must match. If not specified, this field defaults to TCP.

+

Optional. The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.

false

v1.Protocol

diff --git a/docs/api-reference/networking.k8s.io/v1/definitions.html b/docs/api-reference/networking.k8s.io/v1/definitions.html index c8a84f0b388..f01c8b0cb8d 100755 --- a/docs/api-reference/networking.k8s.io/v1/definitions.html +++ b/docs/api-reference/networking.k8s.io/v1/definitions.html @@ -676,7 +676,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

protocol

-

The protocol (TCP or UDP) which traffic must match. If not specified, this field defaults to TCP.

+

The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.

false

v1.Protocol

diff --git a/docs/api-reference/v1/definitions.html b/docs/api-reference/v1/definitions.html index 800d0f02026..c47c47d5218 100755 --- a/docs/api-reference/v1/definitions.html +++ b/docs/api-reference/v1/definitions.html @@ -2780,7 +2780,7 @@ The resulting set of endpoints can be viewed as:

protocol

-

Protocol for port. Must be UDP or TCP. Defaults to "TCP".

+

Protocol for port. Must be UDP, TCP, or SCTP. Defaults to "TCP".

false

string

@@ -3770,7 +3770,7 @@ Examples:

protocol

-

The IP protocol for this port. Must be UDP or TCP. Default is TCP.

+

The IP protocol for this port. Must be UDP, TCP, or SCTP. Default is TCP.

false

string

@@ -4405,7 +4405,7 @@ Examples:

protocol

-

The IP protocol for this port. Supports "TCP" and "UDP". Default is TCP.

+

The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". Default is TCP.

false

string

diff --git a/pkg/apis/core/fuzzer/fuzzer.go b/pkg/apis/core/fuzzer/fuzzer.go index dede9ceb690..7a2c9b7daf9 100644 --- a/pkg/apis/core/fuzzer/fuzzer.go +++ b/pkg/apis/core/fuzzer/fuzzer.go @@ -263,7 +263,7 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} { *d = policies[c.Rand.Intn(len(policies))] }, func(p *core.Protocol, c fuzz.Continue) { - protocols := []core.Protocol{core.ProtocolTCP, core.ProtocolUDP} + protocols := []core.Protocol{core.ProtocolTCP, core.ProtocolUDP, core.ProtocolSCTP} *p = protocols[c.Rand.Intn(len(protocols))] }, func(p *core.ServiceAffinity, c fuzz.Continue) { diff --git a/pkg/apis/core/types.go b/pkg/apis/core/types.go index 15d7746e6da..1f5f9ee8fd1 100644 --- a/pkg/apis/core/types.go +++ b/pkg/apis/core/types.go @@ -564,6 +564,8 @@ const ( ProtocolTCP Protocol = "TCP" // ProtocolUDP is the UDP protocol. ProtocolUDP Protocol = "UDP" + // ProtocolSCTP is the SCTP protocol. + ProtocolSCTP Protocol = "SCTP" ) // Represents a Persistent Disk resource in Google Compute Engine. @@ -1570,7 +1572,7 @@ type ContainerPort struct { HostPort int32 // Required: This must be a valid port number, 0 < x < 65536. ContainerPort int32 - // Required: Supports "TCP" and "UDP". + // Required: Supports "TCP" and "UDP". "SCTP" is supported only if no HostPort is specified. // +optional Protocol Protocol // Optional: What host IP to bind the external port to. @@ -3175,7 +3177,7 @@ type ServicePort struct { // the 'Name' field in EndpointPort objects. Name string - // The IP protocol for this port. Supports "TCP" and "UDP". + // The IP protocol for this port. Supports "TCP", "UDP", and SCTP. Protocol Protocol // The port that will be exposed on the service. diff --git a/pkg/apis/core/validation/validation.go b/pkg/apis/core/validation/validation.go index 72919778229..e33919b68df 100644 --- a/pkg/apis/core/validation/validation.go +++ b/pkg/apis/core/validation/validation.go @@ -1918,7 +1918,10 @@ func ValidatePersistentVolumeClaimStatusUpdate(newPvc, oldPvc *core.PersistentVo return allErrs } -var supportedPortProtocols = sets.NewString(string(core.ProtocolTCP), string(core.ProtocolUDP)) +var supportedServicePortProtocols = sets.NewString(string(core.ProtocolTCP), string(core.ProtocolUDP), string(core.ProtocolSCTP)) +var supportedLoadBalancerProtocols = sets.NewString(string(core.ProtocolTCP), string(core.ProtocolUDP)) +var supportedContainerPortProtocols = sets.NewString(string(core.ProtocolTCP), string(core.ProtocolUDP), string(core.ProtocolSCTP)) +var supportedHostPortProtocols = sets.NewString(string(core.ProtocolTCP), string(core.ProtocolUDP)) func validateContainerPorts(ports []core.ContainerPort, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} @@ -1951,8 +1954,12 @@ func validateContainerPorts(ports []core.ContainerPort, fldPath *field.Path) fie } if len(port.Protocol) == 0 { allErrs = append(allErrs, field.Required(idxPath.Child("protocol"), "")) - } else if !supportedPortProtocols.Has(string(port.Protocol)) { - allErrs = append(allErrs, field.NotSupported(idxPath.Child("protocol"), port.Protocol, supportedPortProtocols.List())) + } else if port.HostPort != 0 { + if !supportedHostPortProtocols.Has(string(port.Protocol)) { + allErrs = append(allErrs, field.NotSupported(idxPath.Child("protocol"), port.Protocol, supportedHostPortProtocols.List())) + } + } else if !supportedContainerPortProtocols.Has(string(port.Protocol)) { + allErrs = append(allErrs, field.NotSupported(idxPath.Child("protocol"), port.Protocol, supportedContainerPortProtocols.List())) } } return allErrs @@ -3726,7 +3733,7 @@ func ValidateService(service *core.Service) field.ErrorList { includeProtocols := sets.NewString() for i := range service.Spec.Ports { portPath := portsPath.Index(i) - if !supportedPortProtocols.Has(string(service.Spec.Ports[i].Protocol)) { + if !supportedLoadBalancerProtocols.Has(string(service.Spec.Ports[i].Protocol)) { allErrs = append(allErrs, field.Invalid(portPath.Child("protocol"), service.Spec.Ports[i].Protocol, "cannot create an external load balancer with non-TCP/UDP ports")) } else { includeProtocols.Insert(string(service.Spec.Ports[i].Protocol)) @@ -3825,8 +3832,8 @@ func validateServicePort(sp *core.ServicePort, requireName, isHeadlessService bo if len(sp.Protocol) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("protocol"), "")) - } else if !supportedPortProtocols.Has(string(sp.Protocol)) { - allErrs = append(allErrs, field.NotSupported(fldPath.Child("protocol"), sp.Protocol, supportedPortProtocols.List())) + } else if !supportedServicePortProtocols.Has(string(sp.Protocol)) { + allErrs = append(allErrs, field.NotSupported(fldPath.Child("protocol"), sp.Protocol, supportedServicePortProtocols.List())) } allErrs = append(allErrs, ValidatePortNumOrName(sp.TargetPort, fldPath.Child("targetPort"))...) @@ -5194,8 +5201,8 @@ func validateEndpointPort(port *core.EndpointPort, requireName bool, fldPath *fi } if len(port.Protocol) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("protocol"), "")) - } else if !supportedPortProtocols.Has(string(port.Protocol)) { - allErrs = append(allErrs, field.NotSupported(fldPath.Child("protocol"), port.Protocol, supportedPortProtocols.List())) + } else if !supportedServicePortProtocols.Has(string(port.Protocol)) { + allErrs = append(allErrs, field.NotSupported(fldPath.Child("protocol"), port.Protocol, supportedServicePortProtocols.List())) } return allErrs } diff --git a/pkg/apis/core/validation/validation_test.go b/pkg/apis/core/validation/validation_test.go index 91c1c2a6249..a80161b297d 100644 --- a/pkg/apis/core/validation/validation_test.go +++ b/pkg/apis/core/validation/validation_test.go @@ -4125,12 +4125,12 @@ func TestValidatePorts(t *testing.T) { "invalid protocol case": { []core.ContainerPort{{ContainerPort: 80, Protocol: "tcp"}}, field.ErrorTypeNotSupported, - "protocol", `supported values: "TCP", "UDP"`, + "protocol", `supported values: "SCTP", "TCP", "UDP"`, }, "invalid protocol": { []core.ContainerPort{{ContainerPort: 80, Protocol: "ICMP"}}, field.ErrorTypeNotSupported, - "protocol", `supported values: "TCP", "UDP"`, + "protocol", `supported values: "SCTP", "TCP", "UDP"`, }, "protocol required": { []core.ContainerPort{{Name: "abc", ContainerPort: 80}}, @@ -8947,6 +8947,7 @@ func TestValidateService(t *testing.T) { s.Spec.Type = core.ServiceTypeNodePort s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt(1)}) s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 2, Protocol: "UDP", NodePort: 1, TargetPort: intstr.FromInt(2)}) + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "s", Port: 3, Protocol: "SCTP", NodePort: 1, TargetPort: intstr.FromInt(3)}) }, numErrs: 0, }, @@ -8965,6 +8966,7 @@ func TestValidateService(t *testing.T) { s.Spec.Type = core.ServiceTypeClusterIP s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "q", Port: 12345, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}) s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "r", Port: 12345, Protocol: "UDP", TargetPort: intstr.FromInt(80)}) + s.Spec.Ports = append(s.Spec.Ports, core.ServicePort{Name: "s", Port: 12345, Protocol: "SCTP", TargetPort: intstr.FromInt(8088)}) }, numErrs: 0, }, diff --git a/pkg/apis/networking/types.go b/pkg/apis/networking/types.go index b92e03295bc..541b884b58c 100644 --- a/pkg/apis/networking/types.go +++ b/pkg/apis/networking/types.go @@ -134,7 +134,7 @@ type NetworkPolicyEgressRule struct { // NetworkPolicyPort describes a port to allow traffic on type NetworkPolicyPort struct { - // The protocol (TCP or UDP) which traffic must match. If not specified, this + // The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this // field defaults to TCP. // +optional Protocol *api.Protocol diff --git a/pkg/apis/networking/validation/validation.go b/pkg/apis/networking/validation/validation.go index c64d085d603..0741f6ce7ff 100644 --- a/pkg/apis/networking/validation/validation.go +++ b/pkg/apis/networking/validation/validation.go @@ -38,8 +38,8 @@ func ValidateNetworkPolicyName(name string, prefix bool) []string { func ValidateNetworkPolicyPort(port *networking.NetworkPolicyPort, portPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} - if port.Protocol != nil && *port.Protocol != api.ProtocolTCP && *port.Protocol != api.ProtocolUDP { - allErrs = append(allErrs, field.NotSupported(portPath.Child("protocol"), *port.Protocol, []string{string(api.ProtocolTCP), string(api.ProtocolUDP)})) + if port.Protocol != nil && *port.Protocol != api.ProtocolTCP && *port.Protocol != api.ProtocolUDP && *port.Protocol != api.ProtocolSCTP { + allErrs = append(allErrs, field.NotSupported(portPath.Child("protocol"), *port.Protocol, []string{string(api.ProtocolTCP), string(api.ProtocolUDP), string(api.ProtocolSCTP)})) } if port.Port != nil { if port.Port.Type == intstr.Int { diff --git a/pkg/apis/networking/validation/validation_test.go b/pkg/apis/networking/validation/validation_test.go index 67ebc24f915..70dcb408743 100644 --- a/pkg/apis/networking/validation/validation_test.go +++ b/pkg/apis/networking/validation/validation_test.go @@ -29,6 +29,7 @@ func TestValidateNetworkPolicy(t *testing.T) { protocolTCP := api.ProtocolTCP protocolUDP := api.ProtocolUDP protocolICMP := api.Protocol("ICMP") + protocolSCTP := api.ProtocolSCTP successCases := []networking.NetworkPolicy{ { @@ -79,6 +80,10 @@ func TestValidateNetworkPolicy(t *testing.T) { Protocol: &protocolUDP, Port: &intstr.IntOrString{Type: intstr.String, StrVal: "dns"}, }, + { + Protocol: &protocolSCTP, + Port: &intstr.IntOrString{Type: intstr.Int, IntVal: 7777}, + }, }, }, }, @@ -262,6 +267,10 @@ func TestValidateNetworkPolicy(t *testing.T) { Protocol: &protocolUDP, Port: &intstr.IntOrString{Type: intstr.String, StrVal: "dns"}, }, + { + Protocol: &protocolSCTP, + Port: &intstr.IntOrString{Type: intstr.Int, IntVal: 7777}, + }, }, }, }, diff --git a/pkg/cloudprovider/providers/azure/azure_loadbalancer.go b/pkg/cloudprovider/providers/azure/azure_loadbalancer.go index ffe66a32bc6..18434371b59 100644 --- a/pkg/cloudprovider/providers/azure/azure_loadbalancer.go +++ b/pkg/cloudprovider/providers/azure/azure_loadbalancer.go @@ -736,6 +736,13 @@ func (az *Cloud) reconcileLoadBalancer(clusterName string, service *v1.Service, // compatible with UDP (it uses an HTTP check) return nil, fmt.Errorf("services requiring health checks are incompatible with UDP ports") } + + if port.Protocol == v1.ProtocolSCTP { + // ERROR: this isn't supported + // health check (aka source ip preservation) is not + // compatible with SCTP (it uses an HTTP check) + return nil, fmt.Errorf("services requiring health checks are incompatible with SCTP ports") + } podPresencePath, podPresencePort := serviceapi.GetServiceHealthCheckPathPort(service) @@ -749,7 +756,7 @@ func (az *Cloud) reconcileLoadBalancer(clusterName string, service *v1.Service, NumberOfProbes: to.Int32Ptr(2), }, }) - } else if port.Protocol != v1.ProtocolUDP { + } else if port.Protocol != v1.ProtocolUDP && port.Protocol != v1.ProtocolSCTP { // we only add the expected probe if we're doing TCP expectedProbes = append(expectedProbes, network.Probe{ Name: &lbRuleName, @@ -787,8 +794,8 @@ func (az *Cloud) reconcileLoadBalancer(clusterName string, service *v1.Service, expectedRule.LoadBalancingRulePropertiesFormat.IdleTimeoutInMinutes = lbIdleTimeout } - // we didn't construct the probe objects for UDP because they're not used/needed/allowed - if port.Protocol != v1.ProtocolUDP { + // we didn't construct the probe objects for UDP or SCTP because they're not used/needed/allowed + if port.Protocol != v1.ProtocolUDP && port.Protocol != v1.ProtocolSCTP { expectedRule.Probe = &network.SubResource{ ID: to.StringPtr(az.getLoadBalancerProbeID(lbName, lbRuleName)), } diff --git a/pkg/cloudprovider/providers/gce/gce_loadbalancer_internal.go b/pkg/cloudprovider/providers/gce/gce_loadbalancer_internal.go index e4401b21ab1..4542fa1232b 100644 --- a/pkg/cloudprovider/providers/gce/gce_loadbalancer_internal.go +++ b/pkg/cloudprovider/providers/gce/gce_loadbalancer_internal.go @@ -38,6 +38,9 @@ const ( func (gce *GCECloud) ensureInternalLoadBalancer(clusterName, clusterID string, svc *v1.Service, existingFwdRule *compute.ForwardingRule, nodes []*v1.Node) (*v1.LoadBalancerStatus, error) { nm := types.NamespacedName{Name: svc.Name, Namespace: svc.Namespace} ports, protocol := getPortsAndProtocol(svc.Spec.Ports) + if protocol != v1.ProtocolTCP && protocol != v1.ProtocolUDP { + return nil, fmt.Errorf("Invalid protocol %s, only TCP and UDP are supported", string(protocol)) + } scheme := cloud.SchemeInternal loadBalancerName := gce.GetLoadBalancerName(context.TODO(), clusterName, svc) sharedBackend := shareBackendService(svc) diff --git a/pkg/cloudprovider/providers/gce/gce_util_test.go b/pkg/cloudprovider/providers/gce/gce_util_test.go index c2d1dda1f82..bd21e26f6b4 100644 --- a/pkg/cloudprovider/providers/gce/gce_util_test.go +++ b/pkg/cloudprovider/providers/gce/gce_util_test.go @@ -103,11 +103,15 @@ func TestFirewallToGcloudArgs(t *testing.T) { IPProtocol: "tcp", Ports: []string{"321", "123-456", "123"}, }, + { + IPProtocol: "sctp", + Ports: []string{"321", "123-456", "123"}, + }, }, } got := firewallToGcloudArgs(&firewall, "my-project") - var e = `--description "Last Line of Defense" --allow tcp:123,tcp:123-456,tcp:321,udp:123,udp:123-456,udp:321 --source-ranges 1.1.1.1/20,2.2.2.2/20,3.3.3.3/20 --target-tags band-nodes,jock-nodes --project my-project` + var e = `--description "Last Line of Defense" --allow sctp:123,sctp:123-456,sctp:321,tcp:123,tcp:123-456,tcp:321,udp:123,udp:123-456,udp:321 --source-ranges 1.1.1.1/20,2.2.2.2/20,3.3.3.3/20 --target-tags band-nodes,jock-nodes --project my-project` if got != e { t.Errorf("%q does not equal %q", got, e) } diff --git a/pkg/controller/endpoint/endpoints_controller_test.go b/pkg/controller/endpoint/endpoints_controller_test.go index 24446913b8c..3625d233b2f 100644 --- a/pkg/controller/endpoint/endpoints_controller_test.go +++ b/pkg/controller/endpoint/endpoints_controller_test.go @@ -344,6 +344,47 @@ func TestSyncEndpointsProtocolUDP(t *testing.T) { endpointsHandler.ValidateRequest(t, testapi.Default.ResourcePath("endpoints", ns, "foo"), "PUT", &data) } +func TestSyncEndpointsProtocolSCTP(t *testing.T) { + ns := "other" + testServer, endpointsHandler := makeTestServer(t, ns) + defer testServer.Close() + endpoints := newController(testServer.URL) + endpoints.endpointsStore.Add(&v1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: ns, + ResourceVersion: "1", + }, + Subsets: []v1.EndpointSubset{{ + Addresses: []v1.EndpointAddress{{IP: "6.7.8.9", NodeName: &emptyNodeName}}, + Ports: []v1.EndpointPort{{Port: 1000, Protocol: "SCTP"}}, + }}, + }) + addPods(endpoints.podStore, ns, 1, 1, 0) + endpoints.serviceStore.Add(&v1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns}, + Spec: v1.ServiceSpec{ + Selector: map[string]string{}, + Ports: []v1.ServicePort{{Port: 80, TargetPort: intstr.FromInt(8080), Protocol: "SCTP"}}, + }, + }) + endpoints.syncService(ns + "/foo") + + endpointsHandler.ValidateRequestCount(t, 1) + data := runtime.EncodeOrDie(testapi.Default.Codec(), &v1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: ns, + ResourceVersion: "1", + }, + Subsets: []v1.EndpointSubset{{ + Addresses: []v1.EndpointAddress{{IP: "1.2.3.4", NodeName: &emptyNodeName, TargetRef: &v1.ObjectReference{Kind: "Pod", Name: "pod0", Namespace: ns}}}, + Ports: []v1.EndpointPort{{Port: 8080, Protocol: "SCTP"}}, + }}, + }) + endpointsHandler.ValidateRequest(t, testapi.Default.ResourcePath("endpoints", ns, "foo"), "PUT", &data) +} + func TestSyncEndpointsItemsEmptySelectorSelectsAll(t *testing.T) { ns := "other" testServer, endpointsHandler := makeTestServer(t, ns) diff --git a/pkg/controller/service/service_controller_test.go b/pkg/controller/service/service_controller_test.go index 266929c281f..962fe62b1b2 100644 --- a/pkg/controller/service/service_controller_test.go +++ b/pkg/controller/service/service_controller_test.go @@ -124,6 +124,24 @@ func TestCreateExternalLoadBalancer(t *testing.T) { expectErr: false, expectCreateAttempt: true, }, + { + service: &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "sctp-service", + Namespace: "default", + SelfLink: testapi.Default.SelfLink("services", "sctp-service"), + }, + Spec: v1.ServiceSpec{ + Ports: []v1.ServicePort{{ + Port: 80, + Protocol: v1.ProtocolSCTP, + }}, + Type: v1.ServiceTypeLoadBalancer, + }, + }, + expectErr: false, + expectCreateAttempt: true, + }, } for _, item := range table { diff --git a/pkg/kubectl/cmd/expose.go b/pkg/kubectl/cmd/expose.go index b671662aa78..6a9222838fd 100644 --- a/pkg/kubectl/cmd/expose.go +++ b/pkg/kubectl/cmd/expose.go @@ -126,7 +126,7 @@ func NewCmdExposeService(f cmdutil.Factory, streams genericclioptions.IOStreams) } cmd := &cobra.Command{ - Use: "expose (-f FILENAME | TYPE NAME) [--port=port] [--protocol=TCP|UDP] [--target-port=number-or-name] [--name=name] [--external-ip=external-ip-of-service] [--type=type]", + Use: "expose (-f FILENAME | TYPE NAME) [--port=port] [--protocol=TCP|UDP|SCTP] [--target-port=number-or-name] [--name=name] [--external-ip=external-ip-of-service] [--type=type]", DisableFlagsInUseLine: true, Short: i18n.T("Take a replication controller, service, deployment or pod and expose it as a new Kubernetes Service"), Long: exposeLong, diff --git a/pkg/kubectl/cmd/expose_test.go b/pkg/kubectl/cmd/expose_test.go index ec5a76ca94d..c968facd9f4 100644 --- a/pkg/kubectl/cmd/expose_test.go +++ b/pkg/kubectl/cmd/expose_test.go @@ -425,6 +425,11 @@ func TestRunExposeService(t *testing.T) { Port: 8081, TargetPort: intstr.FromInt(8081), }, + { + Protocol: corev1.ProtocolSCTP, + Port: 8082, + TargetPort: intstr.FromInt(8082), + }, }, }, }, @@ -451,12 +456,144 @@ func TestRunExposeService(t *testing.T) { Port: 8081, TargetPort: intstr.FromInt(8081), }, + { + Name: "port-4", + Protocol: corev1.ProtocolSCTP, + Port: 8082, + TargetPort: intstr.FromInt(8082), + }, }, Selector: map[string]string{"svc": "fromfoo"}, }, }, status: 200, }, + { + name: "expose-service-from-service-no-selector-defined-sctp", + args: []string{"service", "baz"}, + ns: "test", + calls: map[string]string{ + "GET": "/namespaces/test/services/baz", + "POST": "/namespaces/test/services", + }, + input: &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"}, + Spec: corev1.ServiceSpec{ + Selector: map[string]string{"app": "go"}, + }, + }, + flags: map[string]string{"protocol": "SCTP", "port": "14", "name": "foo", "labels": "svc=test"}, + output: &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}}, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Protocol: corev1.ProtocolSCTP, + Port: 14, + TargetPort: intstr.FromInt(14), + }, + }, + Selector: map[string]string{"app": "go"}, + }, + }, + expected: "service/foo exposed", + status: 200, + }, + { + name: "expose-service-from-service-sctp", + args: []string{"service", "baz"}, + ns: "test", + calls: map[string]string{ + "GET": "/namespaces/test/services/baz", + "POST": "/namespaces/test/services", + }, + input: &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"}, + Spec: corev1.ServiceSpec{ + Selector: map[string]string{"app": "go"}, + }, + }, + flags: map[string]string{"selector": "func=stream", "protocol": "SCTP", "port": "14", "name": "foo", "labels": "svc=test"}, + output: &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}}, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Protocol: corev1.ProtocolSCTP, + Port: 14, + TargetPort: intstr.FromInt(14), + }, + }, + Selector: map[string]string{"func": "stream"}, + }, + }, + expected: "service/foo exposed", + status: 200, + }, + { + name: "expose-service-cluster-ip-sctp", + args: []string{"service", "baz"}, + ns: "test", + calls: map[string]string{ + "GET": "/namespaces/test/services/baz", + "POST": "/namespaces/test/services", + }, + input: &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"}, + Spec: corev1.ServiceSpec{ + Selector: map[string]string{"app": "go"}, + }, + }, + flags: map[string]string{"selector": "func=stream", "protocol": "SCTP", "port": "14", "name": "foo", "labels": "svc=test", "cluster-ip": "10.10.10.10", "dry-run": "true"}, + output: &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}}, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Protocol: corev1.ProtocolSCTP, + Port: 14, + TargetPort: intstr.FromInt(14), + }, + }, + Selector: map[string]string{"func": "stream"}, + ClusterIP: "10.10.10.10", + }, + }, + expected: "service /foo exposed", + status: 200, + }, + { + name: "expose-headless-service-sctp", + args: []string{"service", "baz"}, + ns: "test", + calls: map[string]string{ + "GET": "/namespaces/test/services/baz", + "POST": "/namespaces/test/services", + }, + input: &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"}, + Spec: corev1.ServiceSpec{ + Selector: map[string]string{"app": "go"}, + }, + }, + flags: map[string]string{"selector": "func=stream", "protocol": "SCTP", "port": "14", "name": "foo", "labels": "svc=test", "cluster-ip": "None", "dry-run": "true"}, + output: &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "", Labels: map[string]string{"svc": "test"}}, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Protocol: corev1.ProtocolSCTP, + Port: 14, + TargetPort: intstr.FromInt(14), + }, + }, + Selector: map[string]string{"func": "stream"}, + ClusterIP: corev1.ClusterIPNone, + }, + }, + expected: "service/foo exposed", + status: 200, + }, } for _, test := range tests { diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-create-list-error/3.response b/pkg/kubectl/cmd/testdata/edit/testcase-create-list-error/3.response index 156f2ea1314..d47aa684fa7 100755 --- a/pkg/kubectl/cmd/testdata/edit/testcase-create-list-error/3.response +++ b/pkg/kubectl/cmd/testdata/edit/testcase-create-list-error/3.response @@ -3,7 +3,7 @@ "apiVersion": "v1", "metadata": {}, "status": "Failure", - "message": "Service \"svc2\" is invalid: [spec.ports[0].protocol: Unsupported value: \"VHF\": supported values: TCP, UDP, spec.clusterIP: Invalid value: \"10.0.0.182.1\": must be empty, 'None', or a valid IP address]", + "message": "Service \"svc2\" is invalid: [spec.ports[0].protocol: Unsupported value: \"VHF\": supported values: TCP, UDP, SCTP, spec.clusterIP: Invalid value: \"10.0.0.182.1\": must be empty, 'None', or a valid IP address]", "reason": "Invalid", "details": { "name": "svc2", @@ -11,7 +11,7 @@ "causes": [ { "reason": "FieldValueNotSupported", - "message": "Unsupported value: \"VHF\": supported values: TCP, UDP", + "message": "Unsupported value: \"VHF\": supported values: TCP, UDP, SCTP", "field": "spec.ports[0].protocol" }, { diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/3.response b/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/3.response index b40462575f6..887b0b5fe2f 100755 --- a/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/3.response +++ b/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/3.response @@ -3,7 +3,7 @@ "apiVersion": "v1", "metadata": {}, "status": "Failure", - "message": "Service \"svc1\" is invalid: [spec.clusterIP: Invalid value: \"10.0.0.10\": field is immutable, spec.ports[0].protocol: Unsupported value: \"VHF\": supported values: TCP, UDP]", + "message": "Service \"svc1\" is invalid: [spec.clusterIP: Invalid value: \"10.0.0.10\": field is immutable, spec.ports[0].protocol: Unsupported value: \"VHF\": supported values: TCP, UDP, SCTP]", "reason": "Invalid", "details": { "name": "svc1", @@ -16,7 +16,7 @@ }, { "reason": "FieldValueNotSupported", - "message": "Unsupported value: \"VHF\": supported values: TCP, UDP", + "message": "Unsupported value: \"VHF\": supported values: TCP, UDP, SCTP", "field": "spec.ports[0].protocol" } ] diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/5.edited b/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/5.edited index 3aa682b445d..09b5aee489f 100755 --- a/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/5.edited +++ b/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/5.edited @@ -4,7 +4,7 @@ # # services "svc1" was not valid: # * spec.clusterIP: Invalid value: "10.0.0.10": field is immutable -# * spec.ports[0].protocol: Unsupported value: "VHF": supported values: TCP, UDP +# * spec.ports[0].protocol: Unsupported value: "VHF": supported values: TCP, UDP, SCTP # apiVersion: v1 items: diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/5.original b/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/5.original index 7913d211ac7..b56a73ca4e6 100755 --- a/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/5.original +++ b/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/5.original @@ -4,7 +4,7 @@ # # services "svc1" was not valid: # * spec.clusterIP: Invalid value: "10.0.0.10": field is immutable -# * spec.ports[0].protocol: Unsupported value: "VHF": supported values: TCP, UDP +# * spec.ports[0].protocol: Unsupported value: "VHF": supported values: TCP, UDP, SCTP # apiVersion: v1 items: diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/6.response b/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/6.response index 726f0fd487b..eba9193950b 100755 --- a/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/6.response +++ b/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/6.response @@ -3,7 +3,7 @@ "apiVersion": "v1", "metadata": {}, "status": "Failure", - "message": "Service \"svc1\" is invalid: spec.ports[0].protocol: Unsupported value: \"VHF\": supported values: TCP, UDP", + "message": "Service \"svc1\" is invalid: spec.ports[0].protocol: Unsupported value: \"VHF\": supported values: TCP, UDP, SCTP", "reason": "Invalid", "details": { "name": "svc1", @@ -11,7 +11,7 @@ "causes": [ { "reason": "FieldValueNotSupported", - "message": "Unsupported value: \"VHF\": supported values: TCP, UDP", + "message": "Unsupported value: \"VHF\": supported values: TCP, UDP, SCTP", "field": "spec.ports[0].protocol" } ] diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/8.edited b/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/8.edited index 7ad39a82a45..6c0168cd63b 100755 --- a/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/8.edited +++ b/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/8.edited @@ -3,7 +3,7 @@ # reopened with the relevant failures. # # services "svc1" was not valid: -# * spec.ports[0].protocol: Unsupported value: "VHF": supported values: TCP, UDP +# * spec.ports[0].protocol: Unsupported value: "VHF": supported values: TCP, UDP, SCTP # apiVersion: v1 items: diff --git a/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/8.original b/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/8.original index 4d82b22b206..eee7f0d5083 100755 --- a/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/8.original +++ b/pkg/kubectl/cmd/testdata/edit/testcase-list-errors/8.original @@ -3,7 +3,7 @@ # reopened with the relevant failures. # # services "svc1" was not valid: -# * spec.ports[0].protocol: Unsupported value: "VHF": supported values: TCP, UDP +# * spec.ports[0].protocol: Unsupported value: "VHF": supported values: TCP, UDP, SCTP # apiVersion: v1 items: diff --git a/pkg/kubectl/generate_test.go b/pkg/kubectl/generate_test.go index 6bb174add13..d62d79469d1 100644 --- a/pkg/kubectl/generate_test.go +++ b/pkg/kubectl/generate_test.go @@ -366,10 +366,12 @@ func TestMakeParseProtocols(t *testing.T) { protocols: map[string]string{ "102": "UDP", "101": "TCP", + "103": "SCTP", }, expected: map[string]string{ "102": "UDP", "101": "TCP", + "103": "SCTP", }, }, } diff --git a/pkg/kubectl/service_test.go b/pkg/kubectl/service_test.go index ceaba9b4633..420ed8e7120 100644 --- a/pkg/kubectl/service_test.go +++ b/pkg/kubectl/service_test.go @@ -598,6 +598,355 @@ func TestGenerateService(t *testing.T) { }, }, }, + { + generator: ServiceGeneratorV2{}, + params: map[string]interface{}{ + "selector": "foo=bar,baz=blah", + "name": "test", + "port": "80", + "protocol": "SCTP", + "container-port": "1234", + }, + expected: v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: v1.ServiceSpec{ + Selector: map[string]string{ + "foo": "bar", + "baz": "blah", + }, + Ports: []v1.ServicePort{ + { + Port: 80, + Protocol: "SCTP", + TargetPort: intstr.FromInt(1234), + }, + }, + }, + }, + }, + { + generator: ServiceGeneratorV2{}, + params: map[string]interface{}{ + "selector": "foo=bar,baz=blah", + "labels": "key1=value1,key2=value2", + "name": "test", + "port": "80", + "protocol": "SCTP", + "container-port": "1234", + }, + expected: v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Labels: map[string]string{ + "key1": "value1", + "key2": "value2", + }, + }, + Spec: v1.ServiceSpec{ + Selector: map[string]string{ + "foo": "bar", + "baz": "blah", + }, + Ports: []v1.ServicePort{ + { + Port: 80, + Protocol: "SCTP", + TargetPort: intstr.FromInt(1234), + }, + }, + }, + }, + }, + { + generator: ServiceGeneratorV1{}, + params: map[string]interface{}{ + "selector": "foo=bar,baz=blah", + "name": "test", + "port": "80", + "protocol": "SCTP", + "container-port": "1234", + }, + expected: v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: v1.ServiceSpec{ + Selector: map[string]string{ + "foo": "bar", + "baz": "blah", + }, + Ports: []v1.ServicePort{ + { + Name: "default", + Port: 80, + Protocol: "SCTP", + TargetPort: intstr.FromInt(1234), + }, + }, + }, + }, + }, + { + generator: ServiceGeneratorV1{}, + params: map[string]interface{}{ + "selector": "foo=bar,baz=blah", + "name": "test", + "port": "80", + "protocol": "SCTP", + "container-port": "1234", + "session-affinity": "ClientIP", + }, + expected: v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: v1.ServiceSpec{ + Selector: map[string]string{ + "foo": "bar", + "baz": "blah", + }, + Ports: []v1.ServicePort{ + { + Name: "default", + Port: 80, + Protocol: "SCTP", + TargetPort: intstr.FromInt(1234), + }, + }, + SessionAffinity: v1.ServiceAffinityClientIP, + }, + }, + }, + { + generator: ServiceGeneratorV2{}, + params: map[string]interface{}{ + "selector": "foo=bar,baz=blah", + "name": "test", + "port": "80", + "protocol": "SCTP", + "container-port": "1234", + "cluster-ip": "10.10.10.10", + }, + expected: v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: v1.ServiceSpec{ + Selector: map[string]string{ + "foo": "bar", + "baz": "blah", + }, + Ports: []v1.ServicePort{ + { + Port: 80, + Protocol: "SCTP", + TargetPort: intstr.FromInt(1234), + }, + }, + ClusterIP: "10.10.10.10", + }, + }, + }, + { + generator: ServiceGeneratorV2{}, + params: map[string]interface{}{ + "selector": "foo=bar,baz=blah", + "name": "test", + "port": "80", + "protocol": "SCTP", + "container-port": "1234", + "cluster-ip": "None", + }, + expected: v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: v1.ServiceSpec{ + Selector: map[string]string{ + "foo": "bar", + "baz": "blah", + }, + Ports: []v1.ServicePort{ + { + Port: 80, + Protocol: "SCTP", + TargetPort: intstr.FromInt(1234), + }, + }, + ClusterIP: v1.ClusterIPNone, + }, + }, + }, + { + generator: ServiceGeneratorV1{}, + params: map[string]interface{}{ + "selector": "foo=bar", + "name": "test", + "ports": "80,443", + "protocol": "SCTP", + "container-port": "foobar", + }, + expected: v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: v1.ServiceSpec{ + Selector: map[string]string{ + "foo": "bar", + }, + Ports: []v1.ServicePort{ + { + Name: "port-1", + Port: 80, + Protocol: v1.ProtocolSCTP, + TargetPort: intstr.FromString("foobar"), + }, + { + Name: "port-2", + Port: 443, + Protocol: v1.ProtocolSCTP, + TargetPort: intstr.FromString("foobar"), + }, + }, + }, + }, + }, + { + generator: ServiceGeneratorV2{}, + params: map[string]interface{}{ + "selector": "foo=bar", + "name": "test", + "ports": "80,443", + "protocol": "SCTP", + }, + expected: v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: v1.ServiceSpec{ + Selector: map[string]string{ + "foo": "bar", + }, + Ports: []v1.ServicePort{ + { + Name: "port-1", + Port: 80, + Protocol: v1.ProtocolSCTP, + TargetPort: intstr.FromInt(80), + }, + { + Name: "port-2", + Port: 443, + Protocol: v1.ProtocolSCTP, + TargetPort: intstr.FromInt(443), + }, + }, + }, + }, + }, + { + generator: ServiceGeneratorV2{}, + params: map[string]interface{}{ + "selector": "foo=bar", + "name": "test", + "ports": "80,8080", + "protocols": "8080/SCTP", + }, + expected: v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: v1.ServiceSpec{ + Selector: map[string]string{ + "foo": "bar", + }, + Ports: []v1.ServicePort{ + { + Name: "port-1", + Port: 80, + Protocol: v1.ProtocolTCP, + TargetPort: intstr.FromInt(80), + }, + { + Name: "port-2", + Port: 8080, + Protocol: v1.ProtocolSCTP, + TargetPort: intstr.FromInt(8080), + }, + }, + }, + }, + }, + { + generator: ServiceGeneratorV2{}, + params: map[string]interface{}{ + "selector": "foo=bar", + "name": "test", + "ports": "80,8080,8081,8082", + "protocols": "8080/UDP,8081/TCP,8082/SCTP", + }, + expected: v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: v1.ServiceSpec{ + Selector: map[string]string{ + "foo": "bar", + }, + Ports: []v1.ServicePort{ + { + Name: "port-1", + Port: 80, + Protocol: v1.ProtocolTCP, + TargetPort: intstr.FromInt(80), + }, + { + Name: "port-2", + Port: 8080, + Protocol: v1.ProtocolUDP, + TargetPort: intstr.FromInt(8080), + }, + { + Name: "port-3", + Port: 8081, + Protocol: v1.ProtocolTCP, + TargetPort: intstr.FromInt(8081), + }, + { + Name: "port-4", + Port: 8082, + Protocol: v1.ProtocolSCTP, + TargetPort: intstr.FromInt(8082), + }, + }, + }, + }, + }, + { + generator: ServiceGeneratorV2{}, + params: map[string]interface{}{ + "selector": "foo=bar,baz=blah", + "name": "test", + "protocol": "SCTP", + "container-port": "1234", + "cluster-ip": "None", + }, + expected: v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + Spec: v1.ServiceSpec{ + Selector: map[string]string{ + "foo": "bar", + "baz": "blah", + }, + Ports: []v1.ServicePort{}, + ClusterIP: v1.ClusterIPNone, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/kubelet/apis/cri/runtime/v1alpha2/api.pb.go b/pkg/kubelet/apis/cri/runtime/v1alpha2/api.pb.go index 6b496711e1b..76e8e456ce2 100644 --- a/pkg/kubelet/apis/cri/runtime/v1alpha2/api.pb.go +++ b/pkg/kubelet/apis/cri/runtime/v1alpha2/api.pb.go @@ -160,17 +160,20 @@ const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package type Protocol int32 const ( - Protocol_TCP Protocol = 0 - Protocol_UDP Protocol = 1 + Protocol_TCP Protocol = 0 + Protocol_UDP Protocol = 1 + Protocol_SCTP Protocol = 2 ) var Protocol_name = map[int32]string{ 0: "TCP", 1: "UDP", + 2: "SCTP", } var Protocol_value = map[string]int32{ - "TCP": 0, - "UDP": 1, + "TCP": 0, + "UDP": 1, + "SCTP": 2, } func (x Protocol) String() string { diff --git a/pkg/kubelet/apis/cri/runtime/v1alpha2/api.proto b/pkg/kubelet/apis/cri/runtime/v1alpha2/api.proto index bd6cacc1484..bb00c3842c5 100644 --- a/pkg/kubelet/apis/cri/runtime/v1alpha2/api.proto +++ b/pkg/kubelet/apis/cri/runtime/v1alpha2/api.proto @@ -143,6 +143,7 @@ message DNSConfig { enum Protocol { TCP = 0; UDP = 1; + SCTP = 2; } // PortMapping specifies the port mapping configurations of a sandbox. diff --git a/pkg/kubelet/dockershim/docker_checkpoint.go b/pkg/kubelet/dockershim/docker_checkpoint.go index 8bfa1a77822..e44f0bd62f2 100644 --- a/pkg/kubelet/dockershim/docker_checkpoint.go +++ b/pkg/kubelet/dockershim/docker_checkpoint.go @@ -28,6 +28,7 @@ const ( sandboxCheckpointDir = "sandbox" protocolTCP = Protocol("tcp") protocolUDP = Protocol("udp") + protocolSCTP = Protocol("sctp") schemaVersion = "v1" ) diff --git a/pkg/kubelet/dockershim/docker_sandbox.go b/pkg/kubelet/dockershim/docker_sandbox.go index 13e9c42366e..c5cf216698d 100644 --- a/pkg/kubelet/dockershim/docker_sandbox.go +++ b/pkg/kubelet/dockershim/docker_sandbox.go @@ -672,6 +672,8 @@ func toCheckpointProtocol(protocol runtimeapi.Protocol) Protocol { return protocolTCP case runtimeapi.Protocol_UDP: return protocolUDP + case runtimeapi.Protocol_SCTP: + return protocolSCTP } glog.Warningf("Unknown protocol %q: defaulting to TCP", protocol) return protocolTCP diff --git a/pkg/kubelet/dockershim/docker_service.go b/pkg/kubelet/dockershim/docker_service.go index d7fcc9232d1..a0ca35e0b58 100644 --- a/pkg/kubelet/dockershim/docker_service.go +++ b/pkg/kubelet/dockershim/docker_service.go @@ -515,6 +515,8 @@ func toAPIProtocol(protocol Protocol) v1.Protocol { return v1.ProtocolTCP case protocolUDP: return v1.ProtocolUDP + case protocolSCTP: + return v1.ProtocolSCTP } glog.Warningf("Unknown protocol %q: defaulting to TCP", protocol) return v1.ProtocolTCP diff --git a/pkg/kubelet/dockershim/helpers.go b/pkg/kubelet/dockershim/helpers.go index 3caa2441dac..05d23c75b29 100644 --- a/pkg/kubelet/dockershim/helpers.go +++ b/pkg/kubelet/dockershim/helpers.go @@ -172,6 +172,8 @@ func makePortsAndBindings(pm []*runtimeapi.PortMapping) (dockernat.PortSet, map[ protocol = "/udp" case runtimeapi.Protocol_TCP: protocol = "/tcp" + case runtimeapi.Protocol_SCTP: + protocol = "/sctp" default: glog.Warningf("Unknown protocol %q: defaulting to TCP", port.Protocol) protocol = "/tcp" diff --git a/pkg/kubelet/dockershim/network/hostport/hostport_manager_test.go b/pkg/kubelet/dockershim/network/hostport/hostport_manager_test.go index 1b3b460cb5a..d495379bd8e 100644 --- a/pkg/kubelet/dockershim/network/hostport/hostport_manager_test.go +++ b/pkg/kubelet/dockershim/network/hostport/hostport_manager_test.go @@ -83,6 +83,16 @@ func TestOpenCloseHostports(t *testing.T) { }, false, }, + { + &PodPortMapping{ + Namespace: "ns1", + Name: "n4", + PortMappings: []*PortMapping{ + {HostPort: 7777, Protocol: v1.Protocol("STCP")}, + }, + }, + false, + }, } iptables := NewFakeIPTables() @@ -269,9 +279,11 @@ func TestHostportManager(t *testing.T) { `:KUBE-HP-IJHALPHTORMHHPPK - [0:0]`: true, `:KUBE-HP-63UPIDJXVRSZGSUZ - [0:0]`: true, `:KUBE-HP-WFBOALXEP42XEMJK - [0:0]`: true, + `:KUBE-HP-XU6AWMMJYOZOFTFZ - [0:0]`: true, "-A KUBE-HOSTPORTS -m comment --comment \"pod3_ns1 hostport 8443\" -m tcp -p tcp --dport 8443 -j KUBE-HP-WFBOALXEP42XEMJK": true, "-A KUBE-HOSTPORTS -m comment --comment \"pod1_ns1 hostport 8081\" -m udp -p udp --dport 8081 -j KUBE-HP-63UPIDJXVRSZGSUZ": true, "-A KUBE-HOSTPORTS -m comment --comment \"pod1_ns1 hostport 8080\" -m tcp -p tcp --dport 8080 -j KUBE-HP-IJHALPHTORMHHPPK": true, + "-A KUBE-HOSTPORTS -m comment --comment \"pod1_ns1 hostport 8083\" -m sctp -p sctp --dport 8083 -j KUBE-HP-XU6AWMMJYOZOFTFZ": true, "-A OUTPUT -m comment --comment \"kube hostport portals\" -m addrtype --dst-type LOCAL -j KUBE-HOSTPORTS": true, "-A PREROUTING -m comment --comment \"kube hostport portals\" -m addrtype --dst-type LOCAL -j KUBE-HOSTPORTS": true, "-A POSTROUTING -m comment --comment \"SNAT for localhost access to hostports\" -o cbr0 -s 127.0.0.0/8 -j MASQUERADE": true, @@ -279,6 +291,8 @@ func TestHostportManager(t *testing.T) { "-A KUBE-HP-IJHALPHTORMHHPPK -m comment --comment \"pod1_ns1 hostport 8080\" -m tcp -p tcp -j DNAT --to-destination 10.1.1.2:80": true, "-A KUBE-HP-63UPIDJXVRSZGSUZ -m comment --comment \"pod1_ns1 hostport 8081\" -s 10.1.1.2/32 -j KUBE-MARK-MASQ": true, "-A KUBE-HP-63UPIDJXVRSZGSUZ -m comment --comment \"pod1_ns1 hostport 8081\" -m udp -p udp -j DNAT --to-destination 10.1.1.2:81": true, + "-A KUBE-HP-XU6AWMMJYOZOFTFZ -m comment --comment \"pod1_ns1 hostport 8083\" -s 10.1.1.2/32 -j KUBE-MARK-MASQ": true, + "-A KUBE-HP-XU6AWMMJYOZOFTFZ -m comment --comment \"pod1_ns1 hostport 8083\" -m sctp -p sctp -j DNAT --to-destination 10.1.1.2:83": true, "-A KUBE-HP-WFBOALXEP42XEMJK -m comment --comment \"pod3_ns1 hostport 8443\" -s 10.1.1.4/32 -j KUBE-MARK-MASQ": true, "-A KUBE-HP-WFBOALXEP42XEMJK -m comment --comment \"pod3_ns1 hostport 8443\" -m tcp -p tcp -j DNAT --to-destination 10.1.1.4:443": true, `COMMIT`: true, diff --git a/pkg/kubelet/envvars/envvars_test.go b/pkg/kubelet/envvars/envvars_test.go index 7d0cd84dccb..b99dd31bf95 100644 --- a/pkg/kubelet/envvars/envvars_test.go +++ b/pkg/kubelet/envvars/envvars_test.go @@ -90,6 +90,16 @@ func TestFromServices(t *testing.T) { }, }, }, + { + ObjectMeta: metav1.ObjectMeta{Name: "sctp-1"}, + Spec: v1.ServiceSpec{ + Selector: map[string]string{"bar": "sctp-sel"}, + ClusterIP: "1.2.3.4", + Ports: []v1.ServicePort{ + {Port: 777, Protocol: "SCTP"}, + }, + }, + }, } vars := envvars.FromServices(sl) expected := []v1.EnvVar{ @@ -138,6 +148,13 @@ func TestFromServices(t *testing.T) { {Name: "SUPER_IPV6_PORT_8084_TCP_PROTO", Value: "tcp"}, {Name: "SUPER_IPV6_PORT_8084_TCP_PORT", Value: "8084"}, {Name: "SUPER_IPV6_PORT_8084_TCP_ADDR", Value: "2001:DB8::"}, + {Name: "SCTP_1_SERVICE_HOST", Value: "1.2.3.4"}, + {Name: "SCTP_1_SERVICE_PORT", Value: "777"}, + {Name: "SCTP_1_PORT", Value: "sctp://1.2.3.4:777"}, + {Name: "SCTP_1_PORT_777_SCTP", Value: "sctp://1.2.3.4:777"}, + {Name: "SCTP_1_PORT_777_SCTP_PROTO", Value: "sctp"}, + {Name: "SCTP_1_PORT_777_SCTP_PORT", Value: "777"}, + {Name: "SCTP_1_PORT_777_SCTP_ADDR", Value: "1.2.3.4"}, } if len(vars) != len(expected) { t.Errorf("Expected %d env vars, got: %+v", len(expected), vars) diff --git a/pkg/kubelet/kuberuntime/helpers.go b/pkg/kubelet/kuberuntime/helpers.go index d52faa06407..fd977d5a16e 100644 --- a/pkg/kubelet/kuberuntime/helpers.go +++ b/pkg/kubelet/kuberuntime/helpers.go @@ -79,6 +79,8 @@ func toRuntimeProtocol(protocol v1.Protocol) runtimeapi.Protocol { return runtimeapi.Protocol_TCP case v1.ProtocolUDP: return runtimeapi.Protocol_UDP + case v1.ProtocolSCTP: + return runtimeapi.Protocol_SCTP } glog.Warningf("Unknown protocol %q: defaulting to TCP", protocol) diff --git a/pkg/kubemark/hollow_proxy.go b/pkg/kubemark/hollow_proxy.go index 8af54ae1763..810639b4f81 100644 --- a/pkg/kubemark/hollow_proxy.go +++ b/pkg/kubemark/hollow_proxy.go @@ -94,6 +94,7 @@ func NewHollowProxyOrDie( recorder, nil, []string{}, + false, ) if err != nil { return nil, fmt.Errorf("unable to create proxier: %v", err) diff --git a/pkg/master/controller_test.go b/pkg/master/controller_test.go index a20d82cd698..8e31a71ccfc 100644 --- a/pkg/master/controller_test.go +++ b/pkg/master/controller_test.go @@ -372,6 +372,20 @@ func TestReconcileEndpoints(t *testing.T) { }}, }, }, + { + testName: "no existing sctp endpoints", + serviceName: "boo", + ip: "1.2.3.4", + endpointPorts: []api.EndpointPort{{Name: "boo", Port: 7777, Protocol: "SCTP"}}, + endpoints: nil, + expectCreate: &api.Endpoints{ + ObjectMeta: om("boo"), + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}}, + Ports: []api.EndpointPort{{Name: "boo", Port: 7777, Protocol: "SCTP"}}, + }}, + }, + }, } for _, test := range reconcile_tests { fakeClient := fake.NewSimpleClientset() diff --git a/pkg/printers/internalversion/printers_test.go b/pkg/printers/internalversion/printers_test.go index b8968dd57d3..acfb762d5e7 100644 --- a/pkg/printers/internalversion/printers_test.go +++ b/pkg/printers/internalversion/printers_test.go @@ -1152,6 +1152,10 @@ func TestPrintHumanReadableService(t *testing.T) { Port: 8000, Protocol: "TCP", }, + { + Port: 7777, + Protocol: "SCTP", + }, }, }, }, diff --git a/pkg/proxy/apis/kubeproxyconfig/types.go b/pkg/proxy/apis/kubeproxyconfig/types.go index bb44675267d..33c55d8c758 100644 --- a/pkg/proxy/apis/kubeproxyconfig/types.go +++ b/pkg/proxy/apis/kubeproxyconfig/types.go @@ -155,6 +155,12 @@ type KubeProxyConfiguration struct { // If set it to a non-zero IP block, kube-proxy will filter that down to just the IPs that applied to the node. // An empty string slice is meant to select all network interfaces. NodePortAddresses []string + // sctpUserSpaceNode is to enable the deployment of applications that use a userspace SCTP protocol implementation. + // If set to "true" the kube-proxy does not start listening on the host node on the SCTP ports of Services + // specified with externalIP or type=NodePort. + // If set to "false" kube-proxy listens on the SCTP ports of Services specified with externalIP or type=NodePort. + // Default value is "false". + SCTPUserSpaceNode bool } // Currently, three modes of proxy are available in Linux platform: 'userspace' (older, going to be EOL), 'iptables' diff --git a/pkg/proxy/apis/kubeproxyconfig/v1alpha1/types.go b/pkg/proxy/apis/kubeproxyconfig/v1alpha1/types.go index beab095d605..502954c9747 100644 --- a/pkg/proxy/apis/kubeproxyconfig/v1alpha1/types.go +++ b/pkg/proxy/apis/kubeproxyconfig/v1alpha1/types.go @@ -151,6 +151,12 @@ type KubeProxyConfiguration struct { // If set it to a non-zero IP block, kube-proxy will filter that down to just the IPs that applied to the node. // An empty string slice is meant to select all network interfaces. NodePortAddresses []string `json:"nodePortAddresses"` + // sctpUserSpaceNode is to enable the deployment of applications that use a userspace SCTP protocol implementation. + // If set to "true" the kube-proxy does not start listening on the host node on the SCTP ports of Services + // specified with externalIP or type=NodePort. + // If set to "false" kube-proxy listens on the SCTP ports of Services specified with externalIP or type=NodePort. + // Default value is "false". + SCTPUserSpaceNode bool `json:"sctpUserSpaceNode"` } // Currently, three modes of proxy are available in Linux platform: 'userspace' (older, going to be EOL), 'iptables' diff --git a/pkg/proxy/apis/kubeproxyconfig/v1alpha1/zz_generated.conversion.go b/pkg/proxy/apis/kubeproxyconfig/v1alpha1/zz_generated.conversion.go index c674771a913..82f629b4f5d 100644 --- a/pkg/proxy/apis/kubeproxyconfig/v1alpha1/zz_generated.conversion.go +++ b/pkg/proxy/apis/kubeproxyconfig/v1alpha1/zz_generated.conversion.go @@ -144,6 +144,7 @@ func autoConvert_v1alpha1_KubeProxyConfiguration_To_kubeproxyconfig_KubeProxyCon } out.ConfigSyncPeriod = in.ConfigSyncPeriod out.NodePortAddresses = *(*[]string)(unsafe.Pointer(&in.NodePortAddresses)) + out.SCTPUserSpaceNode = in.SCTPUserSpaceNode return nil } @@ -179,6 +180,7 @@ func autoConvert_kubeproxyconfig_KubeProxyConfiguration_To_v1alpha1_KubeProxyCon } out.ConfigSyncPeriod = in.ConfigSyncPeriod out.NodePortAddresses = *(*[]string)(unsafe.Pointer(&in.NodePortAddresses)) + out.SCTPUserSpaceNode = in.SCTPUserSpaceNode return nil } diff --git a/pkg/proxy/iptables/proxier.go b/pkg/proxy/iptables/proxier.go index 470916ce705..fd3a7f04578 100644 --- a/pkg/proxy/iptables/proxier.go +++ b/pkg/proxy/iptables/proxier.go @@ -33,6 +33,7 @@ import ( "time" "github.com/golang/glog" + "github.com/ishidawataru/sctp" "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" @@ -259,6 +260,10 @@ type Proxier struct { // networkInterfacer defines an interface for several net library functions. // Inject for test purpose. networkInterfacer utilproxy.NetworkInterfacer + + // Indicates whether a node is dedicated for applications that use userspace + // SCTP stack. + sctpUserSpaceNode bool } // listenPortOpener opens ports by calling bind() and listen(). @@ -290,6 +295,7 @@ func NewProxier(ipt utiliptables.Interface, recorder record.EventRecorder, healthzServer healthcheck.HealthzUpdater, nodePortAddresses []string, + sctpUserSpaceNode bool, ) (*Proxier, error) { // Set the route_localnet sysctl we need for if err := sysctl.SetSysctl(sysctlRouteLocalnet, 1); err != nil { @@ -346,6 +352,7 @@ func NewProxier(ipt utiliptables.Interface, natRules: bytes.NewBuffer(nil), nodePortAddresses: nodePortAddresses, networkInterfacer: utilproxy.RealNetwork{}, + sctpUserSpaceNode: sctpUserSpaceNode, } burstSyncs := 2 glog.V(3).Infof("minSyncPeriod: %v, syncPeriod: %v, burstSyncs: %d", minSyncPeriod, syncPeriod, burstSyncs) @@ -845,9 +852,15 @@ func (proxier *Proxier) syncProxyRules() { // If the "external" IP happens to be an IP that is local to this // machine, hold the local port open so no other process can open it // (because the socket might open but it would never work). + // Exception: if the node is dedicated for applications that use + // userspace SCTP stack we must not start listening on the local port + // in the kernel because that would load the SCTP kernel module, and + // there are interworking issues between the SCTP kernel module and + // userspace SCTP stacks. if local, err := utilproxy.IsLocalIP(externalIP); err != nil { glog.Errorf("can't determine if IP is local, assuming not: %v", err) - } else if local { + } else if local && (svcInfo.GetProtocol() != api.ProtocolSCTP || !proxier.sctpUserSpaceNode) { + lp := utilproxy.LocalPort{ Description: "externalIP for " + svcNameString, IP: externalIP, @@ -1016,7 +1029,7 @@ func (proxier *Proxier) syncProxyRules() { if proxier.portsMap[lp] != nil { glog.V(4).Infof("Port %s was open before and is still needed", lp.String()) replacementPortsMap[lp] = proxier.portsMap[lp] - } else { + } else if svcInfo.GetProtocol() != api.ProtocolSCTP || !proxier.sctpUserSpaceNode { socket, err := proxier.portMapper.OpenLocalPort(&lp) if err != nil { glog.Errorf("can't open %s, skipping this nodePort: %v", lp.String(), err) @@ -1419,6 +1432,18 @@ func openLocalPort(lp *utilproxy.LocalPort) (utilproxy.Closeable, error) { return nil, err } socket = conn + case "sctp": + // There is not any golang/net way to bind or listen on an SCTP socket. + // We have to use a 3rd part lib - the same which is used in Docker + addr, err := sctp.ResolveSCTPAddr("sctp", net.JoinHostPort(lp.IP, strconv.Itoa(lp.Port))) + if err != nil { + return nil, err + } + conn, err := sctp.ListenSCTP("sctp", addr) + if err != nil { + return nil, err + } + socket = conn default: return nil, fmt.Errorf("unknown protocol %q", lp.Protocol) } diff --git a/pkg/proxy/iptables/proxier_test.go b/pkg/proxy/iptables/proxier_test.go index c11cde9f0b1..defee66431d 100644 --- a/pkg/proxy/iptables/proxier_test.go +++ b/pkg/proxy/iptables/proxier_test.go @@ -161,8 +161,9 @@ func newFakeServiceInfo(service proxy.ServicePortName, ip net.IP, port int, prot func TestDeleteEndpointConnections(t *testing.T) { const ( - UDP = v1.ProtocolUDP - TCP = v1.ProtocolTCP + UDP = api.ProtocolUDP + TCP = api.ProtocolTCP + SCTP = api.ProtocolSCTP ) testCases := []struct { description string @@ -188,7 +189,14 @@ func TestDeleteEndpointConnections(t *testing.T) { svcPort: 80, protocol: TCP, endpoint: "10.240.0.4:80", - }, { + },{ + description: "V4 SCTP", + svcName: "v4-sctp", + svcIP: "10.96.3.3", + svcPort: 80, + protocol: SCTP, + endpoint: "10.240.0.5:80", + },{ description: "V4 UDP, nothing to delete, benign error", svcName: "v4-udp-nothing-to-delete", svcIP: "10.96.1.1", @@ -218,6 +226,13 @@ func TestDeleteEndpointConnections(t *testing.T) { svcPort: 80, protocol: TCP, endpoint: "[2001:db8::3]:80", + }, { + description: "V6 SCTP", + svcName: "v6-sctp", + svcIP: "fd00:1234::40", + svcPort: 80, + protocol: SCTP, + endpoint: "[2001:db8::4]:80", }, } @@ -379,6 +394,7 @@ func NewFakeProxier(ipt utiliptables.Interface) *Proxier { natRules: bytes.NewBuffer(nil), nodePortAddresses: make([]string, 0), networkInterfacer: utilproxytest.NewFakeNetwork(), + sctpUserSpaceNode: false, } p.syncRunner = async.NewBoundedFrequencyRunner("test-sync-runner", p.syncProxyRules, 0, time.Minute, 1) return p @@ -1061,12 +1077,14 @@ func TestBuildServiceMapAddRemove(t *testing.T) { svc.Spec.ClusterIP = "172.16.55.4" svc.Spec.Ports = addTestPort(svc.Spec.Ports, "something", "UDP", 1234, 4321, 0) svc.Spec.Ports = addTestPort(svc.Spec.Ports, "somethingelse", "UDP", 1235, 5321, 0) + svc.Spec.Ports = addTestPort(svc.Spec.Ports, "sctpport", "SCTP", 1236, 6321, 0) }), makeTestService("somewhere-else", "node-port", func(svc *v1.Service) { svc.Spec.Type = v1.ServiceTypeNodePort svc.Spec.ClusterIP = "172.16.55.10" svc.Spec.Ports = addTestPort(svc.Spec.Ports, "blahblah", "UDP", 345, 678, 0) svc.Spec.Ports = addTestPort(svc.Spec.Ports, "moreblahblah", "TCP", 344, 677, 0) + svc.Spec.Ports = addTestPort(svc.Spec.Ports, "muchmoreblah", "SCTP", 343, 676, 0) }), makeTestService("somewhere", "load-balancer", func(svc *v1.Service) { svc.Spec.Type = v1.ServiceTypeLoadBalancer @@ -1100,8 +1118,8 @@ func TestBuildServiceMapAddRemove(t *testing.T) { fp.OnServiceAdd(services[i]) } result := proxy.UpdateServiceMap(fp.serviceMap, fp.serviceChanges) - if len(fp.serviceMap) != 8 { - t.Errorf("expected service map length 8, got %v", fp.serviceMap) + if len(fp.serviceMap) != 10 { + t.Errorf("expected service map length 10, got %v", fp.serviceMap) } // The only-local-loadbalancer ones get added diff --git a/pkg/proxy/ipvs/ipset.go b/pkg/proxy/ipvs/ipset.go index 5da72749b42..b5fd17cc35d 100644 --- a/pkg/proxy/ipvs/ipset.go +++ b/pkg/proxy/ipvs/ipset.go @@ -64,6 +64,12 @@ const ( kubeNodePortLocalSetUDPComment = "Kubernetes nodeport UDP port with externalTrafficPolicy=local" kubeNodePortLocalSetUDP = "KUBE-NODE-PORT-LOCAL-UDP" + + kubeNodePortSetSCTPComment = "Kubernetes nodeport SCTP port for masquerade purpose" + kubeNodePortSetSCTP = "KUBE-NODE-PORT-SCTP" + + kubeNodePortLocalSetSCTPComment = "Kubernetes nodeport SCTP port with externalTrafficPolicy=local" + kubeNodePortLocalSetSCTP = "KUBE-NODE-PORT-LOCAL-SCTP" ) // IPSetVersioner can query the current ipset version. diff --git a/pkg/proxy/ipvs/ipset_test.go b/pkg/proxy/ipvs/ipset_test.go index 2e6f667de59..f90dca706ed 100644 --- a/pkg/proxy/ipvs/ipset_test.go +++ b/pkg/proxy/ipvs/ipset_test.go @@ -179,6 +179,26 @@ func TestSyncIPSetEntries(t *testing.T) { currentEntries: []string{"80", "9090", "8081", "8082"}, expectedEntries: []string{"8080"}, }, + { // case 12 + set: &utilipset.IPSet{ + Name: "sctp-1", + }, + setType: utilipset.HashIPPort, + ipv6: false, + activeEntries: []string{"172.17.0.4,sctp:80"}, + currentEntries: nil, + expectedEntries: []string{"172.17.0.4,sctp:80"}, + }, + { // case 1 + set: &utilipset.IPSet{ + Name: "sctp-2", + }, + setType: utilipset.HashIPPort, + ipv6: true, + activeEntries: []string{"FE80::0202:B3FF:FE1E:8329,sctp:80"}, + currentEntries: []string{"FE80::0202:B3FF:FE1E:8329,sctp:80"}, + expectedEntries: []string{"FE80::0202:B3FF:FE1E:8329,sctp:80"}, + }, } for i := range testCases { diff --git a/pkg/proxy/ipvs/proxier.go b/pkg/proxy/ipvs/proxier.go index 35a65101815..359817b43fc 100644 --- a/pkg/proxy/ipvs/proxier.go +++ b/pkg/proxy/ipvs/proxier.go @@ -29,6 +29,7 @@ import ( "time" "github.com/golang/glog" + "github.com/ishidawataru/sctp" "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" @@ -47,6 +48,8 @@ import ( utilnet "k8s.io/kubernetes/pkg/util/net" utilsysctl "k8s.io/kubernetes/pkg/util/sysctl" utilexec "k8s.io/utils/exec" + + ) const ( @@ -128,6 +131,8 @@ var ipsetInfo = []struct { {kubeNodePortLocalSetTCP, utilipset.BitmapPort, false, kubeNodePortLocalSetTCPComment}, {kubeNodePortSetUDP, utilipset.BitmapPort, false, kubeNodePortSetUDPComment}, {kubeNodePortLocalSetUDP, utilipset.BitmapPort, false, kubeNodePortLocalSetUDPComment}, + {kubeNodePortSetSCTP, utilipset.BitmapPort, false, kubeNodePortSetSCTPComment}, + {kubeNodePortLocalSetSCTP, utilipset.BitmapPort, false, kubeNodePortLocalSetSCTPComment}, } // ipsetWithIptablesChain is the ipsets list with iptables source chain and the chain jump to @@ -152,6 +157,8 @@ var ipsetWithIptablesChain = []struct { {kubeNodePortSetTCP, string(KubeNodePortChain), string(KubeMarkMasqChain), "dst", "tcp"}, {kubeNodePortLocalSetUDP, string(KubeNodePortChain), "RETURN", "dst", "udp"}, {kubeNodePortSetUDP, string(KubeNodePortChain), string(KubeMarkMasqChain), "dst", "udp"}, + {kubeNodePortSetSCTP, string(kubeServicesChain), string(KubeNodePortChain), "dst", "sctp"}, + {kubeNodePortLocalSetSCTP, string(KubeNodePortChain), "RETURN", "dst", "sctp"}, } var ipvsModules = []string{ @@ -227,6 +234,7 @@ type Proxier struct { // networkInterfacer defines an interface for several net library functions. // Inject for test purpose. networkInterfacer utilproxy.NetworkInterfacer + sctpUserSpaceNode bool } // IPGetter helps get node network interface IP @@ -292,6 +300,7 @@ func NewProxier(ipt utiliptables.Interface, healthzServer healthcheck.HealthzUpdater, scheduler string, nodePortAddresses []string, + sctpUserSpaceNode bool, ) (*Proxier, error) { // Set the route_localnet sysctl we need for if err := sysctl.SetSysctl(sysctlRouteLocalnet, 1); err != nil { @@ -373,6 +382,7 @@ func NewProxier(ipt utiliptables.Interface, ipset: ipset, nodePortAddresses: nodePortAddresses, networkInterfacer: utilproxy.RealNetwork{}, + sctpUserSpaceNode: sctpUserSpaceNode, } // initialize ipsetList with all sets we needed proxier.ipsetList = make(map[string]*IPSet) @@ -805,7 +815,7 @@ func (proxier *Proxier) syncProxyRules() { for _, externalIP := range svcInfo.ExternalIPs { if local, err := utilproxy.IsLocalIP(externalIP); err != nil { glog.Errorf("can't determine if IP is local, assuming not: %v", err) - } else if local { + } else if local && (svcInfo.GetProtocol() != api.ProtocolSCTP || !proxier.sctpUserSpaceNode) { lp := utilproxy.LocalPort{ Description: "externalIP for " + svcNameString, IP: externalIP, @@ -1004,7 +1014,7 @@ func (proxier *Proxier) syncProxyRules() { if proxier.portsMap[lp] != nil { glog.V(4).Infof("Port %s was open before and is still needed", lp.String()) replacementPortsMap[lp] = proxier.portsMap[lp] - } else { + } else if svcInfo.GetProtocol() != api.ProtocolSCTP || !proxier.sctpUserSpaceNode { socket, err := proxier.portMapper.OpenLocalPort(&lp) if err != nil { glog.Errorf("can't open %s, skipping this nodePort: %v", lp.String(), err) @@ -1032,6 +1042,8 @@ func (proxier *Proxier) syncProxyRules() { nodePortSet = proxier.ipsetList[kubeNodePortSetTCP] case "udp": nodePortSet = proxier.ipsetList[kubeNodePortSetUDP] + case "sctp": + nodePortSet = proxier.ipsetList[kubeNodePortSetSCTP] default: // It should never hit glog.Errorf("Unsupported protocol type: %s", protocol) @@ -1052,6 +1064,8 @@ func (proxier *Proxier) syncProxyRules() { nodePortLocalSet = proxier.ipsetList[kubeNodePortLocalSetTCP] case "udp": nodePortLocalSet = proxier.ipsetList[kubeNodePortLocalSetUDP] + case "sctp": + nodePortLocalSet = proxier.ipsetList[kubeNodePortLocalSetSCTP] default: // It should never hit glog.Errorf("Unsupported protocol type: %s", protocol) @@ -1636,6 +1650,18 @@ func openLocalPort(lp *utilproxy.LocalPort) (utilproxy.Closeable, error) { return nil, err } socket = conn + case "sctp": + // SCTP is not supported by golang/net, or any other built-in lib, + // so we have to manage SCTP via a 3rd party lib. + sctpAddr, err := sctp.ResolveSCTPAddr("sctp", net.JoinHostPort(lp.IP, strconv.Itoa(lp.Port))) + if err != nil { + return nil, err + } + conn, err := sctp.ListenSCTP("sctp", sctpAddr) + if err != nil { + return nil, err + } + socket = conn default: return nil, fmt.Errorf("unknown protocol %q", lp.Protocol) } diff --git a/pkg/proxy/ipvs/proxier_test.go b/pkg/proxy/ipvs/proxier_test.go index 4f2ea198b7f..58328af7da7 100644 --- a/pkg/proxy/ipvs/proxier_test.go +++ b/pkg/proxy/ipvs/proxier_test.go @@ -171,6 +171,7 @@ func NewFakeProxier(ipt utiliptables.Interface, ipvs utilipvs.Interface, ipset u ipsetList: ipsetList, nodePortAddresses: make([]string, 0), networkInterfacer: proxyutiltest.NewFakeNetwork(), + sctpUserSpaceNode: false, } } @@ -1345,12 +1346,14 @@ func TestBuildServiceMapAddRemove(t *testing.T) { svc.Spec.ClusterIP = "172.16.55.4" svc.Spec.Ports = addTestPort(svc.Spec.Ports, "something", "UDP", 1234, 4321, 0) svc.Spec.Ports = addTestPort(svc.Spec.Ports, "somethingelse", "UDP", 1235, 5321, 0) + svc.Spec.Ports = addTestPort(svc.Spec.Ports, "somesctp", "SCTP", 1236, 6321, 0) }), makeTestService("somewhere-else", "node-port", func(svc *v1.Service) { svc.Spec.Type = v1.ServiceTypeNodePort svc.Spec.ClusterIP = "172.16.55.10" svc.Spec.Ports = addTestPort(svc.Spec.Ports, "blahblah", "UDP", 345, 678, 0) svc.Spec.Ports = addTestPort(svc.Spec.Ports, "moreblahblah", "TCP", 344, 677, 0) + svc.Spec.Ports = addTestPort(svc.Spec.Ports, "sctpblah", "SCTP", 343, 676, 0) }), makeTestService("somewhere", "load-balancer", func(svc *v1.Service) { svc.Spec.Type = v1.ServiceTypeLoadBalancer @@ -1358,8 +1361,9 @@ func TestBuildServiceMapAddRemove(t *testing.T) { svc.Spec.LoadBalancerIP = "5.6.7.8" svc.Spec.Ports = addTestPort(svc.Spec.Ports, "foobar", "UDP", 8675, 30061, 7000) svc.Spec.Ports = addTestPort(svc.Spec.Ports, "baz", "UDP", 8676, 30062, 7001) - svc.Status.LoadBalancer = v1.LoadBalancerStatus{ - Ingress: []v1.LoadBalancerIngress{ + svc.Spec.Ports = addTestPort(svc.Spec.Ports, "sctpfoo", "SCTP", 8677, 30063, 7002) + svc.Status.LoadBalancer = api.LoadBalancerStatus{ + Ingress: []api.LoadBalancerIngress{ {IP: "10.1.2.4"}, }, } @@ -1370,8 +1374,9 @@ func TestBuildServiceMapAddRemove(t *testing.T) { svc.Spec.LoadBalancerIP = "5.6.7.8" svc.Spec.Ports = addTestPort(svc.Spec.Ports, "foobar2", "UDP", 8677, 30063, 7002) svc.Spec.Ports = addTestPort(svc.Spec.Ports, "baz", "UDP", 8678, 30064, 7003) - svc.Status.LoadBalancer = v1.LoadBalancerStatus{ - Ingress: []v1.LoadBalancerIngress{ + svc.Spec.Ports = addTestPort(svc.Spec.Ports, "sctpbaz", "SCTP", 8679, 30065, 7004) + svc.Status.LoadBalancer = api.LoadBalancerStatus{ + Ingress: []api.LoadBalancerIngress{ {IP: "10.1.2.3"}, }, } @@ -1384,8 +1389,8 @@ func TestBuildServiceMapAddRemove(t *testing.T) { fp.OnServiceAdd(services[i]) } result := proxy.UpdateServiceMap(fp.serviceMap, fp.serviceChanges) - if len(fp.serviceMap) != 8 { - t.Errorf("expected service map length 8, got %v", fp.serviceMap) + if len(fp.serviceMap) != 12 { + t.Errorf("expected service map length 12, got %v", fp.serviceMap) } // The only-local-loadbalancer ones get added @@ -1455,6 +1460,11 @@ func TestBuildServiceMapServiceHeadless(t *testing.T) { svc.Spec.Type = v1.ServiceTypeClusterIP svc.Spec.ClusterIP = v1.ClusterIPNone }), + makeTestService("somewhere-else", "headless-sctp", func(svc *api.Service) { + svc.Spec.Type = api.ServiceTypeClusterIP + svc.Spec.ClusterIP = api.ClusterIPNone + svc.Spec.Ports = addTestPort(svc.Spec.Ports, "sip", "SCTP", 1235, 0, 0) + }), ) // Headless service should be ignored @@ -2620,6 +2630,76 @@ func Test_syncService(t *testing.T) { }, bindAddr: true, }, + { + // case 4, SCTP, old virtual server is same as new virtual server + oldVirtualServer: &utilipvs.VirtualServer{ + Address: net.ParseIP("1.2.3.4"), + Protocol: string(api.ProtocolSCTP), + Port: 80, + Scheduler: "rr", + Flags: utilipvs.FlagHashed, + }, + svcName: "foo", + newVirtualServer: &utilipvs.VirtualServer{ + Address: net.ParseIP("1.2.3.4"), + Protocol: string(api.ProtocolSCTP), + Port: 80, + Scheduler: "rr", + Flags: utilipvs.FlagHashed, + }, + bindAddr: false, + }, + { + // case 5, old virtual server is different from new virtual server + oldVirtualServer: &utilipvs.VirtualServer{ + Address: net.ParseIP("1.2.3.4"), + Protocol: string(api.ProtocolSCTP), + Port: 8080, + Scheduler: "rr", + Flags: utilipvs.FlagHashed, + }, + svcName: "bar", + newVirtualServer: &utilipvs.VirtualServer{ + Address: net.ParseIP("1.2.3.4"), + Protocol: string(api.ProtocolSCTP), + Port: 8080, + Scheduler: "rr", + Flags: utilipvs.FlagPersistent, + }, + bindAddr: false, + }, + { + // case 6, old virtual server is different from new virtual server + oldVirtualServer: &utilipvs.VirtualServer{ + Address: net.ParseIP("1.2.3.4"), + Protocol: string(api.ProtocolSCTP), + Port: 8080, + Scheduler: "rr", + Flags: utilipvs.FlagHashed, + }, + svcName: "bar", + newVirtualServer: &utilipvs.VirtualServer{ + Address: net.ParseIP("1.2.3.4"), + Protocol: string(api.ProtocolSCTP), + Port: 8080, + Scheduler: "wlc", + Flags: utilipvs.FlagHashed, + }, + bindAddr: false, + }, + { + // case 7, old virtual server is nil, and create new virtual server + oldVirtualServer: nil, + svcName: "baz", + newVirtualServer: &utilipvs.VirtualServer{ + Address: net.ParseIP("1.2.3.4"), + Protocol: string(api.ProtocolSCTP), + Port: 53, + Scheduler: "rr", + Flags: utilipvs.FlagHashed, + }, + bindAddr: true, + }, } for i := range testCases { @@ -2748,6 +2828,7 @@ func TestCleanLegacyService(t *testing.T) { nil, DefaultScheduler, make([]string, 0), + false, ) if err != nil { t.Errorf("Unexpected error: %v", err) diff --git a/pkg/proxy/service_test.go b/pkg/proxy/service_test.go index 61bfa1ee42a..35cc892afe2 100644 --- a/pkg/proxy/service_test.go +++ b/pkg/proxy/service_test.go @@ -114,6 +114,15 @@ func TestServiceToServiceMap(t *testing.T) { }), expected: map[ServicePortName]*BaseServiceInfo{}, }, + { + desc: "headless sctp service", + service: makeTestService("ns2", "headless", func(svc *api.Service) { + svc.Spec.Type = api.ServiceTypeClusterIP + svc.Spec.ClusterIP = api.ClusterIPNone + svc.Spec.Ports = addTestPort(svc.Spec.Ports, "sip", "SCTP", 7777, 0, 0) + }), + expected: map[ServicePortName]*BaseServiceInfo{}, + }, { desc: "headless service without port", service: makeTestService("ns2", "headless-without-port", func(svc *v1.Service) { diff --git a/pkg/proxy/userspace/proxysocket.go b/pkg/proxy/userspace/proxysocket.go index d16b002ac99..098f68c15aa 100644 --- a/pkg/proxy/userspace/proxysocket.go +++ b/pkg/proxy/userspace/proxysocket.go @@ -68,6 +68,8 @@ func newProxySocket(protocol v1.Protocol, ip net.IP, port int) (ProxySocket, err return nil, err } return &udpProxySocket{UDPConn: conn, port: port}, nil + case "SCTP": + return nil, fmt.Errorf("SCTP is not supported for user space proxy") } return nil, fmt.Errorf("unknown protocol %q", protocol) } diff --git a/pkg/proxy/util/port_test.go b/pkg/proxy/util/port_test.go index f6a4fccb7f4..7295fd5000c 100644 --- a/pkg/proxy/util/port_test.go +++ b/pkg/proxy/util/port_test.go @@ -38,6 +38,8 @@ func TestLocalPortString(t *testing.T) { {"IPv4 UDP", "1.2.3.4", 9999, "udp", "\"IPv4 UDP\" (1.2.3.4:9999/udp)"}, {"IPv4 TCP", "5.6.7.8", 1053, "tcp", "\"IPv4 TCP\" (5.6.7.8:1053/tcp)"}, {"IPv6 TCP", "2001:db8::1", 80, "tcp", "\"IPv6 TCP\" ([2001:db8::1]:80/tcp)"}, + {"IPv4 SCTP", "9.10.11.12", 7777, "sctp", "\"IPv4 SCTP\" (9.10.11.12:7777/sctp)"}, + {"IPv6 SCTP", "2001:db8::2", 80, "sctp", "\"IPv6 SCTP\" ([2001:db8::2]:80/sctp)"}, } for _, tc := range testCases { diff --git a/pkg/proxy/winkernel/proxier.go b/pkg/proxy/winkernel/proxier.go index d87aec75a0d..df46205a2d7 100644 --- a/pkg/proxy/winkernel/proxier.go +++ b/pkg/proxy/winkernel/proxier.go @@ -428,6 +428,9 @@ func Enum(p v1.Protocol) uint16 { if p == v1.ProtocolUDP { return 17 } + if p == api.ProtocolSCTP { + return 132 + } return 0 } diff --git a/pkg/proxy/winuserspace/proxysocket.go b/pkg/proxy/winuserspace/proxysocket.go index a000f27f5e7..df7d334d948 100644 --- a/pkg/proxy/winuserspace/proxysocket.go +++ b/pkg/proxy/winuserspace/proxysocket.go @@ -101,6 +101,8 @@ func newProxySocket(protocol v1.Protocol, ip net.IP, port int) (proxySocket, err return nil, err } return &udpProxySocket{UDPConn: conn, port: port}, nil + case "SCTP": + return nil, fmt.Errorf("SCTP is not supported for user space proxy") } return nil, fmt.Errorf("unknown protocol %q", protocol) } diff --git a/pkg/util/ipset/ipset.go b/pkg/util/ipset/ipset.go index 270987badf1..4bc4a739d38 100644 --- a/pkg/util/ipset/ipset.go +++ b/pkg/util/ipset/ipset.go @@ -153,7 +153,7 @@ type Entry struct { // Port is the entry's Port. Port int // Protocol is the entry's Protocol. The protocols of entries in the same ip set are all - // the same. The accepted protocols are TCP and UDP. + // the same. The accepted protocols are TCP, UDP and SCTP. Protocol string // Net is the entry's IP network address. Network address with zero prefix size can NOT // be stored. @@ -482,10 +482,10 @@ func IsNotFoundError(err error) bool { // checks if given protocol is supported in entry func validateProtocol(protocol string) bool { - if protocol == ProtocolTCP || protocol == ProtocolUDP { + if protocol == ProtocolTCP || protocol == ProtocolUDP || protocol == ProtocolSCTP { return true } - glog.Errorf("Invalid entry's protocol: %s, supported protocols are [%s, %s]", protocol, ProtocolTCP, ProtocolUDP) + glog.Errorf("Invalid entry's protocol: %s, supported protocols are [%s, %s]", protocol, ProtocolTCP, ProtocolUDP, ProtocolSCTP) return false } diff --git a/pkg/util/ipset/ipset_test.go b/pkg/util/ipset/ipset_test.go index 1e0ac8fe836..b089a629993 100644 --- a/pkg/util/ipset/ipset_test.go +++ b/pkg/util/ipset/ipset_test.go @@ -346,6 +346,22 @@ var testCases = []struct { }, delCombinedOutputLog: []string{"ipset", "del", "SIX", "80"}, }, + { // case 7 + entry: &Entry{ + IP: "192.168.1.2", + Port: 80, + Protocol: ProtocolSCTP, + SetType: HashIPPort, + }, + set: &IPSet{ + Name: "SETTE", + }, + addCombinedOutputLog: [][]string{ + {"ipset", "add", "SETTE", "192.168.1.2,sctp:80"}, + {"ipset", "add", "SETTE", "192.168.1.2,sctp:80", "-exist"}, + }, + delCombinedOutputLog: []string{"ipset", "del", "SETTE", "192.168.1.2,sctp:80"}, + }, } func TestAddEntry(t *testing.T) { @@ -755,6 +771,10 @@ func Test_validateFamily(t *testing.T) { family: "", valid: false, }, + { // case[8] + family: "sctp", + valid: false, + }, } for i := range testCases { valid := validateHashFamily(testCases[i].family) @@ -804,6 +824,10 @@ func Test_validateProtocol(t *testing.T) { protocol: "", valid: false, }, + { // case[8] + protocol: ProtocolSCTP, + valid: true, + }, } for i := range testCases { valid := validateProtocol(testCases[i].protocol) @@ -1403,14 +1427,14 @@ func TestValidateEntry(t *testing.T) { entry: &Entry{ SetType: HashIPPortIP, IP: "10.20.30.40", - Protocol: "SCTP ", + Protocol: ProtocolSCTP, Port: 8090, IP2: "10.20.30.41", }, set: &IPSet{ - Name: "unsupported-protocol", + Name: "sctp", }, - valid: false, + valid: true, }, { // case[20] entry: &Entry{ diff --git a/pkg/util/ipset/types.go b/pkg/util/ipset/types.go index cacfd6f8d8e..0c2d16daacc 100644 --- a/pkg/util/ipset/types.go +++ b/pkg/util/ipset/types.go @@ -49,6 +49,8 @@ const ( ProtocolTCP = "tcp" // ProtocolUDP represents UDP protocol. ProtocolUDP = "udp" + // ProtocolSCTP represents SCTP protocol. + ProtocolSCTP = "sctp" ) // ValidIPSetTypes defines the supported ip set type. diff --git a/pkg/util/ipvs/ipvs_linux.go b/pkg/util/ipvs/ipvs_linux.go index 86c42730d2b..02f9dce7760 100644 --- a/pkg/util/ipvs/ipvs_linux.go +++ b/pkg/util/ipvs/ipvs_linux.go @@ -252,6 +252,8 @@ func stringToProtocol(protocol string) uint16 { return uint16(syscall.IPPROTO_TCP) case "udp": return uint16(syscall.IPPROTO_UDP) + case "sctp": + return uint16(syscall.IPPROTO_SCTP) } return uint16(0) } @@ -263,6 +265,8 @@ func protocolToString(proto Protocol) string { return "TCP" case syscall.IPPROTO_UDP: return "UDP" + case syscall.IPPROTO_SCTP: + return "SCTP" } return "" } diff --git a/pkg/util/ipvs/ipvs_linux_test.go b/pkg/util/ipvs/ipvs_linux_test.go index 9a6de6c0b54..706073870d6 100644 --- a/pkg/util/ipvs/ipvs_linux_test.go +++ b/pkg/util/ipvs/ipvs_linux_test.go @@ -147,6 +147,30 @@ func Test_toVirtualServer(t *testing.T) { false, "", }, + { + libipvs.Service{ + Protocol: syscall.IPPROTO_SCTP, + Port: 80, + FWMark: 0, + SchedName: "", + Flags: uint32(FlagPersistent + FlagHashed), + Timeout: 0, + Netmask: 0xffffffff, + AddressFamily: syscall.AF_INET, + Address: nil, + PEName: "", + }, + VirtualServer{ + Address: net.ParseIP("0.0.0.0"), + Protocol: "SCTP", + Port: 80, + Scheduler: "", + Flags: ServiceFlags(FlagPersistent), + Timeout: 0, + }, + false, + "", + }, } for i := range Tests { @@ -359,10 +383,10 @@ func Test_toIPVSDestination(t *testing.T) { func Test_stringToProtocol(t *testing.T) { tests := []string{ - "TCP", "UDP", "ICMP", + "TCP", "UDP", "ICMP", "SCTP", } expected := []uint16{ - uint16(syscall.IPPROTO_TCP), uint16(syscall.IPPROTO_UDP), uint16(0), + uint16(syscall.IPPROTO_TCP), uint16(syscall.IPPROTO_UDP), uint16(0), uint16(syscall.IPPROTO_SCTP), } for i := range tests { got := stringToProtocol(tests[i]) @@ -375,10 +399,10 @@ func Test_stringToProtocol(t *testing.T) { func Test_protocolToString(t *testing.T) { tests := []Protocol{ - syscall.IPPROTO_TCP, syscall.IPPROTO_UDP, Protocol(0), + syscall.IPPROTO_TCP, syscall.IPPROTO_UDP, Protocol(0), syscall.IPPROTO_SCTP, } expected := []string{ - "TCP", "UDP", "", + "TCP", "UDP", "", "SCTP", } for i := range tests { got := protocolToString(tests[i]) diff --git a/pkg/util/ipvs/ipvs_test.go b/pkg/util/ipvs/ipvs_test.go index ae92f02bf8e..52e6b010d89 100644 --- a/pkg/util/ipvs/ipvs_test.go +++ b/pkg/util/ipvs/ipvs_test.go @@ -188,6 +188,46 @@ func TestVirtualServerEqual(t *testing.T) { equal: true, reason: "All fields equal", }, + { + svcA: &VirtualServer{ + Address: net.ParseIP("2012::beef"), + Protocol: "TCP", + Port: 0, + Scheduler: "wrr", + Flags: 0, + Timeout: 0, + }, + svcB: &VirtualServer{ + Address: net.ParseIP("2012::beeef"), + Protocol: "SCTP", + Port: 0, + Scheduler: "wrr", + Flags: 0, + Timeout: 0, + }, + equal: false, + reason: "Protocol not equal", + }, + { + svcA: &VirtualServer{ + Address: net.ParseIP("1.2.3.4"), + Protocol: "SCTP", + Port: 80, + Scheduler: "rr", + Flags: 0x1, + Timeout: 10800, + }, + svcB: &VirtualServer{ + Address: net.ParseIP("1.2.3.4"), + Protocol: "SCTP", + Port: 80, + Scheduler: "rr", + Flags: 0x1, + Timeout: 10800, + }, + equal: true, + reason: "All fields equal", + }, } for i := range Tests { diff --git a/pkg/util/ipvs/testing/fake_test.go b/pkg/util/ipvs/testing/fake_test.go index c66e8930f27..c07a2617f63 100644 --- a/pkg/util/ipvs/testing/fake_test.go +++ b/pkg/util/ipvs/testing/fake_test.go @@ -71,12 +71,22 @@ func TestVirtualServer(t *testing.T) { if err != nil { t.Errorf("Unexpected error when add virtual server, error: %v", err) } + // Add another virtual server + vs3 := &utilipvs.VirtualServer{ + Address: net.ParseIP("10::40"), + Port: uint16(7777), + Protocol: string("SCTP"), + } + err = fake.AddVirtualServer(vs3) + if err != nil { + t.Errorf("Unexpected error when add virtual server, error: %v", err) + } // List all virtual servers list, err := fake.GetVirtualServers() if err != nil { t.Errorf("Fail to list virtual servers, error: %v", err) } - if len(list) != 2 { + if len(list) != 3 { t.Errorf("Expect 2 virtual servers, got: %d", len(list)) } // Delete a virtual server diff --git a/staging/src/k8s.io/api/core/v1/generated.proto b/staging/src/k8s.io/api/core/v1/generated.proto index f89b7d858b1..ff47e1c8272 100644 --- a/staging/src/k8s.io/api/core/v1/generated.proto +++ b/staging/src/k8s.io/api/core/v1/generated.proto @@ -749,7 +749,7 @@ message ContainerPort { // This must be a valid port number, 0 < x < 65536. optional int32 containerPort = 3; - // Protocol for port. Must be UDP or TCP. + // Protocol for port. Must be UDP, TCP, or SCTP. // Defaults to "TCP". // +optional optional string protocol = 4; @@ -968,7 +968,7 @@ message EndpointPort { optional int32 port = 2; // The IP protocol for this port. - // Must be UDP or TCP. + // Must be UDP, TCP, or SCTP. // Default is TCP. // +optional optional string protocol = 3; @@ -4148,7 +4148,7 @@ message ServicePort { // +optional optional string name = 1; - // The IP protocol for this port. Supports "TCP" and "UDP". + // The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". // Default is TCP. // +optional optional string protocol = 2; diff --git a/staging/src/k8s.io/api/core/v1/types.go b/staging/src/k8s.io/api/core/v1/types.go index 96e7b30a3d6..6a6f1cab65b 100644 --- a/staging/src/k8s.io/api/core/v1/types.go +++ b/staging/src/k8s.io/api/core/v1/types.go @@ -861,6 +861,8 @@ const ( ProtocolTCP Protocol = "TCP" // ProtocolUDP is the UDP protocol. ProtocolUDP Protocol = "UDP" + // ProtocolSCTP is the SCTP protocol. + ProtocolSCTP Protocol = "SCTP" ) // Represents a Persistent Disk resource in Google Compute Engine. @@ -1662,7 +1664,7 @@ type ContainerPort struct { // Number of port to expose on the pod's IP address. // This must be a valid port number, 0 < x < 65536. ContainerPort int32 `json:"containerPort" protobuf:"varint,3,opt,name=containerPort"` - // Protocol for port. Must be UDP or TCP. + // Protocol for port. Must be UDP, TCP, or SCTP. "SCTP" is supported only if "HostPort" is not specified. // Defaults to "TCP". // +optional Protocol Protocol `json:"protocol,omitempty" protobuf:"bytes,4,opt,name=protocol,casttype=Protocol"` @@ -3515,7 +3517,7 @@ type ServicePort struct { // +optional Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"` - // The IP protocol for this port. Supports "TCP" and "UDP". + // The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". // Default is TCP. // +optional Protocol Protocol `json:"protocol,omitempty" protobuf:"bytes,2,opt,name=protocol,casttype=Protocol"` @@ -3729,7 +3731,7 @@ type EndpointPort struct { Port int32 `json:"port" protobuf:"varint,2,opt,name=port"` // The IP protocol for this port. - // Must be UDP or TCP. + // Must be UDP, TCP, or SCTP. // Default is TCP. // +optional Protocol Protocol `json:"protocol,omitempty" protobuf:"bytes,3,opt,name=protocol,casttype=Protocol"` diff --git a/staging/src/k8s.io/api/core/v1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/core/v1/types_swagger_doc_generated.go index 13d5c789800..e83641754c0 100644 --- a/staging/src/k8s.io/api/core/v1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/core/v1/types_swagger_doc_generated.go @@ -353,7 +353,7 @@ var map_ContainerPort = map[string]string{ "name": "If specified, this must be an IANA_SVC_NAME and unique within the pod. Each named port in a pod must have a unique name. Name for the port that can be referred to by services.", "hostPort": "Number of port to expose on the host. If specified, this must be a valid port number, 0 < x < 65536. If HostNetwork is specified, this must match ContainerPort. Most containers do not need this.", "containerPort": "Number of port to expose on the pod's IP address. This must be a valid port number, 0 < x < 65536.", - "protocol": "Protocol for port. Must be UDP or TCP. Defaults to \"TCP\".", + "protocol": "Protocol for port. Must be UDP, TCP, or SCTP. Defaults to \"TCP\".", "hostIP": "What host IP to bind the external port to.", } @@ -488,7 +488,7 @@ var map_EndpointPort = map[string]string{ "": "EndpointPort is a tuple that describes a single port.", "name": "The name of this port (corresponds to ServicePort.Name). Must be a DNS_LABEL. Optional only if one port is defined.", "port": "The port number of the endpoint.", - "protocol": "The IP protocol for this port. Must be UDP or TCP. Default is TCP.", + "protocol": "The IP protocol for this port. Must be UDP, TCP, or SCTP. Default is TCP.", } func (EndpointPort) SwaggerDoc() map[string]string { @@ -2058,7 +2058,7 @@ func (ServiceList) SwaggerDoc() map[string]string { var map_ServicePort = map[string]string{ "": "ServicePort contains information on service's port.", "name": "The name of this port within the service. This must be a DNS_LABEL. All ports within a ServiceSpec must have unique names. This maps to the 'Name' field in EndpointPort objects. Optional if only one ServicePort is defined on this service.", - "protocol": "The IP protocol for this port. Supports \"TCP\" and \"UDP\". Default is TCP.", + "protocol": "The IP protocol for this port. Supports \"TCP\", \"UDP\", and \"SCTP\". Default is TCP.", "port": "The port that will be exposed by this service.", "targetPort": "Number or name of the port to access on the pods targeted by the service. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. If this is a string, it will be looked up as a named port in the target Pod's container ports. If this is not specified, the value of the 'port' field is used (an identity map). This field is ignored for services with clusterIP=None, and should be omitted or set equal to the 'port' field. More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service", "nodePort": "The port on each node on which this service is exposed when type=NodePort or LoadBalancer. Usually assigned by the system. If specified, it will be allocated to the service if unused or else creation of the service will fail. Default is to auto-allocate a port if the ServiceType of this Service requires one. More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport", diff --git a/staging/src/k8s.io/api/extensions/v1beta1/generated.proto b/staging/src/k8s.io/api/extensions/v1beta1/generated.proto index 1d40fd6c7e0..1cba30a17c3 100644 --- a/staging/src/k8s.io/api/extensions/v1beta1/generated.proto +++ b/staging/src/k8s.io/api/extensions/v1beta1/generated.proto @@ -712,7 +712,7 @@ message NetworkPolicyPeer { // DEPRECATED 1.9 - This group version of NetworkPolicyPort is deprecated by networking/v1/NetworkPolicyPort. message NetworkPolicyPort { - // Optional. The protocol (TCP or UDP) which traffic must match. + // Optional. The protocol (TCP, UDP, or SCTP) which traffic must match. // If not specified, this field defaults to TCP. // +optional optional string protocol = 1; diff --git a/staging/src/k8s.io/api/extensions/v1beta1/types.go b/staging/src/k8s.io/api/extensions/v1beta1/types.go index a2b17822c29..b2780368d93 100644 --- a/staging/src/k8s.io/api/extensions/v1beta1/types.go +++ b/staging/src/k8s.io/api/extensions/v1beta1/types.go @@ -1275,7 +1275,7 @@ type NetworkPolicyEgressRule struct { // DEPRECATED 1.9 - This group version of NetworkPolicyPort is deprecated by networking/v1/NetworkPolicyPort. type NetworkPolicyPort struct { - // Optional. The protocol (TCP or UDP) which traffic must match. + // Optional. The protocol (TCP, UDP, or SCTP) which traffic must match. // If not specified, this field defaults to TCP. // +optional Protocol *v1.Protocol `json:"protocol,omitempty" protobuf:"bytes,1,opt,name=protocol,casttype=k8s.io/api/core/v1.Protocol"` diff --git a/staging/src/k8s.io/api/extensions/v1beta1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/extensions/v1beta1/types_swagger_doc_generated.go index c9ffadec1e7..268d2c4fcc9 100644 --- a/staging/src/k8s.io/api/extensions/v1beta1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/extensions/v1beta1/types_swagger_doc_generated.go @@ -419,7 +419,7 @@ func (NetworkPolicyPeer) SwaggerDoc() map[string]string { var map_NetworkPolicyPort = map[string]string{ "": "DEPRECATED 1.9 - This group version of NetworkPolicyPort is deprecated by networking/v1/NetworkPolicyPort.", - "protocol": "Optional. The protocol (TCP or UDP) which traffic must match. If not specified, this field defaults to TCP.", + "protocol": "Optional. The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.", "port": "If specified, the port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched.", } diff --git a/staging/src/k8s.io/api/networking/v1/generated.proto b/staging/src/k8s.io/api/networking/v1/generated.proto index eacf0ed90eb..4e068d08f03 100644 --- a/staging/src/k8s.io/api/networking/v1/generated.proto +++ b/staging/src/k8s.io/api/networking/v1/generated.proto @@ -138,7 +138,7 @@ message NetworkPolicyPeer { // NetworkPolicyPort describes a port to allow traffic on message NetworkPolicyPort { - // The protocol (TCP or UDP) which traffic must match. If not specified, this + // The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this // field defaults to TCP. // +optional optional string protocol = 1; diff --git a/staging/src/k8s.io/api/networking/v1/types.go b/staging/src/k8s.io/api/networking/v1/types.go index e1b81fdc7c4..ce70448d320 100644 --- a/staging/src/k8s.io/api/networking/v1/types.go +++ b/staging/src/k8s.io/api/networking/v1/types.go @@ -136,7 +136,7 @@ type NetworkPolicyEgressRule struct { // NetworkPolicyPort describes a port to allow traffic on type NetworkPolicyPort struct { - // The protocol (TCP or UDP) which traffic must match. If not specified, this + // The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this // field defaults to TCP. // +optional Protocol *v1.Protocol `json:"protocol,omitempty" protobuf:"bytes,1,opt,name=protocol,casttype=k8s.io/api/core/v1.Protocol"` diff --git a/staging/src/k8s.io/api/networking/v1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/networking/v1/types_swagger_doc_generated.go index af2553a9dfa..f4363bc09e2 100644 --- a/staging/src/k8s.io/api/networking/v1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/networking/v1/types_swagger_doc_generated.go @@ -90,7 +90,7 @@ func (NetworkPolicyPeer) SwaggerDoc() map[string]string { var map_NetworkPolicyPort = map[string]string{ "": "NetworkPolicyPort describes a port to allow traffic on", - "protocol": "The protocol (TCP or UDP) which traffic must match. If not specified, this field defaults to TCP.", + "protocol": "The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.", "port": "The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers.", }