Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

kube-apiserver: use SO_REUSEPORT when creating listener #88893

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/kube-scheduler/app/options/insecure_serving_test.go
Expand Up @@ -253,7 +253,7 @@ type mockListener struct {
port int
}

func createMockListener(network, addr string) (net.Listener, int, error) {
func createMockListener(network, addr string, config net.ListenConfig) (net.Listener, int, error) {
host, portInt, err := splitHostIntPort(addr)
if err != nil {
return nil, 0, err
Expand Down
1 change: 1 addition & 0 deletions staging/src/k8s.io/apiserver/go.mod
Expand Up @@ -38,6 +38,7 @@ require (
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7
google.golang.org/grpc v1.26.0
gopkg.in/natefinch/lumberjack.v2 v2.0.0
gopkg.in/square/go-jose.v2 v2.2.2
Expand Down
40 changes: 39 additions & 1 deletion staging/src/k8s.io/apiserver/pkg/server/options/BUILD
Expand Up @@ -18,6 +18,8 @@ go_library(
"recommended.go",
"server_run_options.go",
"serving.go",
"serving_unix.go",
"serving_windows.go",
"serving_with_loopback.go",
"webhook.go",
],
Expand Down Expand Up @@ -91,7 +93,42 @@ go_library(
"//vendor/k8s.io/klog:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/common:go_default_library",
"//vendor/k8s.io/utils/path:go_default_library",
],
] + select({
"@io_bazel_rules_go//go/platform:android": [
"//vendor/golang.org/x/sys/unix:go_default_library",
],
"@io_bazel_rules_go//go/platform:darwin": [
"//vendor/golang.org/x/sys/unix:go_default_library",
],
"@io_bazel_rules_go//go/platform:dragonfly": [
"//vendor/golang.org/x/sys/unix:go_default_library",
],
"@io_bazel_rules_go//go/platform:freebsd": [
"//vendor/golang.org/x/sys/unix:go_default_library",
],
"@io_bazel_rules_go//go/platform:ios": [
"//vendor/golang.org/x/sys/unix:go_default_library",
],
"@io_bazel_rules_go//go/platform:linux": [
"//vendor/golang.org/x/sys/unix:go_default_library",
],
"@io_bazel_rules_go//go/platform:nacl": [
"//vendor/golang.org/x/sys/unix:go_default_library",
],
"@io_bazel_rules_go//go/platform:netbsd": [
"//vendor/golang.org/x/sys/unix:go_default_library",
],
"@io_bazel_rules_go//go/platform:openbsd": [
"//vendor/golang.org/x/sys/unix:go_default_library",
],
"@io_bazel_rules_go//go/platform:plan9": [
"//vendor/golang.org/x/sys/unix:go_default_library",
],
"@io_bazel_rules_go//go/platform:solaris": [
"//vendor/golang.org/x/sys/unix:go_default_library",
],
"//conditions:default": [],
}),
)

go_test(
Expand All @@ -104,6 +141,7 @@ go_test(
"etcd_test.go",
"server_run_options_test.go",
"serving_test.go",
"serving_unix_test.go",
],
data = glob(["testdata/**"]),
embed = [":go_default_library"],
Expand Down
Expand Up @@ -43,7 +43,7 @@ type DeprecatedInsecureServingOptions struct {

// ListenFunc can be overridden to create a custom listener, e.g. for mocking in tests.
// It defaults to options.CreateListener.
ListenFunc func(network, addr string) (net.Listener, int, error)
ListenFunc func(network, addr string, config net.ListenConfig) (net.Listener, int, error)
}

// Validate ensures that the insecure port values within the range of the port.
Expand Down Expand Up @@ -113,7 +113,7 @@ func (s *DeprecatedInsecureServingOptions) ApplyTo(c **server.DeprecatedInsecure
listen = s.ListenFunc
}
addr := net.JoinHostPort(s.BindAddress.String(), fmt.Sprintf("%d", s.BindPort))
s.Listener, s.BindPort, err = listen(s.BindNetwork, addr)
s.Listener, s.BindPort, err = listen(s.BindNetwork, addr, net.ListenConfig{})
if err != nil {
return fmt.Errorf("failed to create listener: %v", err)
}
Expand Down
23 changes: 20 additions & 3 deletions staging/src/k8s.io/apiserver/pkg/server/options/serving.go
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package options

import (
"context"
"fmt"
"net"
"path"
Expand Down Expand Up @@ -66,6 +67,10 @@ type SecureServingOptions struct {
// HTTP2MaxStreamsPerConnection is the limit that the api server imposes on each client.
// A value of zero means to use the default provided by golang's HTTP/2 support.
HTTP2MaxStreamsPerConnection int

// PermitPortSharing controls if SO_REUSEPORT is used when binding the port, which allows
// more than one instance to bind on the same address and port.
PermitPortSharing bool
}

type CertKey struct {
Expand Down Expand Up @@ -192,6 +197,10 @@ func (s *SecureServingOptions) AddFlags(fs *pflag.FlagSet) {
"The limit that the server gives to clients for "+
"the maximum number of streams in an HTTP/2 connection. "+
"Zero means to use golang's default.")

fs.BoolVar(&s.PermitPortSharing, "permit-port-sharing", s.PermitPortSharing,
"If true, SO_REUSEPORT will be used when binding the port, which allows "+
"more than one instance to bind on the same address and port. [default=false]")
invidian marked this conversation as resolved.
Show resolved Hide resolved
}

// ApplyTo fills up serving information in the server configuration.
Expand All @@ -206,7 +215,14 @@ func (s *SecureServingOptions) ApplyTo(config **server.SecureServingInfo) error
if s.Listener == nil {
var err error
addr := net.JoinHostPort(s.BindAddress.String(), strconv.Itoa(s.BindPort))
s.Listener, s.BindPort, err = CreateListener(s.BindNetwork, addr)

c := net.ListenConfig{}

if s.PermitPortSharing {
c.Control = permitPortReuse
}

s.Listener, s.BindPort, err = CreateListener(s.BindNetwork, addr, c)
if err != nil {
return fmt.Errorf("failed to create listener: %v", err)
}
Expand Down Expand Up @@ -317,11 +333,12 @@ func (s *SecureServingOptions) MaybeDefaultWithSelfSignedCerts(publicAddress str
return nil
}

func CreateListener(network, addr string) (net.Listener, int, error) {
func CreateListener(network, addr string, config net.ListenConfig) (net.Listener, int, error) {
if len(network) == 0 {
network = "tcp"
}
ln, err := net.Listen(network, addr)

ln, err := config.Listen(context.TODO(), network, addr)
if err != nil {
return nil, 0, fmt.Errorf("failed to listen on %v: %v", addr, err)
}
Expand Down
31 changes: 31 additions & 0 deletions staging/src/k8s.io/apiserver/pkg/server/options/serving_unix.go
@@ -0,0 +1,31 @@
// +build !windows

/*
Copyright 2020 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package options

import (
"syscall"

"golang.org/x/sys/unix"
)

func permitPortReuse(network, addr string, conn syscall.RawConn) error {
return conn.Control(func(fd uintptr) {
syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, unix.SO_REUSEPORT, 1)
})
}
@@ -0,0 +1,49 @@
// +build !windows

/*
Copyright 2020 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package options

import (
"net"
"testing"
)

func TestCreateListenerSharePort(t *testing.T) {
addr := "127.0.0.1:12345"
c := net.ListenConfig{Control: permitPortReuse}

if _, _, err := CreateListener("tcp", addr, c); err != nil {
t.Fatalf("failed to create listener: %v", err)
}

if _, _, err := CreateListener("tcp", addr, c); err != nil {
t.Fatalf("failed to create 2nd listener: %v", err)
}
}

func TestCreateListenerPreventUpgrades(t *testing.T) {
addr := "127.0.0.1:12346"

if _, _, err := CreateListener("tcp", addr, net.ListenConfig{}); err != nil {
t.Fatalf("failed to create listener: %v", err)
}

if _, _, err := CreateListener("tcp", addr, net.ListenConfig{Control: permitPortReuse}); err == nil {
t.Fatalf("creating second listener without port sharing should fail")
}
}
30 changes: 30 additions & 0 deletions staging/src/k8s.io/apiserver/pkg/server/options/serving_windows.go
@@ -0,0 +1,30 @@
// +build windows

/*
Copyright 2020 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package options

import (
"fmt"
"syscall"
)

// Windows only supports SO_REUSEADDR, which may cause undefined behavior, as
// there is no protection against port hijacking.
func permitPortReuse(network, address string, c syscall.RawConn) error {
return fmt.Errorf("port reuse is not supported on Windows")
}
2 changes: 1 addition & 1 deletion test/integration/etcd/server.go
Expand Up @@ -65,7 +65,7 @@ func StartRealMasterOrDie(t *testing.T, configFuncs ...func(*options.ServerRunOp
t.Fatal(err)
}

listener, _, err := genericapiserveroptions.CreateListener("tcp", "127.0.0.1:0")
listener, _, err := genericapiserveroptions.CreateListener("tcp", "127.0.0.1:0", net.ListenConfig{})
if err != nil {
t.Fatal(err)
}
Expand Down
2 changes: 1 addition & 1 deletion test/integration/examples/apiserver_test.go
Expand Up @@ -73,7 +73,7 @@ func TestAggregatedAPIServer(t *testing.T) {
defer os.Remove(wardleToKASKubeConfigFile)
wardleCertDir, _ := ioutil.TempDir("", "test-integration-wardle-server")
defer os.RemoveAll(wardleCertDir)
listener, wardlePort, err := genericapiserveroptions.CreateListener("tcp", "127.0.0.1:0")
listener, wardlePort, err := genericapiserveroptions.CreateListener("tcp", "127.0.0.1:0", net.ListenConfig{})
if err != nil {
t.Fatal(err)
}
Expand Down
2 changes: 1 addition & 1 deletion test/integration/framework/test_server.go
Expand Up @@ -81,7 +81,7 @@ func StartTestServer(t *testing.T, stopCh <-chan struct{}, setup TestServerSetup
t.Fatal(err)
}

listener, _, err := genericapiserveroptions.CreateListener("tcp", "127.0.0.1:0")
listener, _, err := genericapiserveroptions.CreateListener("tcp", "127.0.0.1:0", net.ListenConfig{})
if err != nil {
t.Fatal(err)
}
Expand Down