k8s.io/kubernetes@v1.29.3/test/e2e/storage/drivers/proxy/portproxy.go (about)

     1  /*
     2  Copyright 2020 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package proxy
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"io"
    24  	"net"
    25  	"net/http"
    26  	"sync"
    27  	"time"
    28  
    29  	v1 "k8s.io/api/core/v1"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	"k8s.io/apimachinery/pkg/runtime/schema"
    32  	"k8s.io/apimachinery/pkg/util/httpstream"
    33  	"k8s.io/client-go/kubernetes"
    34  	"k8s.io/client-go/kubernetes/scheme"
    35  	"k8s.io/client-go/rest"
    36  	"k8s.io/client-go/tools/portforward"
    37  	"k8s.io/client-go/transport/spdy"
    38  	"k8s.io/klog/v2"
    39  )
    40  
    41  // Maximum number of forwarded connections. In practice we don't
    42  // need more than one per sidecar and kubelet. Keeping this reasonably
    43  // small ensures that we don't establish connections through the apiserver
    44  // and the remote kernel which then aren't needed.
    45  //
    46  // The proxy code below establishes this many connections in advance,
    47  // without waiting for a client on the remote side. On the local side
    48  // a gRPC server will accept the same number of connections and then wait
    49  // for data from a future client.
    50  //
    51  // This approach has the advantage that a client on the remote side can
    52  // immediately start communicating, without the delay caused by establishing
    53  // the connection. That delay is large enough that clients like the
    54  // node-driver-registrar with a very small timeout for gRPC did indeed
    55  // time out unnecessarily.
    56  const maxConcurrentConnections = 10
    57  
    58  // This delay determines how quickly we notice when someone has
    59  // connected inside the cluster. With socat, we cannot make this too small
    60  // because otherwise we get many rejected connections. With the mock
    61  // driver as proxy that doesn't happen as long as we don't
    62  // ask for too many concurrent connections because the mock driver
    63  // keeps the listening port open at all times and the Linux
    64  // kernel automatically accepts our connection requests.
    65  const connectionPollInterval = 100 * time.Millisecond
    66  
    67  // Listen creates a listener which returns new connections whenever someone connects
    68  // to a socat or mock driver proxy instance running inside the given pod.
    69  //
    70  // socat must by started with "<listen>,fork TCP-LISTEN:<port>,reuseport"
    71  // for this to work. "<listen>" can be anything that accepts connections,
    72  // for example "UNIX-LISTEN:/csi/csi.sock". In this mode, socat will
    73  // accept exactly one connection on the given port for each connection
    74  // that socat itself accepted.
    75  //
    76  // Listening stops when the context is done or Close() is called.
    77  func Listen(ctx context.Context, clientset kubernetes.Interface, restConfig *rest.Config, addr Addr) (net.Listener, error) {
    78  	// We connect through port forwarding. Strictly
    79  	// speaking this is overkill because we don't need a local
    80  	// port. But this way we can reuse client-go/tools/portforward
    81  	// instead of having to replicate handleConnection
    82  	// in our own code.
    83  	restClient := clientset.CoreV1().RESTClient()
    84  	if restConfig.GroupVersion == nil {
    85  		restConfig.GroupVersion = &schema.GroupVersion{}
    86  	}
    87  	if restConfig.NegotiatedSerializer == nil {
    88  		restConfig.NegotiatedSerializer = scheme.Codecs
    89  	}
    90  
    91  	// The setup code around the actual portforward is from
    92  	// https://github.com/kubernetes/kubernetes/blob/c652ffbe4a29143623a1aaec39f745575f7e43ad/staging/src/k8s.io/kubectl/pkg/cmd/portforward/portforward.go
    93  	req := restClient.Post().
    94  		Resource("pods").
    95  		Namespace(addr.Namespace).
    96  		Name(addr.PodName).
    97  		SubResource("portforward")
    98  	transport, upgrader, err := spdy.RoundTripperFor(restConfig)
    99  	if err != nil {
   100  		return nil, fmt.Errorf("create round tripper: %w", err)
   101  	}
   102  	dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, "POST", req.URL())
   103  
   104  	prefix := fmt.Sprintf("port forwarding for %s", addr)
   105  	ctx, cancel := context.WithCancel(ctx)
   106  	l := &listener{
   107  		ctx:    ctx,
   108  		cancel: cancel,
   109  		addr:   addr,
   110  	}
   111  
   112  	var connectionsCreated int
   113  
   114  	runForwarding := func() {
   115  		klog.V(2).Infof("%s: starting connection polling", prefix)
   116  		defer klog.V(2).Infof("%s: connection polling ended", prefix)
   117  
   118  		tryConnect := time.NewTicker(connectionPollInterval)
   119  		defer tryConnect.Stop()
   120  		for {
   121  			select {
   122  			case <-ctx.Done():
   123  				return
   124  			case <-tryConnect.C:
   125  				func() {
   126  					l.mutex.Lock()
   127  					defer l.mutex.Unlock()
   128  
   129  					for i, c := range l.connections {
   130  						if c == nil {
   131  							klog.V(5).Infof("%s: trying to create a new connection #%d", prefix, connectionsCreated)
   132  							stream, err := dial(ctx, fmt.Sprintf("%s #%d", prefix, connectionsCreated), dialer, addr.Port)
   133  							if err != nil {
   134  								klog.Errorf("%s: no connection: %v", prefix, err)
   135  								return
   136  							}
   137  							// Make the connection available to Accept below.
   138  							klog.V(5).Infof("%s: created a new connection #%d", prefix, connectionsCreated)
   139  							c := &connection{
   140  								l:       l,
   141  								stream:  stream,
   142  								addr:    addr,
   143  								counter: connectionsCreated,
   144  							}
   145  							l.connections[i] = c
   146  							connectionsCreated++
   147  							return
   148  						}
   149  					}
   150  				}()
   151  			}
   152  		}
   153  	}
   154  
   155  	// Portforwarding and polling for connections run in the background.
   156  	go func() {
   157  		for {
   158  			running := false
   159  			pod, err := clientset.CoreV1().Pods(addr.Namespace).Get(ctx, addr.PodName, metav1.GetOptions{})
   160  			if err != nil {
   161  				klog.V(5).Infof("checking for container %q in pod %s/%s: %v", addr.ContainerName, addr.Namespace, addr.PodName, err)
   162  			}
   163  			for i, status := range pod.Status.ContainerStatuses {
   164  				if pod.Spec.Containers[i].Name == addr.ContainerName &&
   165  					status.State.Running != nil {
   166  					running = true
   167  					break
   168  				}
   169  			}
   170  
   171  			if running {
   172  				klog.V(2).Infof("container %q in pod %s/%s is running", addr.ContainerName, addr.Namespace, addr.PodName)
   173  				runForwarding()
   174  			}
   175  
   176  			select {
   177  			case <-ctx.Done():
   178  				return
   179  			// Sleep a bit before restarting. This is
   180  			// where we potentially wait for the pod to
   181  			// start.
   182  			case <-time.After(1 * time.Second):
   183  			}
   184  		}
   185  	}()
   186  
   187  	return l, nil
   188  }
   189  
   190  // Addr contains all relevant parameters for a certain port in a pod.
   191  // The container must be running before connections are attempted.
   192  type Addr struct {
   193  	Namespace, PodName, ContainerName string
   194  	Port                              int
   195  }
   196  
   197  var _ net.Addr = Addr{}
   198  
   199  func (a Addr) Network() string {
   200  	return "port-forwarding"
   201  }
   202  
   203  func (a Addr) String() string {
   204  	return fmt.Sprintf("%s/%s:%d", a.Namespace, a.PodName, a.Port)
   205  }
   206  
   207  type stream struct {
   208  	httpstream.Stream
   209  	streamConn httpstream.Connection
   210  }
   211  
   212  func dial(ctx context.Context, prefix string, dialer httpstream.Dialer, port int) (s *stream, finalErr error) {
   213  	streamConn, _, err := dialer.Dial(portforward.PortForwardProtocolV1Name)
   214  	if err != nil {
   215  		return nil, fmt.Errorf("dialer failed: %w", err)
   216  	}
   217  	requestID := "1"
   218  	defer func() {
   219  		if finalErr != nil {
   220  			streamConn.Close()
   221  		}
   222  	}()
   223  
   224  	// create error stream
   225  	headers := http.Header{}
   226  	headers.Set(v1.StreamType, v1.StreamTypeError)
   227  	headers.Set(v1.PortHeader, fmt.Sprintf("%d", port))
   228  	headers.Set(v1.PortForwardRequestIDHeader, requestID)
   229  
   230  	// We're not writing to this stream, just reading an error message from it.
   231  	// This happens asynchronously.
   232  	errorStream, err := streamConn.CreateStream(headers)
   233  	if err != nil {
   234  		return nil, fmt.Errorf("error creating error stream: %w", err)
   235  	}
   236  	errorStream.Close()
   237  	go func() {
   238  		message, err := io.ReadAll(errorStream)
   239  		switch {
   240  		case err != nil:
   241  			klog.Errorf("%s: error reading from error stream: %v", prefix, err)
   242  		case len(message) > 0:
   243  			klog.Errorf("%s: an error occurred connecting to the remote port: %v", prefix, string(message))
   244  		}
   245  	}()
   246  
   247  	// create data stream
   248  	headers.Set(v1.StreamType, v1.StreamTypeData)
   249  	dataStream, err := streamConn.CreateStream(headers)
   250  	if err != nil {
   251  		return nil, fmt.Errorf("error creating data stream: %w", err)
   252  	}
   253  
   254  	return &stream{
   255  		Stream:     dataStream,
   256  		streamConn: streamConn,
   257  	}, nil
   258  }
   259  
   260  func (s *stream) Close() {
   261  	s.Stream.Close()
   262  	s.streamConn.Close()
   263  }
   264  
   265  type listener struct {
   266  	addr   Addr
   267  	ctx    context.Context
   268  	cancel func()
   269  
   270  	mutex       sync.Mutex
   271  	connections [maxConcurrentConnections]*connection
   272  }
   273  
   274  var _ net.Listener = &listener{}
   275  
   276  func (l *listener) Close() error {
   277  	klog.V(5).Infof("forward listener for %s: closing", l.addr)
   278  	l.cancel()
   279  
   280  	l.mutex.Lock()
   281  	defer l.mutex.Unlock()
   282  	for _, c := range l.connections {
   283  		if c != nil {
   284  			c.stream.Close()
   285  		}
   286  	}
   287  
   288  	return nil
   289  }
   290  
   291  func (l *listener) Accept() (net.Conn, error) {
   292  	tryAccept := time.NewTicker(connectionPollInterval)
   293  	defer tryAccept.Stop()
   294  	for {
   295  		select {
   296  		case <-l.ctx.Done():
   297  			return nil, errors.New("listening was stopped")
   298  		case <-tryAccept.C:
   299  			conn := func() net.Conn {
   300  				l.mutex.Lock()
   301  				defer l.mutex.Unlock()
   302  
   303  				for _, c := range l.connections {
   304  					if c != nil && !c.accepted {
   305  						klog.V(5).Infof("forward listener for %s: got a new connection #%d", l.addr, c.counter)
   306  						c.accepted = true
   307  						return c
   308  					}
   309  				}
   310  				return nil
   311  			}()
   312  			if conn != nil {
   313  				return conn, nil
   314  			}
   315  		}
   316  	}
   317  }
   318  
   319  type connection struct {
   320  	l                *listener
   321  	stream           *stream
   322  	addr             Addr
   323  	counter          int
   324  	mutex            sync.Mutex
   325  	accepted, closed bool
   326  }
   327  
   328  var _ net.Conn = &connection{}
   329  
   330  func (c *connection) LocalAddr() net.Addr {
   331  	return c.addr
   332  }
   333  
   334  func (c *connection) RemoteAddr() net.Addr {
   335  	return c.addr
   336  }
   337  
   338  func (c *connection) SetDeadline(t time.Time) error {
   339  	return nil
   340  }
   341  
   342  func (c *connection) SetReadDeadline(t time.Time) error {
   343  	return nil
   344  }
   345  
   346  func (c *connection) SetWriteDeadline(t time.Time) error {
   347  	return nil
   348  }
   349  
   350  func (c *connection) Read(b []byte) (int, error) {
   351  	n, err := c.stream.Read(b)
   352  	if errors.Is(err, io.EOF) {
   353  		klog.V(5).Infof("forward connection #%d for %s: remote side closed the stream", c.counter, c.addr)
   354  	}
   355  	return n, err
   356  }
   357  
   358  func (c *connection) Write(b []byte) (int, error) {
   359  	n, err := c.stream.Write(b)
   360  	if errors.Is(err, io.EOF) {
   361  		klog.V(5).Infof("forward connection #%d for %s: remote side closed the stream", c.counter, c.addr)
   362  	}
   363  	return n, err
   364  }
   365  
   366  func (c *connection) Close() error {
   367  	c.mutex.Lock()
   368  	defer c.mutex.Unlock()
   369  	if !c.closed {
   370  		// Do the logging and book-keeping only once. The function itself may be called more than once.
   371  		klog.V(5).Infof("forward connection #%d for %s: closing our side", c.counter, c.addr)
   372  
   373  		c.l.mutex.Lock()
   374  		defer c.l.mutex.Unlock()
   375  		for i, c2 := range c.l.connections {
   376  			if c2 == c {
   377  				c.l.connections[i] = nil
   378  				break
   379  			}
   380  		}
   381  	}
   382  	c.stream.Close()
   383  
   384  	return nil
   385  }
   386  
   387  func (l *listener) Addr() net.Addr {
   388  	return l.addr
   389  }