gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/runsc/boot/portforward/portforward_hostinet.go (about) 1 // Copyright 2023 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 16 17 import ( 18 "fmt" 19 "io" 20 "sync" 21 22 "golang.org/x/sys/unix" 23 "gvisor.dev/gvisor/pkg/cleanup" 24 "gvisor.dev/gvisor/pkg/context" 25 "gvisor.dev/gvisor/pkg/errors/linuxerr" 26 fileDescriptor "gvisor.dev/gvisor/pkg/fd" 27 "gvisor.dev/gvisor/pkg/fdnotifier" 28 "gvisor.dev/gvisor/pkg/waiter" 29 ) 30 31 var ( 32 localHost = [4]byte{127, 0, 0, 1} 33 ) 34 35 // hostInetConn allows reading and writing to a local host socket for hostinet. 36 // hostInetConn implments proxyConn. 37 type hostInetConn struct { 38 // wq is the WaitQueue registered with fdnotifier for this fd. 39 wq waiter.Queue 40 // fd is the file descriptor for the socket. 41 fd *fileDescriptor.FD 42 // port is the port on which to connect. 43 port uint16 44 // once makes sure we close only once. 45 once sync.Once 46 } 47 48 // NewHostInetConn creates a hostInetConn backed by a host socket on the localhost address. 49 func NewHostInetConn(port uint16) (proxyConn, error) { 50 // NOTE: Options must match sandbox seccomp filters. See filter/config.go 51 fd, err := unix.Socket(unix.AF_INET, unix.SOCK_STREAM|unix.SOCK_NONBLOCK|unix.SOCK_CLOEXEC, unix.IPPROTO_TCP) 52 if err != nil { 53 return nil, err 54 } 55 s := hostInetConn{ 56 fd: fileDescriptor.New(fd), 57 port: port, 58 } 59 60 cu := cleanup.Make(func() { 61 s.fd.Close() 62 }) 63 defer cu.Clean() 64 if err := fdnotifier.AddFD(int32(s.fd.FD()), &s.wq); err != nil { 65 return nil, err 66 } 67 cu.Add(func() { fdnotifier.RemoveFD(int32(s.fd.FD())) }) 68 sockAddr := &unix.SockaddrInet4{ 69 Addr: localHost, 70 Port: int(s.port), 71 } 72 73 if err := unix.Connect(s.fd.FD(), sockAddr); err != nil { 74 if err != unix.EINPROGRESS { 75 return nil, fmt.Errorf("unix.Connect: %w", err) 76 } 77 78 // Connect is in progress. Wait for the socket to be writable. 79 mask := waiter.WritableEvents 80 waitEntry, notifyCh := waiter.NewChannelEntry(mask) 81 s.eventRegister(&waitEntry) 82 defer s.eventUnregister(&waitEntry) 83 84 // Wait for connect to succeed. 85 // Check the current socket state and if not ready, wait for the event. 86 if fdnotifier.NonBlockingPoll(int32(s.fd.FD()), mask)&mask == 0 { 87 <-notifyCh 88 } 89 90 // Call getsockopt to get the connection result. 91 val, err := unix.GetsockoptInt(s.fd.FD(), unix.SOL_SOCKET, unix.SO_ERROR) 92 if err != nil { 93 return nil, fmt.Errorf("unix.GetSockoptInt: %w", err) 94 } 95 if val != 0 { 96 return nil, fmt.Errorf("unix.GetSockoptInt: %w", unix.Errno(val)) 97 } 98 } 99 cu.Release() 100 return &s, nil 101 } 102 103 func (s *hostInetConn) Name() string { 104 return fmt.Sprintf("localhost:port:%d", s.port) 105 } 106 107 // Read implements io.Reader.Read. It performs a blocking read on the fd. 108 func (s *hostInetConn) Read(ctx context.Context, buf []byte, cancel <-chan struct{}) (int, error) { 109 var ch chan struct{} 110 var e waiter.Entry 111 n, err := s.fd.Read(buf) 112 for ctx.Err() == nil && linuxerr.Equals(linuxerr.ErrWouldBlock, err) { 113 if ch == nil { 114 e, ch = waiter.NewChannelEntry(waiter.ReadableEvents | waiter.EventHUp | waiter.EventErr) 115 // Register for when the endpoint is writable or disconnected. 116 s.eventRegister(&e) 117 defer s.eventUnregister(&e) 118 } 119 select { 120 case <-ch: 121 case <-cancel: 122 return 0, io.EOF 123 case <-ctx.Done(): 124 return 0, ctx.Err() 125 } 126 n, err = s.fd.Read(buf) 127 } 128 return n, err 129 } 130 131 // Write implements io.Writer.Write. It performs a blocking write on the fd. 132 func (s *hostInetConn) Write(ctx context.Context, buf []byte, cancel <-chan struct{}) (int, error) { 133 var ch chan struct{} 134 var e waiter.Entry 135 n, err := s.fd.Write(buf) 136 for ctx.Err() == nil && linuxerr.Equals(linuxerr.ErrWouldBlock, err) { 137 if ch == nil { 138 e, ch = waiter.NewChannelEntry(waiter.WritableEvents | waiter.EventHUp | waiter.EventErr) 139 // Register for when the endpoint is writable or disconnected. 140 s.eventRegister(&e) 141 defer s.eventUnregister(&e) 142 143 } 144 select { 145 case <-ch: 146 case <-cancel: 147 return 0, io.EOF 148 case <-ctx.Done(): 149 return 0, ctx.Err() 150 } 151 n, err = s.fd.Write(buf) 152 } 153 return n, err 154 } 155 156 func (s *hostInetConn) eventRegister(e *waiter.Entry) { 157 s.wq.EventRegister(e) 158 fdnotifier.UpdateFD(int32(s.fd.FD())) 159 } 160 161 func (s *hostInetConn) eventUnregister(e *waiter.Entry) { 162 s.wq.EventUnregister(e) 163 fdnotifier.UpdateFD(int32(s.fd.FD())) 164 } 165 166 // Close closes the host socket and removes it from notifications. 167 func (s *hostInetConn) Close(_ context.Context) { 168 s.once.Do(func() { 169 fdnotifier.RemoveFD(int32(s.fd.FD())) 170 s.fd.Close() 171 }) 172 }