github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/runsc/boot/portforward/portforward.go (about)

     1  // Copyright 2022 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package portforward holds the infrastructure to support the port forward command.
    16  package portforward
    17  
    18  import (
    19  	"fmt"
    20  	"sync"
    21  
    22  	"github.com/nicocha30/gvisor-ligolo/pkg/cleanup"
    23  	"github.com/nicocha30/gvisor-ligolo/pkg/context"
    24  )
    25  
    26  // proxyConn is a port forwarding connection. It is used to manage the
    27  // lifecycle of the connection and clean it up if necessary.
    28  type proxyConn interface {
    29  	// Name returns a name for this proxyConn.
    30  	Name() string
    31  	// Write performs a write on this connection.  Write should block on ErrWouldBlock, but it must
    32  	// listen to 'cancel' to interrupt blocked calls.
    33  	Write(ctx context.Context, buf []byte, cancel <-chan struct{}) (int, error)
    34  	// Read performs a read on this connection. Read should block on ErrWouldBlock by the underlying
    35  	// connection, but it must listen to `cancel` to interrupt blocked calls.
    36  	Read(ctx context.Context, buf []byte, cancel <-chan struct{}) (int, error)
    37  	// Close cleans up all resources owned by this proxyConn.
    38  	Close(ctx context.Context)
    39  }
    40  
    41  // Proxy controls copying data between two proxyConnections. Proxy takes ownership over the two
    42  // connections and is responsible for cleaning up their resources (i.e. calling their Close method).
    43  // Proxy(s) all run internal to the sandbox on the supervisor context.
    44  type Proxy struct {
    45  	// containerID for this proxy.
    46  	cid string
    47  	// "to" and "from" are the two connections on which this Proxy copies.
    48  	to         proxyConn
    49  	from       proxyConn
    50  	once       sync.Once
    51  	cancelFrom chan struct{}
    52  	cancelTo   chan struct{}
    53  	wg         sync.WaitGroup
    54  	cu         cleanup.Cleanup
    55  }
    56  
    57  // ProxyPair wraps the to/from arguments for NewProxy so that the user explicitly labels to/from.
    58  type ProxyPair struct {
    59  	To   proxyConn
    60  	From proxyConn
    61  }
    62  
    63  // NewProxy returns a new Proxy.
    64  func NewProxy(pair ProxyPair, cid string) *Proxy {
    65  	return &Proxy{
    66  		to:         pair.To,
    67  		from:       pair.From,
    68  		cid:        cid,
    69  		cancelTo:   make(chan struct{}, 1),
    70  		cancelFrom: make(chan struct{}, 1),
    71  	}
    72  }
    73  
    74  // readFrom reads from the application's vfs.FileDescription and writes to the shim.
    75  func (pf *Proxy) readFrom(ctx context.Context) error {
    76  	buf := make([]byte, 16384 /* 16kb buffer size */)
    77  	for ctx.Err() == nil {
    78  		if err := doCopy(ctx, pf.to, pf.from, buf, pf.cancelFrom); err != nil {
    79  			return fmt.Errorf("readFrom failed on container %q: %v", pf.cid, err)
    80  		}
    81  	}
    82  	return ctx.Err()
    83  }
    84  
    85  // writeTo writes to the application's vfs.FileDescription and reads from the shim.
    86  func (pf *Proxy) readTo(ctx context.Context) error {
    87  	buf := make([]byte, 16384 /* 16kb buffer size */)
    88  	for ctx.Err() == nil {
    89  		if err := doCopy(ctx, pf.from, pf.to, buf, pf.cancelTo); err != nil {
    90  			return fmt.Errorf("readTo failed on container %q: %v", pf.cid, err)
    91  		}
    92  	}
    93  	return ctx.Err()
    94  }
    95  
    96  // doCopy is the shared copy code for each of 'readFrom' and 'readTo'.
    97  func doCopy(ctx context.Context, dst, src proxyConn, buf []byte, cancel chan struct{}) error {
    98  	n, err := src.Read(ctx, buf, cancel)
    99  	if err != nil {
   100  		return fmt.Errorf("failed to read from %q: err %v", src.Name(), err)
   101  	}
   102  
   103  	_, err = dst.Write(ctx, buf[0:n], cancel)
   104  	if err != nil {
   105  		return fmt.Errorf("failed to write to %q: err %v", src.Name(), err)
   106  	}
   107  	return nil
   108  }
   109  
   110  // Start starts the proxy. On error on either end, the proxy cleans itself up by stopping both
   111  // connections.
   112  func (pf *Proxy) Start(ctx context.Context) {
   113  	pf.cu.Add(func() {
   114  		pf.to.Close(ctx)
   115  		pf.from.Close(ctx)
   116  	})
   117  
   118  	pf.wg.Add(1)
   119  	go func() {
   120  		if err := pf.readFrom(ctx); err != nil {
   121  			ctx.Warningf("Shutting down copy from %q to %q on container %s: %v", pf.from.Name(), pf.to.Name(), pf.cid, err)
   122  		}
   123  		pf.wg.Done()
   124  		pf.Close()
   125  	}()
   126  	pf.wg.Add(1)
   127  	go func() {
   128  		if err := pf.readTo(ctx); err != nil {
   129  			ctx.Warningf("Shutting down copy from %q to %q on container %s: %v", pf.to.Name(), pf.from.Name(), pf.cid, err)
   130  		}
   131  		pf.wg.Done()
   132  		pf.Close()
   133  	}()
   134  }
   135  
   136  // AddCleanup adds a cleanup to this Proxy's cleanup.
   137  func (pf *Proxy) AddCleanup(cu func()) {
   138  	pf.cu.Add(cu)
   139  }
   140  
   141  // Close cleans up the resources in this Proxy and blocks until all resources are cleaned up
   142  // and their goroutines exit.
   143  func (pf *Proxy) Close() {
   144  	pf.once.Do(func() {
   145  		pf.cu.Clean()
   146  		pf.cancelFrom <- struct{}{}
   147  		defer close(pf.cancelFrom)
   148  		pf.cancelTo <- struct{}{}
   149  		defer close(pf.cancelTo)
   150  	})
   151  	pf.wg.Wait()
   152  }