github.com/MerlinKodo/gvisor@v0.0.0-20231110090155-957f62ecf90e/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/MerlinKodo/gvisor/pkg/cleanup" 23 "github.com/MerlinKodo/gvisor/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 }