mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Add Bind RPC to gvisor's 9P protocol and implement it in runsc/fsgofer.
This new RPC allows a client to be able to bind (and hence create) UDS on the host filesystem. Following changes will add functionality to listen and accept on such a bound UDS. PiperOrigin-RevId: 420149313
This commit is contained in:
@@ -348,6 +348,37 @@ func (c *clientFile) Open(flags OpenFlags) (*fd.FD, QID, uint32, error) {
|
||||
return rlopen.File, rlopen.QID, rlopen.IoUnit, nil
|
||||
}
|
||||
|
||||
func (c *clientFile) Bind(sockType uint32, sockName string, uid UID, gid GID) (File, QID, AttrMask, Attr, error) {
|
||||
if atomic.LoadUint32(&c.closed) != 0 {
|
||||
return nil, QID{}, AttrMask{}, Attr{}, unix.EBADF
|
||||
}
|
||||
|
||||
if !versionSupportsBind(c.client.version) {
|
||||
return nil, QID{}, AttrMask{}, Attr{}, unix.EOPNOTSUPP
|
||||
}
|
||||
|
||||
fid, ok := c.client.fidPool.Get()
|
||||
if !ok {
|
||||
return nil, QID{}, AttrMask{}, Attr{}, ErrOutOfFIDs
|
||||
}
|
||||
|
||||
tbind := Tbind{
|
||||
SockType: sockType,
|
||||
SockName: sockName,
|
||||
UID: uid,
|
||||
GID: gid,
|
||||
Directory: c.fid,
|
||||
NewFID: FID(fid),
|
||||
}
|
||||
rbind := Rbind{}
|
||||
if err := c.client.sendRecv(&tbind, &rbind); err != nil {
|
||||
c.client.fidPool.Put(fid)
|
||||
return nil, QID{}, AttrMask{}, Attr{}, err
|
||||
}
|
||||
|
||||
return c.client.newFile(FID(fid)), rbind.QID, rbind.Valid, rbind.Attr, nil
|
||||
}
|
||||
|
||||
// Connect implements File.Connect.
|
||||
func (c *clientFile) Connect(flags ConnectFlags) (*fd.FD, error) {
|
||||
if atomic.LoadUint32(&c.closed) != 0 {
|
||||
|
||||
@@ -294,6 +294,17 @@ type File interface {
|
||||
// On the server, Flush has a read concurrency guarantee.
|
||||
Flush() error
|
||||
|
||||
// Bind binds to a host unix domain socket. If successful, it creates a
|
||||
// socket file on the host filesystem and returns a File for the newly
|
||||
// created socket file. The File implementation must save the bound socket
|
||||
// FD so that subsequent Listen and Accept operations on the File can be
|
||||
// served.
|
||||
//
|
||||
// Bind is an extension to 9P2000.L, see version.go.
|
||||
//
|
||||
// On the server, UnlinkAt has a write concurrency guarantee.
|
||||
Bind(sockType uint32, sockName string, uid UID, gid GID) (File, QID, AttrMask, Attr, error)
|
||||
|
||||
// Connect establishes a new host-socket backed connection with a
|
||||
// socket. A File does not need to be opened before it can be connected
|
||||
// and it can be connected to multiple times resulting in a unique
|
||||
|
||||
@@ -1398,6 +1398,58 @@ func (t *Tumknod) handle(cs *connState) message {
|
||||
return &Rumknod{*rmknod}
|
||||
}
|
||||
|
||||
// handle implements handler.handle.
|
||||
func (t *Tbind) handle(cs *connState) message {
|
||||
if err := checkSafeName(t.SockName); err != nil {
|
||||
return newErr(err)
|
||||
}
|
||||
|
||||
ref, ok := cs.LookupFID(t.Directory)
|
||||
if !ok {
|
||||
return newErr(unix.EBADF)
|
||||
}
|
||||
defer ref.DecRef()
|
||||
|
||||
var (
|
||||
sockRef *fidRef
|
||||
qid QID
|
||||
valid AttrMask
|
||||
attr Attr
|
||||
)
|
||||
if err := ref.safelyWrite(func() (err error) {
|
||||
// Don't allow creation from non-directories or deleted directories.
|
||||
if ref.isDeleted() || !ref.mode.IsDir() {
|
||||
return unix.EINVAL
|
||||
}
|
||||
|
||||
// Not allowed on open directories.
|
||||
if ref.opened {
|
||||
return unix.EINVAL
|
||||
}
|
||||
|
||||
var sockF File
|
||||
sockF, qid, valid, attr, err = ref.file.Bind(t.SockType, t.SockName, t.UID, t.GID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sockRef = &fidRef{
|
||||
server: cs.server,
|
||||
parent: ref,
|
||||
file: sockF,
|
||||
mode: ModeSocket,
|
||||
pathNode: ref.pathNode.pathNodeFor(t.SockName),
|
||||
}
|
||||
ref.pathNode.addChild(sockRef, t.SockName)
|
||||
ref.IncRef() // Acquire parent reference.
|
||||
return nil
|
||||
}); err != nil {
|
||||
return newErr(err)
|
||||
}
|
||||
cs.InsertFID(t.NewFID, sockRef)
|
||||
return &Rbind{QID: qid, Valid: valid, Attr: attr}
|
||||
}
|
||||
|
||||
// handle implements handler.handle.
|
||||
func (t *Tlconnect) handle(cs *connState) message {
|
||||
ref, ok := cs.LookupFID(t.FID)
|
||||
|
||||
@@ -2438,6 +2438,95 @@ func (r *Rusymlink) String() string {
|
||||
return fmt.Sprintf("Rusymlink{%v}", &r.Rsymlink)
|
||||
}
|
||||
|
||||
// Tbind is a bind request.
|
||||
type Tbind struct {
|
||||
// Directory is the directory inside which the bound socket file should be
|
||||
// created.
|
||||
Directory FID
|
||||
|
||||
// SockType is the type of socket to be used. This is passed as an argument
|
||||
// to socket(2).
|
||||
SockType uint32
|
||||
|
||||
// SockName is the name of the socket file to be created.
|
||||
SockName string
|
||||
|
||||
// UID is the owning user.
|
||||
UID UID
|
||||
|
||||
// GID is the owning group.
|
||||
GID GID
|
||||
|
||||
// NewFID is the resulting FID for the socket file.
|
||||
NewFID FID
|
||||
}
|
||||
|
||||
// decode implements encoder.decode.
|
||||
func (t *Tbind) decode(b *buffer) {
|
||||
t.Directory = b.ReadFID()
|
||||
t.SockType = b.Read32()
|
||||
t.SockName = b.ReadString()
|
||||
t.UID = b.ReadUID()
|
||||
t.GID = b.ReadGID()
|
||||
t.NewFID = b.ReadFID()
|
||||
}
|
||||
|
||||
// encode implements encoder.encode.
|
||||
func (t *Tbind) encode(b *buffer) {
|
||||
b.WriteFID(t.Directory)
|
||||
b.Write32(t.SockType)
|
||||
b.WriteString(t.SockName)
|
||||
b.WriteUID(t.UID)
|
||||
b.WriteGID(t.GID)
|
||||
b.WriteFID(t.NewFID)
|
||||
}
|
||||
|
||||
// Type implements message.Type.
|
||||
func (*Tbind) Type() MsgType {
|
||||
return MsgTbind
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
func (t *Tbind) String() string {
|
||||
return fmt.Sprintf("Tbind{Directory: %d, SockType: %d, SockName: %s, UID: %d, GID: %d, NewFID: %d}", t.Directory, t.SockType, t.SockName, t.UID, t.GID, t.NewFID)
|
||||
}
|
||||
|
||||
// Rbind is a bind response.
|
||||
type Rbind struct {
|
||||
// QID is the resulting QID of the created socket file.
|
||||
QID QID
|
||||
|
||||
// Valid indicates which fields are valid.
|
||||
Valid AttrMask
|
||||
|
||||
// Attr is the set of attributes of the created socket file.
|
||||
Attr Attr
|
||||
}
|
||||
|
||||
// decode implements encoder.decode.
|
||||
func (r *Rbind) decode(b *buffer) {
|
||||
r.QID.decode(b)
|
||||
r.Valid.decode(b)
|
||||
r.Attr.decode(b)
|
||||
}
|
||||
|
||||
// encode implements encoder.encode.
|
||||
func (r *Rbind) encode(b *buffer) {
|
||||
r.QID.encode(b)
|
||||
r.Valid.encode(b)
|
||||
r.Attr.encode(b)
|
||||
}
|
||||
|
||||
// Type implements message.Type.
|
||||
func (*Rbind) Type() MsgType {
|
||||
return MsgRbind
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
func (r *Rbind) String() string {
|
||||
return fmt.Sprintf("Rbind{QID: %s, Valid: %v, Attr: %s}", r.QID, r.Valid, r.Attr)
|
||||
}
|
||||
|
||||
// Tlconnect is a connect request.
|
||||
type Tlconnect struct {
|
||||
// FID is the FID to be connected.
|
||||
@@ -2785,6 +2874,8 @@ func init() {
|
||||
msgRegistry.register(MsgRumknod, func() message { return &Rumknod{} })
|
||||
msgRegistry.register(MsgTusymlink, func() message { return &Tusymlink{} })
|
||||
msgRegistry.register(MsgRusymlink, func() message { return &Rusymlink{} })
|
||||
msgRegistry.register(MsgTbind, func() message { return &Tbind{} })
|
||||
msgRegistry.register(MsgRbind, func() message { return &Rbind{} })
|
||||
msgRegistry.register(MsgTlconnect, func() message { return &Tlconnect{} })
|
||||
msgRegistry.register(MsgRlconnect, func() message { return &Rlconnect{} })
|
||||
msgRegistry.register(MsgTallocate, func() message { return &Tallocate{} })
|
||||
|
||||
@@ -118,6 +118,17 @@ func TestEncodeDecode(t *testing.T) {
|
||||
QID: QID{Type: 1},
|
||||
IoUnit: 2,
|
||||
},
|
||||
&Tbind{
|
||||
Directory: 1,
|
||||
SockType: 2,
|
||||
SockName: "name",
|
||||
GID: 3,
|
||||
UID: 4,
|
||||
NewFID: 5,
|
||||
},
|
||||
&Rbind{
|
||||
QID: QID{Type: 1},
|
||||
},
|
||||
&Tlconnect{
|
||||
FID: 1,
|
||||
},
|
||||
|
||||
@@ -404,6 +404,8 @@ const (
|
||||
MsgRsetattrclunk MsgType = 141
|
||||
MsgTmultigetattr MsgType = 142
|
||||
MsgRmultigetattr MsgType = 143
|
||||
MsgTbind MsgType = 144
|
||||
MsgRbind MsgType = 145
|
||||
MsgTchannel MsgType = 250
|
||||
MsgRchannel MsgType = 251
|
||||
)
|
||||
|
||||
@@ -185,3 +185,9 @@ func versionSupportsTsetattrclunk(v uint32) bool {
|
||||
func versionSupportsTmultiGetAttr(v uint32) bool {
|
||||
return v >= 13
|
||||
}
|
||||
|
||||
// versionSupportsBind returns true if version v supports the Tbind message.
|
||||
func versionSupportsBind(v uint32) bool {
|
||||
// TODO(b/194709873): Bump version and gate with that.
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -75,7 +75,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.
|
||||
// FSGoferHostUDS enables the gofer to mount a host UDS and connect to it or
|
||||
// bind (create) a host UDS and serve it.
|
||||
FSGoferHostUDS bool `flag:"fsgofer-host-uds"`
|
||||
|
||||
// Network indicates what type of network to use.
|
||||
|
||||
+100
-4
@@ -47,6 +47,9 @@ const (
|
||||
openFlags = unix.O_NOFOLLOW | unix.O_CLOEXEC
|
||||
|
||||
allowedOpenFlags = unix.O_TRUNC
|
||||
|
||||
// UNIX_PATH_MAX as defined in include/uapi/linux/un.h.
|
||||
unixPathMax = 108
|
||||
)
|
||||
|
||||
// verityXattrs are the extended attributes used by verity file system.
|
||||
@@ -70,7 +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.
|
||||
// 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 bool
|
||||
|
||||
// EnableVerityXattr allows access to extended attributes used by the
|
||||
@@ -1129,6 +1133,80 @@ func (l *localFile) Flush() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Bind implements p9.File.Bind.
|
||||
func (l *localFile) Bind(sockType uint32, sockName string, uid p9.UID, gid p9.GID) (p9.File, p9.QID, p9.AttrMask, p9.Attr, error) {
|
||||
if !l.attachPoint.conf.HostUDS {
|
||||
// Bind on host UDS is not allowed. As per mknod(2), which is invoked as
|
||||
// part of bind(2), if "the filesystem containing pathname does not support
|
||||
// the type of node requested." then EPERM must be returned.
|
||||
return nil, p9.QID{}, p9.AttrMask{}, p9.Attr{}, unix.EPERM
|
||||
}
|
||||
|
||||
// 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 f.path in our sockaddr. We'd need to redirect through a shorter
|
||||
// path in order to actually connect to this socket.
|
||||
sockPath := path.Join(l.hostPath, sockName)
|
||||
if len(sockPath) >= unixPathMax {
|
||||
return nil, p9.QID{}, p9.AttrMask{}, p9.Attr{}, unix.EINVAL
|
||||
}
|
||||
|
||||
// Create socket only for supported types.
|
||||
switch sockType {
|
||||
case unix.SOCK_STREAM, unix.SOCK_DGRAM, unix.SOCK_SEQPACKET:
|
||||
default:
|
||||
return nil, p9.QID{}, p9.AttrMask{}, p9.Attr{}, unix.ENXIO
|
||||
}
|
||||
sock, err := unix.Socket(unix.AF_UNIX, int(sockType), 0)
|
||||
if err != nil {
|
||||
return nil, p9.QID{}, p9.AttrMask{}, p9.Attr{}, extractErrno(err)
|
||||
}
|
||||
|
||||
// Revert operations on error paths.
|
||||
didBind := false
|
||||
cu := cleanup.Make(func() {
|
||||
_ = unix.Close(sock)
|
||||
if didBind {
|
||||
if err := unix.Unlinkat(l.file.FD(), sockName, 0); err != nil {
|
||||
log.Warningf("error unlinking file %q after failure: %v", sockPath, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
defer cu.Clean()
|
||||
|
||||
// socket FD must be non blocking because RPC operations like Accept on this
|
||||
// socket must be non blocking.
|
||||
if err := unix.SetNonblock(sock, true); err != nil {
|
||||
return nil, p9.QID{}, p9.AttrMask{}, p9.Attr{}, extractErrno(err)
|
||||
}
|
||||
|
||||
// Bind at the given path which should create the socket file.
|
||||
if err := unix.Bind(sock, &unix.SockaddrUnix{Name: sockPath}); err != nil {
|
||||
return nil, p9.QID{}, p9.AttrMask{}, p9.Attr{}, extractErrno(err)
|
||||
}
|
||||
didBind = true
|
||||
|
||||
// Open socket to change ownership.
|
||||
tempSockFD, err := fd.OpenAt(l.file, sockName, unix.O_PATH|openFlags, 0)
|
||||
if err != nil {
|
||||
return nil, p9.QID{}, p9.AttrMask{}, p9.Attr{}, extractErrno(err)
|
||||
}
|
||||
defer tempSockFD.Close()
|
||||
|
||||
if _, err = setOwnerIfNeeded(tempSockFD.FD(), uid, gid); err != nil {
|
||||
return nil, p9.QID{}, p9.AttrMask{}, p9.Attr{}, extractErrno(err)
|
||||
}
|
||||
|
||||
// Generate file for this socket by walking on it.
|
||||
qid, sockF, valid, attr, err := l.WalkGetAttr([]string{sockName})
|
||||
if err != nil {
|
||||
return nil, p9.QID{}, p9.AttrMask{}, p9.Attr{}, err
|
||||
}
|
||||
|
||||
cu.Release()
|
||||
return &socketLocalFile{localFile: sockF.(*localFile), sock: sock}, qid[0], valid, attr, nil
|
||||
}
|
||||
|
||||
// Connect implements p9.File.
|
||||
func (l *localFile) Connect(flags p9.ConnectFlags) (*fd.FD, error) {
|
||||
if !l.attachPoint.conf.HostUDS {
|
||||
@@ -1139,8 +1217,7 @@ func (l *localFile) Connect(flags p9.ConnectFlags) (*fd.FD, error) {
|
||||
// mappings, the app path may have fit in the sockaddr, but we can't
|
||||
// fit f.path in our sockaddr. We'd need to redirect through a shorter
|
||||
// path in order to actually connect to this socket.
|
||||
const UNIX_PATH_MAX = 108 // defined in afunix.h
|
||||
if len(l.hostPath) > UNIX_PATH_MAX {
|
||||
if len(l.hostPath) >= unixPathMax {
|
||||
return nil, unix.ECONNREFUSED
|
||||
}
|
||||
|
||||
@@ -1175,7 +1252,7 @@ func (l *localFile) Connect(flags p9.ConnectFlags) (*fd.FD, error) {
|
||||
return fd.New(f), nil
|
||||
}
|
||||
|
||||
// Close implements p9.File.
|
||||
// Close implements p9.File.Close.
|
||||
func (l *localFile) Close() error {
|
||||
l.mode = invalidMode
|
||||
err := l.file.Close()
|
||||
@@ -1290,3 +1367,22 @@ func (l *localFile) MultiGetAttr(names []string) ([]p9.FullStat, error) {
|
||||
}
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// socketLocalFile is an extension of localFile which is only created via Bind
|
||||
// and additionally implements Listen and Accept. It also tracks the lifecycle
|
||||
// of the socket FD created by socket(2) in addition to the FD opened on the
|
||||
// socket file itself.
|
||||
type socketLocalFile struct {
|
||||
*localFile
|
||||
sock int
|
||||
}
|
||||
|
||||
// Close implements p9.File.Close.
|
||||
func (l *socketLocalFile) Close() error {
|
||||
err := l.localFile.Close()
|
||||
err2 := unix.Close(l.sock)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return err2
|
||||
}
|
||||
|
||||
@@ -796,6 +796,47 @@ func TestReaddir(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestUDS(t *testing.T) {
|
||||
config := Config{ROMount: false, HostUDS: true}
|
||||
dir, err := ioutil.TempDir("", "root-")
|
||||
if err != nil {
|
||||
t.Fatalf("ioutil.TempDir() failed, err: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
// First attach with writable configuration to setup tree.
|
||||
a, err := NewAttachPoint(dir, config)
|
||||
if err != nil {
|
||||
t.Fatalf("NewAttachPoint failed: %v", err)
|
||||
}
|
||||
root, err := a.Attach()
|
||||
if err != nil {
|
||||
t.Fatalf("attach failed, err: %v", err)
|
||||
}
|
||||
defer root.Close()
|
||||
|
||||
name := "sock"
|
||||
uid := p9.UID(os.Getuid())
|
||||
gid := p9.GID(os.Getgid())
|
||||
sockF, _, valid, attr, err := root.Bind(unix.SOCK_STREAM, name, uid, gid)
|
||||
if err != nil {
|
||||
t.Fatalf("Bind failed: %v", err)
|
||||
}
|
||||
defer sockF.Close()
|
||||
|
||||
if valid.Mode && !attr.Mode.IsSocket() {
|
||||
t.Errorf("socket file mode is incorrect: want %d, got %d", p9.ModeSocket, attr.Mode)
|
||||
}
|
||||
if valid.UID && attr.UID != uid {
|
||||
t.Errorf("socket file uid is incorrect: want %d, got %d", uid, attr.UID)
|
||||
}
|
||||
if valid.GID && attr.GID != gid {
|
||||
t.Errorf("socket file gid is incorrect: want %d, got %d", gid, attr.GID)
|
||||
}
|
||||
// TODO(b/194709873): Once listen and accept are implemented, test connecting
|
||||
// and accepting a connection using sockF.
|
||||
}
|
||||
|
||||
// Test that attach point can be written to when it points to a file, e.g.
|
||||
// /etc/hosts.
|
||||
func TestAttachFile(t *testing.T) {
|
||||
|
||||
@@ -655,7 +655,7 @@ func (fd *controlFDLisa) Connect(c *lisafs.Connection, sockType uint32) (int, er
|
||||
// hostPath in our sockaddr. We'd need to redirect through a shorter path
|
||||
// in order to actually connect to this socket.
|
||||
hostPath := fd.FilePathLocked()
|
||||
if len(hostPath) > 108 { // UNIX_PATH_MAX = 108 is defined in afunix.h.
|
||||
if len(hostPath) >= unixPathMax {
|
||||
return -1, unix.ECONNREFUSED
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user