github.com/ttpreport/gvisor-ligolo@v0.0.0-20240123134145-a858404967ba/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  	"github.com/ttpreport/gvisor-ligolo/pkg/cleanup"
    23  	"github.com/ttpreport/gvisor-ligolo/pkg/context"
    24  	"github.com/ttpreport/gvisor-ligolo/pkg/errors/linuxerr"
    25  	fileDescriptor "github.com/ttpreport/gvisor-ligolo/pkg/fd"
    26  	"github.com/ttpreport/gvisor-ligolo/pkg/fdnotifier"
    27  	"github.com/ttpreport/gvisor-ligolo/pkg/waiter"
    28  	"golang.org/x/sys/unix"
    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  }