mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Allow creating unix domain sockets on the host, behind a flag.
When enabled with `AllowUDS`, unix domain sockets can be created in the sandbox and bound on the host filesystem. The application can listen() and accept() on these sockets as usual. Accept'ed sockets will be donated to the sandbox, similar to how connect'ed sockets work. In order to make notifications like poll work, the gofer donates the host-bound socket FD to the sandbox, but the seccomp filters will (correctly) prevent the sandbox from calling listen and accept directly on that FD. Instead, listen and accept calls must go through the gofer. The donated host FD can should only be used to poll for new incoming connectins. Note that I changed the order of some of the Lisa RPCs in order to group Bind with the existing similar Connect method. This changes the RPC numbers in a backwards-incompatible way, but since nobody is using Lisa yet we are OK. It's better to make these cleanup changes now before we have users and are locked in. PiperOrigin-RevId: 447236441
This commit is contained in:
committed by
gVisor bot
parent
409f32743b
commit
d5002c6adc
@@ -28,6 +28,17 @@ go_template_instance(
|
||||
},
|
||||
)
|
||||
|
||||
go_template_instance(
|
||||
name = "bound_socket_fd_refs",
|
||||
out = "bound_socket_fd_refs.go",
|
||||
package = "lisafs",
|
||||
prefix = "boundSocketFD",
|
||||
template = "//pkg/refsvfs2:refs_template",
|
||||
types = {
|
||||
"T": "BoundSocketFD",
|
||||
},
|
||||
)
|
||||
|
||||
go_template_instance(
|
||||
name = "node_fd_refs",
|
||||
out = "node_fd_refs.go",
|
||||
@@ -66,6 +77,7 @@ go_template_instance(
|
||||
go_library(
|
||||
name = "lisafs",
|
||||
srcs = [
|
||||
"bound_socket_fd_refs.go",
|
||||
"channel.go",
|
||||
"client.go",
|
||||
"client_file.go",
|
||||
|
||||
@@ -418,6 +418,44 @@ func (f *ClientFD) Flush(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// BindAt makes the BindAt RPC.
|
||||
func (f *ClientFD) BindAt(ctx context.Context, sockType linux.SockType, name string) (Inode, *ClientBoundSocketFD, error) {
|
||||
req := BindAtReq{
|
||||
DirFD: f.fd,
|
||||
SockType: primitive.Uint32(sockType),
|
||||
Name: SizedString(name),
|
||||
}
|
||||
var (
|
||||
resp BindAtResp
|
||||
hostSocketFD [1]int
|
||||
)
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
err := f.client.SndRcvMessage(BindAt, uint32(req.SizeBytes()), req.MarshalBytes, resp.CheckedUnmarshal, hostSocketFD[:], req.String, resp.String)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
if err == nil && hostSocketFD[0] < 0 {
|
||||
// No host socket fd? We can't proceed.
|
||||
// Clean up any resources the gofer sent to us.
|
||||
if resp.Child.ControlFD.Ok() {
|
||||
f.client.CloseFDBatched(ctx, resp.Child.ControlFD)
|
||||
}
|
||||
if resp.BoundSocketFD.Ok() {
|
||||
f.client.CloseFDBatched(ctx, resp.BoundSocketFD)
|
||||
}
|
||||
err = unix.EBADF
|
||||
}
|
||||
if err != nil {
|
||||
return Inode{}, nil, err
|
||||
}
|
||||
|
||||
cbsFD := &ClientBoundSocketFD{
|
||||
fd: resp.BoundSocketFD,
|
||||
notificationFD: int32(hostSocketFD[0]),
|
||||
client: f.client,
|
||||
}
|
||||
|
||||
return resp.Child, cbsFD, err
|
||||
}
|
||||
|
||||
// Connect makes the Connect RPC.
|
||||
func (f *ClientFD) Connect(ctx context.Context, sockType linux.SockType) (int, error) {
|
||||
req := ConnectReq{FD: f.fd, SockType: uint32(sockType)}
|
||||
@@ -532,3 +570,58 @@ func (f *ClientFD) RemoveXattr(ctx context.Context, name string) error {
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
return err
|
||||
}
|
||||
|
||||
// ClientBoundSocketFD corresponds to a bound socket on the server.
|
||||
//
|
||||
// All fields are immutable.
|
||||
type ClientBoundSocketFD struct {
|
||||
// fd is the FDID of the bound socket on the server.
|
||||
fd FDID
|
||||
|
||||
// notificationFD is the host FD that can be used to notify when new
|
||||
// clients connect to the socket.
|
||||
notificationFD int32
|
||||
|
||||
client *Client
|
||||
}
|
||||
|
||||
// Close closes the host and gofer-backed FDs associated to this bound socket.
|
||||
func (f *ClientBoundSocketFD) Close(ctx context.Context) {
|
||||
_ = unix.Close(int(f.notificationFD))
|
||||
f.client.CloseFDBatched(ctx, f.fd)
|
||||
}
|
||||
|
||||
// NotificationFD is a host FD that can be used to notify when new clients
|
||||
// connect to the socket.
|
||||
func (f *ClientBoundSocketFD) NotificationFD() int32 {
|
||||
return f.notificationFD
|
||||
}
|
||||
|
||||
// Listen makes a Listen RPC.
|
||||
func (f *ClientBoundSocketFD) Listen(ctx context.Context, backlog int32) error {
|
||||
req := ListenReq{
|
||||
FD: f.fd,
|
||||
Backlog: backlog,
|
||||
}
|
||||
var resp ListenResp
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
err := f.client.SndRcvMessage(Listen, uint32(req.SizeBytes()), req.MarshalBytes, resp.CheckedUnmarshal, nil, req.String, resp.String)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
return err
|
||||
}
|
||||
|
||||
// Accept makes an Accept RPC.
|
||||
func (f *ClientBoundSocketFD) Accept(ctx context.Context) (int, error) {
|
||||
req := AcceptReq{
|
||||
FD: f.fd,
|
||||
}
|
||||
var resp AcceptResp
|
||||
var hostSocketFD [1]int
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
err := f.client.SndRcvMessage(Accept, uint32(req.SizeBytes()), req.MarshalBytes, resp.CheckedUnmarshal, hostSocketFD[:], req.String, resp.String)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
if err == nil && hostSocketFD[0] < 0 {
|
||||
err = unix.EBADF
|
||||
}
|
||||
return hostSocketFD[0], err
|
||||
}
|
||||
|
||||
@@ -297,6 +297,22 @@ func (c *Connection) lookupOpenFD(id FDID) (*OpenFD, error) {
|
||||
return ofd, nil
|
||||
}
|
||||
|
||||
// lookupBoundSocketFD retrieves the boundSockedFD identified by id on this
|
||||
// connection. On success, the caller gains a ref on the FD.
|
||||
func (c *Connection) lookupBoundSocketFD(id FDID) (*BoundSocketFD, error) {
|
||||
fd, err := c.lookupFD(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bsfd, ok := fd.(*BoundSocketFD)
|
||||
if !ok {
|
||||
fd.DecRef(nil)
|
||||
return nil, unix.EINVAL
|
||||
}
|
||||
return bsfd, nil
|
||||
}
|
||||
|
||||
// insertFD inserts the passed fd into the internal datastructure to track FDs.
|
||||
// The caller must hold a ref on fd which is transferred to the connection.
|
||||
func (c *Connection) insertFD(fd genericFD) FDID {
|
||||
|
||||
+87
-1
@@ -36,7 +36,7 @@ func (f FDID) Ok() bool {
|
||||
return f != InvalidFDID
|
||||
}
|
||||
|
||||
// genericFD can represent a ControlFD or OpenFD.
|
||||
// genericFD can represent any type of FD.
|
||||
type genericFD interface {
|
||||
refsvfs2.RefCounter
|
||||
}
|
||||
@@ -283,6 +283,54 @@ func (fd *OpenFD) Init(cfd *ControlFD, flags uint32, impl OpenFDImpl) {
|
||||
cfd.openFDsMu.Unlock()
|
||||
}
|
||||
|
||||
// BoundSocketFD represents a bound socket on the server.
|
||||
//
|
||||
// Reference Model:
|
||||
// * A BoundSocketFD takes a reference on the control FD it is bound to.
|
||||
type BoundSocketFD struct {
|
||||
boundSocketFDRefs
|
||||
|
||||
// All the following fields are immutable.
|
||||
|
||||
// controlFD is the ControlFD on which this FD was bound. BoundSocketFD
|
||||
// holds a ref on controlFD for its entire lifetime.
|
||||
controlFD *ControlFD
|
||||
|
||||
// id is the unique FD identifier which identifies this FD on its connection.
|
||||
id FDID
|
||||
|
||||
// impl is the socket FD implementation which embeds this struct. It
|
||||
// contains all the implementation specific details.
|
||||
impl BoundSocketFDImpl
|
||||
}
|
||||
|
||||
var _ genericFD = (*BoundSocketFD)(nil)
|
||||
|
||||
// ControlFD returns the control FD on which this FD was bound.
|
||||
func (fd *BoundSocketFD) ControlFD() ControlFDImpl {
|
||||
return fd.controlFD.impl
|
||||
}
|
||||
|
||||
// DecRef implements refsvfs2.RefCounter.DecRef. Note that the context
|
||||
// parameter should never be used. It exists solely to comply with the
|
||||
// refsvfs2.RefCounter interface.
|
||||
func (fd *BoundSocketFD) DecRef(context.Context) {
|
||||
fd.boundSocketFDRefs.DecRef(func() {
|
||||
fd.controlFD.DecRef(nil) // Drop the ref on the control FD.
|
||||
fd.impl.Close()
|
||||
})
|
||||
}
|
||||
|
||||
// Init must be called before first use of fd.
|
||||
func (fd *BoundSocketFD) Init(cfd *ControlFD, impl BoundSocketFDImpl) {
|
||||
// Initialize fd with 1 ref which is transferred to c via c.insertFD().
|
||||
fd.boundSocketFDRefs.InitRefs()
|
||||
fd.controlFD = cfd
|
||||
fd.id = cfd.conn.insertFD(fd)
|
||||
fd.impl = impl
|
||||
cfd.IncRef() // Holds a ref on cfd for its lifetime.
|
||||
}
|
||||
|
||||
// There are four different types of guarantees provided:
|
||||
//
|
||||
// none: There is no concurrency guarantee. The method may be invoked
|
||||
@@ -436,6 +484,16 @@ type ControlFDImpl interface {
|
||||
// On the server, Connect has a read concurrency guarantee.
|
||||
Connect(sockType uint32) (int, error)
|
||||
|
||||
// BindAt creates a host unix domain socket of type sockType, bound to
|
||||
// the given namt of type sockType, bound to the given name. It returns
|
||||
// a ControlFD that can be used for path operations on the socket, a
|
||||
// BoundSocketFD that can be used to Accept/Listen on the socket, and a
|
||||
// host FD that can be used for event notifications (like new
|
||||
// connections).
|
||||
//
|
||||
// On the server, BindAt has a write concurrency guarantee.
|
||||
BindAt(name string, sockType uint32) (*ControlFD, linux.Statx, *BoundSocketFD, int, error)
|
||||
|
||||
// UnlinkAt the file identified by name in this directory.
|
||||
//
|
||||
// Flags are Linux unlinkat(2) flags.
|
||||
@@ -560,3 +618,31 @@ type OpenFDImpl interface {
|
||||
// On the server, Renamed has a global concurrency guarantee.
|
||||
Renamed()
|
||||
}
|
||||
|
||||
// BoundSocketFDImpl represents a socket on the host filesystem that has been
|
||||
// created by the sandboxed application via Bind.
|
||||
type BoundSocketFDImpl interface {
|
||||
// FD returns a pointer to the embedded BoundSocketFD.
|
||||
FD() *BoundSocketFD
|
||||
|
||||
// Listen marks the socket as accepting incoming connections.
|
||||
//
|
||||
// On the server, Listen has a read concurrency guarantee.
|
||||
Listen(backlog int32) error
|
||||
|
||||
// Accept takes the first pending connection and creates a new socket
|
||||
// for it. The new socket FD is returned along with the peer address of
|
||||
// the connecting socket (which may be empty string).
|
||||
//
|
||||
// On the server, Accept has a read concurrency guarantee.
|
||||
Accept() (int, string, error)
|
||||
|
||||
// Close should clean up resources used by the bound socket FD
|
||||
// implementation.
|
||||
//
|
||||
// Close is called after all references on the FD have been dropped and its
|
||||
// FDID has been released.
|
||||
//
|
||||
// On the server, Close has no concurrency guarantee.
|
||||
Close()
|
||||
}
|
||||
|
||||
+114
-1
@@ -67,7 +67,6 @@ var handlers = [...]RPCHandler{
|
||||
FAllocate: FAllocateHandler,
|
||||
ReadLinkAt: ReadLinkAtHandler,
|
||||
Flush: FlushHandler,
|
||||
Connect: ConnectHandler,
|
||||
UnlinkAt: UnlinkAtHandler,
|
||||
RenameAt: RenameAtHandler,
|
||||
Getdents64: Getdents64Handler,
|
||||
@@ -75,6 +74,10 @@ var handlers = [...]RPCHandler{
|
||||
FSetXattr: FSetXattrHandler,
|
||||
FListXattr: FListXattrHandler,
|
||||
FRemoveXattr: FRemoveXattrHandler,
|
||||
Connect: ConnectHandler,
|
||||
BindAt: BindAtHandler,
|
||||
Listen: ListenHandler,
|
||||
Accept: AcceptHandler,
|
||||
}
|
||||
|
||||
// ErrorHandler handles Error message.
|
||||
@@ -1068,6 +1071,116 @@ func ConnectHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32
|
||||
return 0, comm.DonateFD(sock)
|
||||
}
|
||||
|
||||
// BindAtHandler handles the BindAt RPC.
|
||||
func BindAtHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
||||
var req BindAtReq
|
||||
if _, ok := req.CheckedUnmarshal(comm.PayloadBuf(payloadLen)); !ok {
|
||||
return 0, unix.EIO
|
||||
}
|
||||
|
||||
name := string(req.Name)
|
||||
if err := checkSafeName(name); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
dir, err := c.lookupControlFD(req.DirFD)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer dir.DecRef(nil)
|
||||
|
||||
if !dir.IsDir() {
|
||||
return 0, unix.ENOTDIR
|
||||
}
|
||||
|
||||
var (
|
||||
childFD *ControlFD
|
||||
childStat linux.Statx
|
||||
boundSocketFD *BoundSocketFD
|
||||
hostSocketFD int
|
||||
)
|
||||
if err := dir.safelyWrite(func() error {
|
||||
if dir.node.isDeleted() {
|
||||
return unix.EINVAL
|
||||
}
|
||||
childFD, childStat, boundSocketFD, hostSocketFD, err = dir.impl.BindAt(name, uint32(req.SockType))
|
||||
return err
|
||||
}); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if err := comm.DonateFD(hostSocketFD); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
resp := BindAtResp{
|
||||
Child: Inode{
|
||||
ControlFD: childFD.id,
|
||||
Stat: childStat,
|
||||
},
|
||||
BoundSocketFD: boundSocketFD.id,
|
||||
}
|
||||
respLen := uint32(resp.SizeBytes())
|
||||
resp.MarshalUnsafe(comm.PayloadBuf(respLen))
|
||||
return respLen, nil
|
||||
}
|
||||
|
||||
// ListenHandler handles the Listen RPC.
|
||||
func ListenHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
||||
var req ListenReq
|
||||
if _, ok := req.CheckedUnmarshal(comm.PayloadBuf(payloadLen)); !ok {
|
||||
return 0, unix.EIO
|
||||
}
|
||||
sock, err := c.lookupBoundSocketFD(req.FD)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := sock.controlFD.safelyRead(func() error {
|
||||
if sock.controlFD.node.isDeleted() {
|
||||
return unix.EINVAL
|
||||
}
|
||||
return sock.impl.Listen(req.Backlog)
|
||||
}); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// AcceptHandler handles the Accept RPC.
|
||||
func AcceptHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
||||
var req AcceptReq
|
||||
if _, ok := req.CheckedUnmarshal(comm.PayloadBuf(payloadLen)); !ok {
|
||||
return 0, unix.EIO
|
||||
}
|
||||
sock, err := c.lookupBoundSocketFD(req.FD)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var (
|
||||
newSock int
|
||||
peerAddr string
|
||||
)
|
||||
if err := sock.controlFD.safelyRead(func() error {
|
||||
if sock.controlFD.node.isDeleted() {
|
||||
return unix.EINVAL
|
||||
}
|
||||
var err error
|
||||
newSock, peerAddr, err = sock.impl.Accept()
|
||||
return err
|
||||
}); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := comm.DonateFD(newSock); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
resp := AcceptResp{
|
||||
PeerAddr: SizedString(peerAddr),
|
||||
}
|
||||
respLen := uint32(resp.SizeBytes())
|
||||
resp.MarshalBytes(comm.PayloadBuf(respLen))
|
||||
return respLen, nil
|
||||
}
|
||||
|
||||
// UnlinkAtHandler handles the UnlinkAt RPC.
|
||||
func UnlinkAtHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
||||
if c.readonly {
|
||||
|
||||
@@ -163,6 +163,15 @@ const (
|
||||
|
||||
// FRemoveXattr is analogous to fremovexattr(2).
|
||||
FRemoveXattr MID = 28
|
||||
|
||||
// BindAt is analogous to bind(2).
|
||||
BindAt MID = 29
|
||||
|
||||
// Listen is analogous to listen(2).
|
||||
Listen MID = 30
|
||||
|
||||
// Accept is analogous to accept4(2).
|
||||
Accept MID = 31
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -1278,6 +1287,116 @@ func (*ConnectResp) String() string {
|
||||
return "ConnectResp{}"
|
||||
}
|
||||
|
||||
// BindAtReq is used to make BindAt requests.
|
||||
type BindAtReq struct {
|
||||
DirFD FDID
|
||||
SockType primitive.Uint32
|
||||
Name SizedString
|
||||
}
|
||||
|
||||
// SizeBytes implements marshal.Marshallable.SizeBytes.
|
||||
func (b *BindAtReq) SizeBytes() int {
|
||||
return b.DirFD.SizeBytes() + b.SockType.SizeBytes() + b.Name.SizeBytes()
|
||||
}
|
||||
|
||||
// MarshalBytes implements marshal.Marshallable.MarshalBytes.
|
||||
func (b *BindAtReq) MarshalBytes(dst []byte) []byte {
|
||||
dst = b.DirFD.MarshalUnsafe(dst)
|
||||
dst = b.SockType.MarshalUnsafe(dst)
|
||||
return b.Name.MarshalBytes(dst)
|
||||
}
|
||||
|
||||
// CheckedUnmarshal implements marshal.CheckedMarshallable.CheckedUnmarshal.
|
||||
func (b *BindAtReq) CheckedUnmarshal(src []byte) ([]byte, bool) {
|
||||
b.Name = ""
|
||||
if b.SizeBytes() > len(src) {
|
||||
return src, false
|
||||
}
|
||||
srcRemain := b.DirFD.UnmarshalUnsafe(src)
|
||||
srcRemain = b.SockType.UnmarshalUnsafe(srcRemain)
|
||||
if srcRemain, ok := b.Name.CheckedUnmarshal(srcRemain); ok {
|
||||
return srcRemain, ok
|
||||
}
|
||||
return src, false
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer.String.
|
||||
func (b *BindAtReq) String() string {
|
||||
return fmt.Sprintf("BindAtReq{DirFD: %d, SockType: %d, Name: %q}", b.DirFD, b.SockType, b.Name)
|
||||
}
|
||||
|
||||
// BindAtResp is used to communicate BindAt response.
|
||||
//
|
||||
// +marshal boundCheck
|
||||
type BindAtResp struct {
|
||||
Child Inode
|
||||
BoundSocketFD FDID
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer.String.
|
||||
func (b *BindAtResp) String() string {
|
||||
return fmt.Sprintf("BindAtResp{Child: %+v, BoundSocketFD: %v}", b.Child, b.BoundSocketFD)
|
||||
}
|
||||
|
||||
// ListenReq is used to make Listen requests.
|
||||
//
|
||||
// +marshal boundCheck
|
||||
type ListenReq struct {
|
||||
FD FDID
|
||||
Backlog int32
|
||||
_ uint32
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer.String.
|
||||
func (l *ListenReq) String() string {
|
||||
return fmt.Sprintf("ListenReq{FD: %v, Backlog: %d}", l.FD, l.Backlog)
|
||||
}
|
||||
|
||||
// ListenResp is an empty response to ListenResp.
|
||||
type ListenResp struct{ EmptyMessage }
|
||||
|
||||
// String implements fmt.Stringer.String.
|
||||
func (*ListenResp) String() string {
|
||||
return "ListenResp{}"
|
||||
}
|
||||
|
||||
// AcceptReq is used to make AcceptRequests.
|
||||
//
|
||||
// +marshal boundCheck
|
||||
type AcceptReq struct {
|
||||
FD FDID
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer.String.
|
||||
func (a *AcceptReq) String() string {
|
||||
return fmt.Sprintf("AcceptReq{FD: %v}", a.FD)
|
||||
}
|
||||
|
||||
// AcceptResp is an empty response to AcceptResp.
|
||||
type AcceptResp struct {
|
||||
PeerAddr SizedString
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer.String.
|
||||
func (a *AcceptResp) String() string {
|
||||
return fmt.Sprintf("AcceptResp{PeerAddr: %s}", a.PeerAddr)
|
||||
}
|
||||
|
||||
// SizeBytes implements marshal.Marshallable.SizeBytes.
|
||||
func (a *AcceptResp) SizeBytes() int {
|
||||
return a.PeerAddr.SizeBytes()
|
||||
}
|
||||
|
||||
// MarshalBytes implements marshal.Marshallable.MarshalBytes.
|
||||
func (a *AcceptResp) MarshalBytes(dst []byte) []byte {
|
||||
return a.PeerAddr.MarshalBytes(dst)
|
||||
}
|
||||
|
||||
// CheckedUnmarshal implements marshal.CheckedMarshallable.CheckedUnmarshal.
|
||||
func (a *AcceptResp) CheckedUnmarshal(src []byte) ([]byte, bool) {
|
||||
return a.PeerAddr.CheckedUnmarshal(src)
|
||||
}
|
||||
|
||||
// UnlinkAtReq is used to make UnlinkAt request.
|
||||
type UnlinkAtReq struct {
|
||||
DirFD FDID
|
||||
|
||||
@@ -916,12 +916,30 @@ func (fs *filesystem) MkdirAt(ctx context.Context, rp *vfs.ResolvingPath, opts v
|
||||
func (fs *filesystem) MknodAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.MknodOptions) error {
|
||||
return fs.doCreateAt(ctx, rp, false /* dir */, func(parent *dentry, name string, ds **[]*dentry) error {
|
||||
creds := rp.Credentials()
|
||||
var err error
|
||||
var (
|
||||
childInode lisafs.Inode
|
||||
err error
|
||||
)
|
||||
if fs.opts.lisaEnabled {
|
||||
var childInode lisafs.Inode
|
||||
childInode, err = parent.controlFDLisa.MknodAt(ctx, name, opts.Mode, lisafs.UID(creds.EffectiveKUID), lisafs.GID(creds.EffectiveKGID), opts.DevMinor, opts.DevMajor)
|
||||
if opts.Endpoint != nil {
|
||||
// We are creating a socket. Defer to bindAt instead of MknodAt.
|
||||
ep := opts.Endpoint.(transport.Endpoint)
|
||||
sockType := ep.Type()
|
||||
var boundSocketFD *lisafs.ClientBoundSocketFD
|
||||
childInode, boundSocketFD, err = parent.controlFDLisa.BindAt(ctx, sockType, name)
|
||||
if err == nil {
|
||||
opts.Endpoint.(transport.HostBoundEndpoint).SetBoundSocketFD(boundSocketFD)
|
||||
}
|
||||
} else {
|
||||
childInode, err = parent.controlFDLisa.MknodAt(ctx, name, opts.Mode, lisafs.UID(creds.EffectiveKUID), lisafs.GID(creds.EffectiveKGID), opts.DevMinor, opts.DevMajor)
|
||||
}
|
||||
if err == nil {
|
||||
return parent.insertCreatedChildLocked(ctx, &childInode, name, nil, ds)
|
||||
return parent.insertCreatedChildLocked(ctx, &childInode, name, func(child *dentry) {
|
||||
if opts.Endpoint != nil && fs.opts.lisaEnabled {
|
||||
// Set the endpoint on the newly created child dentry.
|
||||
child.endpoint = opts.Endpoint
|
||||
}
|
||||
}, ds)
|
||||
}
|
||||
} else {
|
||||
_, err = parent.file.mknod(ctx, name, (p9.FileMode)(opts.Mode), opts.DevMajor, opts.DevMinor, (p9.UID)(creds.EffectiveKUID), (p9.GID)(creds.EffectiveKGID))
|
||||
@@ -1766,18 +1784,19 @@ func (fs *filesystem) BoundEndpointAt(ctx context.Context, rp *vfs.ResolvingPath
|
||||
if err := d.checkPermissions(rp.Credentials(), vfs.MayWrite); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d.isSocket() {
|
||||
if !d.isSynthetic() {
|
||||
d.IncRef()
|
||||
ds = appendDentry(ds, d)
|
||||
return &endpoint{
|
||||
dentry: d,
|
||||
path: opts.Addr,
|
||||
}, nil
|
||||
}
|
||||
if d.endpoint != nil {
|
||||
return d.endpoint, nil
|
||||
}
|
||||
if !d.isSocket() {
|
||||
return nil, linuxerr.ECONNREFUSED
|
||||
}
|
||||
if d.endpoint != nil {
|
||||
return d.endpoint, nil
|
||||
}
|
||||
if !d.isSynthetic() {
|
||||
d.IncRef()
|
||||
ds = appendDentry(ds, d)
|
||||
return &endpoint{
|
||||
dentry: d,
|
||||
path: opts.Addr,
|
||||
}, nil
|
||||
}
|
||||
return nil, linuxerr.ECONNREFUSED
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ go_library(
|
||||
"//pkg/errors/linuxerr",
|
||||
"//pkg/fdnotifier",
|
||||
"//pkg/ilist",
|
||||
"//pkg/lisafs",
|
||||
"//pkg/log",
|
||||
"//pkg/refs",
|
||||
"//pkg/refsvfs2",
|
||||
|
||||
@@ -15,8 +15,13 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/fdnotifier"
|
||||
"gvisor.dev/gvisor/pkg/lisafs"
|
||||
"gvisor.dev/gvisor/pkg/sentry/uniqueid"
|
||||
"gvisor.dev/gvisor/pkg/sync"
|
||||
"gvisor.dev/gvisor/pkg/syserr"
|
||||
@@ -104,6 +109,12 @@ type connectionedEndpoint struct {
|
||||
//
|
||||
// If nil, then no listen call has been made.
|
||||
acceptedChan chan *connectionedEndpoint `state:".([]*connectionedEndpoint)"`
|
||||
|
||||
// boundSocketFD corresponds to a bound socket on the host filesystem
|
||||
// that may listen and accept incoming connections.
|
||||
//
|
||||
// boundSocketFD is protected by baseEndpoint.mu.
|
||||
boundSocketFD *lisafs.ClientBoundSocketFD
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -218,8 +229,11 @@ func (e *connectionedEndpoint) ListeningLocked() bool {
|
||||
func (e *connectionedEndpoint) Close(ctx context.Context) {
|
||||
var acceptedChan chan *connectionedEndpoint
|
||||
e.Lock()
|
||||
var c ConnectedEndpoint
|
||||
var r Receiver
|
||||
var (
|
||||
c ConnectedEndpoint
|
||||
r Receiver
|
||||
)
|
||||
bsFD := e.boundSocketFD
|
||||
switch {
|
||||
case e.Connected():
|
||||
e.connected.CloseSend()
|
||||
@@ -251,6 +265,13 @@ func (e *connectionedEndpoint) Close(ctx context.Context) {
|
||||
c.CloseNotify()
|
||||
c.Release(ctx)
|
||||
}
|
||||
|
||||
// Clean up any associated host bound socket.
|
||||
if bsFD != nil {
|
||||
fdnotifier.RemoveFD(bsFD.NotificationFD())
|
||||
bsFD.Close(ctx)
|
||||
}
|
||||
|
||||
if r != nil {
|
||||
r.CloseNotify()
|
||||
r.Release(ctx)
|
||||
@@ -397,6 +418,11 @@ func (e *connectionedEndpoint) Listen(ctx context.Context, backlog int) *syserr.
|
||||
for ep := range origChan {
|
||||
e.acceptedChan <- ep
|
||||
}
|
||||
if e.boundSocketFD != nil {
|
||||
if err := e.boundSocketFD.Listen(ctx, int32(backlog)); err != nil {
|
||||
return syserr.FromError(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if !e.isBound() {
|
||||
@@ -405,6 +431,12 @@ func (e *connectionedEndpoint) Listen(ctx context.Context, backlog int) *syserr.
|
||||
|
||||
// Normal case.
|
||||
e.acceptedChan = make(chan *connectionedEndpoint, backlog)
|
||||
if e.boundSocketFD != nil {
|
||||
if err := e.boundSocketFD.Listen(ctx, int32(backlog)); err != nil {
|
||||
return syserr.FromError(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -417,28 +449,61 @@ func (e *connectionedEndpoint) Accept(ctx context.Context, peerAddr *tcpip.FullA
|
||||
return nil, syserr.ErrInvalidEndpointState
|
||||
}
|
||||
|
||||
ne, err := e.getAcceptedEndpointLocked(ctx)
|
||||
e.Unlock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if peerAddr != nil {
|
||||
ne.Lock()
|
||||
c := ne.connected
|
||||
ne.Unlock()
|
||||
if c != nil {
|
||||
addr, err := c.GetLocalAddress()
|
||||
if err != nil {
|
||||
return nil, syserr.TranslateNetstackError(err)
|
||||
}
|
||||
*peerAddr = addr
|
||||
}
|
||||
}
|
||||
return ne, nil
|
||||
}
|
||||
|
||||
// Preconditions:
|
||||
// * e.Listening()
|
||||
// * e is locked.
|
||||
func (e *connectionedEndpoint) getAcceptedEndpointLocked(ctx context.Context) (*connectionedEndpoint, *syserr.Error) {
|
||||
// Accept connections from within the sentry first, since this avoids
|
||||
// an RPC to the gofer on the common path.
|
||||
select {
|
||||
case ne := <-e.acceptedChan:
|
||||
e.Unlock()
|
||||
if peerAddr != nil {
|
||||
ne.Lock()
|
||||
c := ne.connected
|
||||
ne.Unlock()
|
||||
if c != nil {
|
||||
addr, err := c.GetLocalAddress()
|
||||
if err != nil {
|
||||
return nil, syserr.TranslateNetstackError(err)
|
||||
}
|
||||
*peerAddr = addr
|
||||
}
|
||||
}
|
||||
return ne, nil
|
||||
|
||||
default:
|
||||
e.Unlock()
|
||||
// Nothing left.
|
||||
// No internal connections.
|
||||
}
|
||||
|
||||
if e.boundSocketFD == nil {
|
||||
return nil, syserr.ErrWouldBlock
|
||||
}
|
||||
|
||||
// Check for external connections.
|
||||
nfd, err := e.boundSocketFD.Accept(ctx)
|
||||
if err == unix.EWOULDBLOCK {
|
||||
return nil, syserr.ErrWouldBlock
|
||||
}
|
||||
if err != nil {
|
||||
return nil, syserr.FromError(err)
|
||||
}
|
||||
q := &waiter.Queue{}
|
||||
scme, serr := NewSCMEndpoint(nfd, q, e.path)
|
||||
if serr != nil {
|
||||
unix.Close(nfd)
|
||||
return nil, serr
|
||||
}
|
||||
scme.Init()
|
||||
return NewExternal(e.stype, e.idGenerator, q, scme, scme).(*connectionedEndpoint), nil
|
||||
|
||||
}
|
||||
|
||||
// Bind binds the connection.
|
||||
@@ -476,6 +541,13 @@ func (e *connectionedEndpoint) SendMsg(ctx context.Context, data [][]byte, c Con
|
||||
return e.baseEndpoint.SendMsg(ctx, data, c, to)
|
||||
}
|
||||
|
||||
func (e *connectionedEndpoint) isBoundSocketReadable() bool {
|
||||
if e.boundSocketFD == nil {
|
||||
return false
|
||||
}
|
||||
return fdnotifier.NonBlockingPoll(e.boundSocketFD.NotificationFD(), waiter.ReadableEvents)&waiter.ReadableEvents != 0
|
||||
}
|
||||
|
||||
// Readiness returns the current readiness of the connectionedEndpoint. For
|
||||
// example, if waiter.EventIn is set, the connectionedEndpoint is immediately
|
||||
// readable.
|
||||
@@ -493,7 +565,7 @@ func (e *connectionedEndpoint) Readiness(mask waiter.EventMask) waiter.EventMask
|
||||
ready |= waiter.WritableEvents
|
||||
}
|
||||
case e.ListeningLocked():
|
||||
if mask&waiter.ReadableEvents != 0 && len(e.acceptedChan) > 0 {
|
||||
if mask&waiter.ReadableEvents != 0 && (len(e.acceptedChan) > 0 || e.isBoundSocketReadable()) {
|
||||
ready |= waiter.ReadableEvents
|
||||
}
|
||||
}
|
||||
@@ -524,3 +596,41 @@ func (e *connectionedEndpoint) OnSetSendBufferSize(v int64) (newSz int64) {
|
||||
|
||||
// WakeupWriters implements tcpip.SocketOptionsHandler.WakeupWriters.
|
||||
func (e *connectionedEndpoint) WakeupWriters() {}
|
||||
|
||||
// SetBoundSocketFD implement HostBountEndpoint.SetBoundSocketFD.
|
||||
func (e *connectionedEndpoint) SetBoundSocketFD(bsFD *lisafs.ClientBoundSocketFD) {
|
||||
e.Lock()
|
||||
defer e.Unlock()
|
||||
if e.boundSocketFD != nil {
|
||||
panic(fmt.Sprintf("SetBoundSocketFD called twice\nold: %v\nnew: %v", e.boundSocketFD, bsFD))
|
||||
}
|
||||
e.boundSocketFD = bsFD
|
||||
fdnotifier.AddFD(bsFD.NotificationFD(), e.Queue)
|
||||
}
|
||||
|
||||
// EventRegister implements waiter.Waitable.EventRegister.
|
||||
func (e *connectionedEndpoint) EventRegister(we *waiter.Entry) error {
|
||||
if err := e.baseEndpoint.EventRegister(we); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
e.Lock()
|
||||
bsFD := e.boundSocketFD
|
||||
e.Unlock()
|
||||
if bsFD != nil {
|
||||
fdnotifier.UpdateFD(bsFD.NotificationFD())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// EventUnregister implements waiter.Waitable.EventUnregister.
|
||||
func (e *connectionedEndpoint) EventUnregister(we *waiter.Entry) {
|
||||
e.baseEndpoint.EventUnregister(we)
|
||||
|
||||
e.Lock()
|
||||
bsFD := e.boundSocketFD
|
||||
e.Unlock()
|
||||
if bsFD != nil {
|
||||
fdnotifier.UpdateFD(bsFD.NotificationFD())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,13 @@ func (e *connectionedEndpoint) loadAcceptedChan(acceptedSlice []*connectionedEnd
|
||||
}
|
||||
}
|
||||
|
||||
// beforeSave is invoked by stateify.
|
||||
func (e *connectionedEndpoint) beforeSave() {
|
||||
if e.boundSocketFD != nil {
|
||||
panic("Cannot save endpoint with bound host socket")
|
||||
}
|
||||
}
|
||||
|
||||
// afterLoad is invoked by stateify.
|
||||
func (e *connectionedEndpoint) afterLoad() {
|
||||
e.ops.InitHandler(e, &stackHandler{}, getSendBufferLimits, getReceiveBufferLimits)
|
||||
|
||||
@@ -18,6 +18,7 @@ package transport
|
||||
import (
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/lisafs"
|
||||
"gvisor.dev/gvisor/pkg/log"
|
||||
"gvisor.dev/gvisor/pkg/sync"
|
||||
"gvisor.dev/gvisor/pkg/syserr"
|
||||
@@ -259,6 +260,16 @@ type BoundEndpoint interface {
|
||||
Release(ctx context.Context)
|
||||
}
|
||||
|
||||
// HostBoundEndpoint is an interface that endpoints can implement if they support
|
||||
// binding listening and accepting connections from a bound Unix domain socket
|
||||
// on the host.
|
||||
type HostBoundEndpoint interface {
|
||||
// SetBoundSocketFD will be called on supporting endpoints after
|
||||
// binding a socket on the host filesystem. Implementations should use
|
||||
// delegate Listen and Accept calls to the ClientBoundSocketFD.
|
||||
SetBoundSocketFD(*lisafs.ClientBoundSocketFD)
|
||||
}
|
||||
|
||||
// message represents a message passed over a Unix domain socket.
|
||||
//
|
||||
// +stateify savable
|
||||
|
||||
@@ -77,8 +77,8 @@ type Config struct {
|
||||
// Verity is whether there's one or more verity file system to mount.
|
||||
Verity bool `flag:"verity"`
|
||||
|
||||
// FSGoferHostUDS enables the gofer to mount a host UDS and connect to it or
|
||||
// bind (create) a host UDS and serve it.
|
||||
// FSGoferHostUDS enables the gofer to create and connect to host unix
|
||||
// domain sockets.
|
||||
FSGoferHostUDS bool `flag:"fsgofer-host-uds"`
|
||||
|
||||
// Network indicates what type of network to use.
|
||||
|
||||
@@ -212,6 +212,10 @@ var allowedSyscalls = seccomp.SyscallRules{
|
||||
}
|
||||
|
||||
var udsSyscalls = seccomp.SyscallRules{
|
||||
unix.SYS_ACCEPT4: {},
|
||||
unix.SYS_BIND: {},
|
||||
unix.SYS_CONNECT: {},
|
||||
unix.SYS_LISTEN: {},
|
||||
unix.SYS_SOCKET: []seccomp.Rule{
|
||||
{
|
||||
seccomp.EqualTo(unix.AF_UNIX),
|
||||
@@ -229,11 +233,6 @@ var udsSyscalls = seccomp.SyscallRules{
|
||||
seccomp.EqualTo(0),
|
||||
},
|
||||
},
|
||||
unix.SYS_CONNECT: []seccomp.Rule{
|
||||
{
|
||||
seccomp.MatchAny{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var xattrSyscalls = seccomp.SyscallRules{
|
||||
|
||||
@@ -31,7 +31,7 @@ func Install() error {
|
||||
}
|
||||
|
||||
// InstallUDSFilters extends the allowed syscalls to include those necessary for
|
||||
// connecting to a host UDS.
|
||||
// creating and connecting to host UDS.
|
||||
func InstallUDSFilters() {
|
||||
// Add additional filters required for connecting to the host's sockets.
|
||||
allowedSyscalls.Merge(udsSyscalls)
|
||||
|
||||
@@ -73,8 +73,8 @@ type Config struct {
|
||||
// PanicOnWrite panics on attempts to write to RO mounts.
|
||||
PanicOnWrite bool
|
||||
|
||||
// HostUDS signals whether the gofer can mount a host's UDS and connect to it
|
||||
// or bind (create) a host UDS and serve it.
|
||||
// HostUDS signals whether the gofer can create and connect to host
|
||||
// unix domain sockets.
|
||||
HostUDS bool
|
||||
|
||||
// EnableVerityXattr allows access to extended attributes used by the
|
||||
|
||||
+107
-2
@@ -17,7 +17,9 @@ package fsgofer
|
||||
import (
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
@@ -107,6 +109,9 @@ func (s *LisafsServer) SupportedMessages() []lisafs.MID {
|
||||
lisafs.Getdents64,
|
||||
lisafs.FGetXattr,
|
||||
lisafs.FSetXattr,
|
||||
lisafs.BindAt,
|
||||
lisafs.Listen,
|
||||
lisafs.Accept,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -620,7 +625,7 @@ func (fd *controlFDLisa) Readlink(getLinkBuf func(uint32) []byte) (uint16, error
|
||||
// Connect implements lisafs.ControlFDImpl.Connect.
|
||||
func (fd *controlFDLisa) Connect(sockType uint32) (int, error) {
|
||||
if !fd.Conn().ServerImpl().(*LisafsServer).config.HostUDS {
|
||||
return -1, unix.ECONNREFUSED
|
||||
return -1, unix.EPERM
|
||||
}
|
||||
|
||||
// TODO(gvisor.dev/issue/1003): Due to different app vs replacement
|
||||
@@ -629,7 +634,7 @@ func (fd *controlFDLisa) Connect(sockType uint32) (int, error) {
|
||||
// in order to actually connect to this socket.
|
||||
hostPath := fd.Node().FilePath()
|
||||
if len(hostPath) >= unixPathMax {
|
||||
return -1, unix.ECONNREFUSED
|
||||
return -1, unix.EINVAL
|
||||
}
|
||||
|
||||
// Only the following types are supported.
|
||||
@@ -652,6 +657,68 @@ func (fd *controlFDLisa) Connect(sockType uint32) (int, error) {
|
||||
return sock, nil
|
||||
}
|
||||
|
||||
// BindAt implements lisafs.ControlFDImpl.BindAt.
|
||||
func (fd *controlFDLisa) BindAt(name string, sockType uint32) (*lisafs.ControlFD, linux.Statx, *lisafs.BoundSocketFD, int, error) {
|
||||
if !fd.Conn().ServerImpl().(*LisafsServer).config.HostUDS {
|
||||
return nil, linux.Statx{}, nil, -1, unix.EPERM
|
||||
}
|
||||
|
||||
// Because there is no "bindat" syscall in Linux, we must create an
|
||||
// absolute path to the socket we are creating,
|
||||
socketPath := filepath.Join(fd.Node().FilePath(), name)
|
||||
|
||||
// TODO(gvisor.dev/issue/1003): Due to different app vs replacement
|
||||
// mappings, the app path may have fit in the sockaddr, but we can't fit
|
||||
// hostPath in our sockaddr. We'd need to redirect through a shorter path
|
||||
// in order to actually connect to this socket.
|
||||
if len(socketPath) >= unixPathMax {
|
||||
log.Warningf("BindAt called with name too long: %q (len=%d)", socketPath, len(socketPath))
|
||||
return nil, linux.Statx{}, nil, -1, unix.EINVAL
|
||||
}
|
||||
|
||||
// Only the following types are supported.
|
||||
switch sockType {
|
||||
case unix.SOCK_STREAM, unix.SOCK_SEQPACKET:
|
||||
default:
|
||||
return nil, linux.Statx{}, nil, -1, unix.ENXIO
|
||||
}
|
||||
|
||||
// Create and bind the socket using the sockPath which may be a
|
||||
// symlink.
|
||||
sockFD, err := unix.Socket(unix.AF_UNIX, int(sockType), 0)
|
||||
if err != nil {
|
||||
return nil, linux.Statx{}, nil, -1, err
|
||||
}
|
||||
if err := unix.Bind(sockFD, &unix.SockaddrUnix{Name: socketPath}); err != nil {
|
||||
return nil, linux.Statx{}, nil, -1, err
|
||||
}
|
||||
|
||||
// Stat the socket.
|
||||
sockStat, err := fstatTo(sockFD)
|
||||
if err != nil {
|
||||
_ = unix.Unlink(socketPath)
|
||||
return nil, linux.Statx{}, nil, -1, err
|
||||
}
|
||||
|
||||
// Get an os.File that will back future socket calls.
|
||||
sockFile := os.NewFile(uintptr(sockFD), socketPath)
|
||||
|
||||
// Create an FD that will be donated to the sandbox.
|
||||
sockFDToDonate, err := unix.Dup(int(sockFile.Fd()))
|
||||
if err != nil {
|
||||
_ = unix.Unlink(socketPath)
|
||||
return nil, linux.Statx{}, nil, -1, err
|
||||
}
|
||||
|
||||
socketControlFD := newControlFDLisa(sockFD, fd, socketPath, linux.ModeSocket)
|
||||
boundSocketFD := &boundSocketFDLisa{
|
||||
sock: sockFile,
|
||||
}
|
||||
boundSocketFD.Init(socketControlFD.FD(), boundSocketFD)
|
||||
|
||||
return socketControlFD.FD(), sockStat, boundSocketFD.FD(), sockFDToDonate, nil
|
||||
}
|
||||
|
||||
// Unlink implements lisafs.ControlFDImpl.Unlink.
|
||||
func (fd *controlFDLisa) Unlink(name string, flags uint32) error {
|
||||
return unix.Unlinkat(fd.hostFD, name, int(flags))
|
||||
@@ -838,6 +905,44 @@ func (fd *openFDLisa) Renamed() {
|
||||
// openFDLisa does not have any state to update on rename.
|
||||
}
|
||||
|
||||
type boundSocketFDLisa struct {
|
||||
lisafs.BoundSocketFD
|
||||
|
||||
sock *os.File
|
||||
}
|
||||
|
||||
var _ lisafs.BoundSocketFDImpl = (*boundSocketFDLisa)(nil)
|
||||
|
||||
// Close implements lisafs.BoundSocketFD.Close.
|
||||
func (fd *boundSocketFDLisa) Close() {
|
||||
fd.sock.Close()
|
||||
}
|
||||
|
||||
// FD implements lisafs.BoundSocketFD.FD.
|
||||
func (fd *boundSocketFDLisa) FD() *lisafs.BoundSocketFD {
|
||||
if fd == nil {
|
||||
return nil
|
||||
}
|
||||
return &fd.BoundSocketFD
|
||||
}
|
||||
|
||||
// Listen implements lisafs.BoundSocketFD.Listen.
|
||||
func (fd *boundSocketFDLisa) Listen(backlog int32) error {
|
||||
return unix.Listen(int(fd.sock.Fd()), int(backlog))
|
||||
}
|
||||
|
||||
// Listen implements lisafs.BoundSocketFD.Accept.
|
||||
func (fd *boundSocketFDLisa) Accept() (int, string, error) {
|
||||
flags := unix.O_NONBLOCK | unix.O_CLOEXEC
|
||||
nfd, _, err := unix.Accept4(int(fd.sock.Fd()), flags)
|
||||
if err != nil {
|
||||
return -1, "", err
|
||||
}
|
||||
// Return an empty peer address so that we don't leak the actual host
|
||||
// address.
|
||||
return nfd, "", err
|
||||
}
|
||||
|
||||
// tryOpen tries to open() with different modes as documented.
|
||||
func tryOpen(open func(int) (int, error)) (hostFD int, err error) {
|
||||
// Attempt to open file in the following in order:
|
||||
|
||||
@@ -110,6 +110,52 @@ TEST_P(GoferStreamSeqpacketTest, NonListening) {
|
||||
SyscallFailsWithErrno(ECONNREFUSED));
|
||||
}
|
||||
|
||||
// Bind to a socket, then Listen and Accept.
|
||||
TEST_P(GoferStreamSeqpacketTest, BindListenAccept) {
|
||||
// Binding to host socket requires LisaFS.
|
||||
SKIP_IF(!IsLisafsEnabled());
|
||||
|
||||
std::string env;
|
||||
ProtocolSocket proto;
|
||||
std::tie(env, proto) = GetParam();
|
||||
|
||||
char* val = getenv(env.c_str());
|
||||
ASSERT_NE(val, nullptr);
|
||||
std::string root(val);
|
||||
|
||||
FileDescriptor sock =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_UNIX, proto.protocol, 0));
|
||||
|
||||
std::string socket_path =
|
||||
JoinPath("/tmp/sockets", proto.name, "created-in-sandbox");
|
||||
|
||||
struct sockaddr_un addr = {};
|
||||
addr.sun_family = AF_UNIX;
|
||||
memcpy(addr.sun_path, socket_path.c_str(), socket_path.length());
|
||||
|
||||
ASSERT_THAT(
|
||||
bind(sock.get(), reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)),
|
||||
SyscallSucceeds());
|
||||
|
||||
ASSERT_THAT(listen(sock.get(), 1), SyscallSucceeds());
|
||||
|
||||
FileDescriptor accSock =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Accept(sock.get(), NULL, NULL));
|
||||
|
||||
// Other socket should be echo server.
|
||||
constexpr int kBufferSize = 64;
|
||||
char send_buffer[kBufferSize];
|
||||
memset(send_buffer, 'a', sizeof(send_buffer));
|
||||
|
||||
ASSERT_THAT(WriteFd(accSock.get(), send_buffer, sizeof(send_buffer)),
|
||||
SyscallSucceedsWithValue(sizeof(send_buffer)));
|
||||
|
||||
char recv_buffer[kBufferSize];
|
||||
ASSERT_THAT(ReadFd(accSock.get(), recv_buffer, sizeof(recv_buffer)),
|
||||
SyscallSucceedsWithValue(sizeof(recv_buffer)));
|
||||
ASSERT_EQ(0, memcmp(send_buffer, recv_buffer, sizeof(send_buffer)));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
StreamSeqpacket, GoferStreamSeqpacketTest,
|
||||
::testing::Combine(
|
||||
|
||||
+55
-16
@@ -16,17 +16,33 @@
|
||||
package uds
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"gvisor.dev/gvisor/pkg/log"
|
||||
"gvisor.dev/gvisor/pkg/unet"
|
||||
)
|
||||
|
||||
func doEcho(s *unet.Socket) error {
|
||||
buf := make([]byte, 512)
|
||||
n, err := s.Read(buf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read: %d, %w", n, err)
|
||||
}
|
||||
|
||||
n, err = s.Write(buf[:n])
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write: %d, %w", n, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// createEchoSocket creates a socket that echoes back anything received.
|
||||
//
|
||||
// Only works for stream, seqpacket sockets.
|
||||
@@ -57,20 +73,11 @@ func createEchoSocket(path string, protocol int) (cleanup func(), err error) {
|
||||
defer s.Close()
|
||||
|
||||
for {
|
||||
buf := make([]byte, 512)
|
||||
for {
|
||||
n, err := s.Read(buf)
|
||||
if err == io.EOF {
|
||||
if err := doEcho(s); err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read: %d, %v", n, err)
|
||||
}
|
||||
|
||||
n, err = s.Write(buf[:n])
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write: %d, %v", n, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,6 +100,31 @@ func createEchoSocket(path string, protocol int) (cleanup func(), err error) {
|
||||
return cleanup, nil
|
||||
}
|
||||
|
||||
// connectAndBecomeEcho connects to the given socket and turns into an echo server.
|
||||
func connectAndBecomeEcho(path string, protocol int) (cleanup func(), err error) {
|
||||
usePacket := protocol == unix.SOCK_SEQPACKET
|
||||
go func() {
|
||||
for {
|
||||
sock, err := unet.Connect(path, usePacket)
|
||||
log.Infof("Connecting to UDS at %q, got %v", path, err)
|
||||
if err != nil {
|
||||
// Wait and try again.
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
defer sock.Close()
|
||||
for {
|
||||
log.Infof("Connected to UDS at %q, running echo server", path)
|
||||
if err := doEcho(sock); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return func() {}, nil
|
||||
}
|
||||
|
||||
// createNonListeningSocket creates a socket that is bound but not listening.
|
||||
//
|
||||
// Only relevant for stream, seqpacket sockets.
|
||||
@@ -162,6 +194,11 @@ type socketCreator func(path string, proto int) (cleanup func(), err error)
|
||||
// * /seqpacket/echo
|
||||
// * /seqpacket/nonlistening
|
||||
// * /dgram/null
|
||||
//
|
||||
// Additionally, it will attempt to connect to sockets at the following
|
||||
// locations, and turn into an echo server once connected:
|
||||
// * /stream/created-in-sandbox
|
||||
// * /seqpacket/created-in-sandbox
|
||||
func CreateSocketTree(baseDir string) (dir string, cleanup func(), err error) {
|
||||
dir, err = ioutil.TempDir(baseDir, "sockets")
|
||||
if err != nil {
|
||||
@@ -177,16 +214,18 @@ func CreateSocketTree(baseDir string) (dir string, cleanup func(), err error) {
|
||||
protocol: unix.SOCK_STREAM,
|
||||
name: "stream",
|
||||
sockets: map[string]socketCreator{
|
||||
"echo": createEchoSocket,
|
||||
"nonlistening": createNonListeningSocket,
|
||||
"echo": createEchoSocket,
|
||||
"nonlistening": createNonListeningSocket,
|
||||
"created-in-sandbox": connectAndBecomeEcho,
|
||||
},
|
||||
},
|
||||
{
|
||||
protocol: unix.SOCK_SEQPACKET,
|
||||
name: "seqpacket",
|
||||
sockets: map[string]socketCreator{
|
||||
"echo": createEchoSocket,
|
||||
"nonlistening": createNonListeningSocket,
|
||||
"echo": createEchoSocket,
|
||||
"nonlistening": createNonListeningSocket,
|
||||
"created-in-sandbox": connectAndBecomeEcho,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user