[lisa] Plumb lisafs through runsc.

lisafs is only supported in VFS2. Added a runsc flag which enables lisafs.
When the flag is enabled, the gofer process and the client communicate using
lisafs protocol instead of 9P.

Added a filesystem option in fsimpl/gofer which indicates if lisafs is being
used. That will be used to gate lisafs on the gofer client.

Note that this change does not make the gofer client use lisafs just yet.

Updates #5465

PiperOrigin-RevId: 397917844
This commit is contained in:
Ayush Ranjan
2021-09-20 22:34:01 -07:00
committed by gVisor bot
parent 3fe8d7ecf1
commit e819029f3a
12 changed files with 185 additions and 35 deletions
+3
View File
@@ -1612,6 +1612,9 @@ func (fs *filesystem) MountOptions() string {
if fs.opts.overlayfsStaleRead {
optsKV = append(optsKV, mopt{moptOverlayfsStaleRead, nil})
}
if fs.opts.lisaEnabled {
optsKV = append(optsKV, mopt{moptLisafs, nil})
}
opts := make([]string, 0, len(optsKV))
for _, opt := range optsKV {
+13
View File
@@ -83,6 +83,7 @@ const (
moptForcePageCache = "force_page_cache"
moptLimitHostFDTranslation = "limit_host_fd_translation"
moptOverlayfsStaleRead = "overlayfs_stale_read"
moptLisafs = "lisafs"
)
// Valid values for the "cache" mount option.
@@ -214,6 +215,10 @@ type filesystemOptions struct {
// way that application FDs representing "special files" such as sockets
// do. Note that this disables client caching and mmap for regular files.
regularFilesUseSpecialFileFD bool
// lisaEnabled indicates whether the client will use lisafs protocol to
// communicate with the server instead of 9P.
lisaEnabled bool
}
// InteropMode controls the client's interaction with other remote filesystem
@@ -427,6 +432,14 @@ func (fstype FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt
delete(mopts, moptOverlayfsStaleRead)
fsopts.overlayfsStaleRead = true
}
if lisafs, ok := mopts[moptLisafs]; ok {
delete(mopts, moptLisafs)
fsopts.lisaEnabled, err = strconv.ParseBool(lisafs)
if err != nil {
ctx.Warningf("gofer.FilesystemType.GetFilesystem: invalid lisafs option: %s", lisafs)
return nil, nil, linuxerr.EINVAL
}
}
// fsopts.regularFilesUseSpecialFileFD can only be enabled by specifying
// "cache=none".
+9 -5
View File
@@ -188,8 +188,8 @@ func compileMounts(spec *specs.Spec, conf *config.Config, vfs2Enabled bool) []sp
return mounts
}
// p9MountData creates a slice of p9 mount data.
func p9MountData(fd int, fa config.FileAccessType, vfs2 bool) []string {
// goferMountData creates a slice of gofer mount data.
func goferMountData(fd int, fa config.FileAccessType, attachPath string, vfs2 bool, lisafs bool) []string {
opts := []string{
"trans=fd",
"rfdno=" + strconv.Itoa(fd),
@@ -203,6 +203,10 @@ func p9MountData(fd int, fa config.FileAccessType, vfs2 bool) []string {
if fa == config.FileAccessShared {
opts = append(opts, "cache=remote_revalidating")
}
if vfs2 && lisafs {
opts = append(opts, "lisafs=true")
opts = append(opts, "aname="+attachPath)
}
return opts
}
@@ -761,7 +765,7 @@ func (c *containerMounter) createRootMount(ctx context.Context, conf *config.Con
fd := c.fds.remove()
log.Infof("Mounting root over 9P, ioFD: %d", fd)
p9FS := mustFindFilesystem("9p")
opts := p9MountData(fd, conf.FileAccess, false /* vfs2 */)
opts := goferMountData(fd, conf.FileAccess, "/", false /* vfs2 */, false /* lisafs */)
// We can't check for overlayfs here because sandbox is chroot'ed and gofer
// can only send mount options for specs.Mounts (specs.Root is missing
@@ -822,7 +826,7 @@ func (c *containerMounter) getMountNameAndOptions(conf *config.Config, m *specs.
case bind:
fd := c.fds.remove()
fsName = gofervfs2.Name
opts = p9MountData(fd, c.getMountAccessType(conf, m), conf.VFS2)
opts = goferMountData(fd, c.getMountAccessType(conf, m), m.Destination, conf.VFS2, conf.Lisafs)
// If configured, add overlay to all writable mounts.
useOverlay = conf.Overlay && !mountFlags(m.Options).ReadOnly
case cgroupfs.Name:
@@ -980,7 +984,7 @@ func (c *containerMounter) createRestoreEnvironment(conf *config.Config) (*fs.Re
// Add root mount.
fd := c.fds.remove()
opts := p9MountData(fd, conf.FileAccess, false /* vfs2 */)
opts := goferMountData(fd, conf.FileAccess, "/", false /* vfs2 */, false /* lisafs */)
mf := fs.MountSourceFlags{}
if c.root.Readonly || conf.Overlay {
+2 -2
View File
@@ -208,7 +208,7 @@ func (c *containerMounter) mountAll(conf *config.Config, procArgs *kernel.Create
// createMountNamespaceVFS2 creates the container's root mount and namespace.
func (c *containerMounter) createMountNamespaceVFS2(ctx context.Context, conf *config.Config, creds *auth.Credentials) (*vfs.MountNamespace, error) {
fd := c.fds.remove()
data := p9MountData(fd, conf.FileAccess, true /* vfs2 */)
data := goferMountData(fd, conf.FileAccess, "/", true /* vfs2 */, conf.Lisafs)
// We can't check for overlayfs here because sandbox is chroot'ed and gofer
// can only send mount options for specs.Mounts (specs.Root is missing
@@ -515,7 +515,7 @@ func (c *containerMounter) getMountNameAndOptionsVFS2(conf *config.Config, m *mo
// but unlikely to be correct in this context.
return "", nil, false, fmt.Errorf("9P mount requires a connection FD")
}
data = p9MountData(m.fd, c.getMountAccessType(conf, m.mount), true /* vfs2 */)
data = goferMountData(m.fd, c.getMountAccessType(conf, m.mount), m.mount.Destination, true /* vfs2 */, conf.Lisafs)
internalData = gofer.InternalFilesystemOptions{
UniqueID: m.mount.Destination,
}
+1 -1
View File
@@ -228,7 +228,7 @@ func Main(version string) {
log.Infof("\t\tFileAccess: %v, overlay: %t", conf.FileAccess, conf.Overlay)
log.Infof("\t\tNetwork: %v, logging: %t", conf.Network, conf.LogPackets)
log.Infof("\t\tStrace: %t, max size: %d, syscalls: %s", conf.Strace, conf.StraceLogSize, conf.StraceSyscalls)
log.Infof("\t\tVFS2 enabled: %v", conf.VFS2)
log.Infof("\t\tVFS2 enabled: %t, LISAFS: %t", conf.VFS2, conf.Lisafs)
log.Infof("***************************")
if conf.TestOnlyAllowRunAsCurrentUserWithoutChroot {
+96 -24
View File
@@ -71,8 +71,8 @@ func (*Gofer) Name() string {
}
// Synopsis implements subcommands.Command.
func (*Gofer) Synopsis() string {
return "launch a gofer process that serves files over 9P protocol (internal use only)"
func (g *Gofer) Synopsis() string {
return fmt.Sprintf("launch a gofer process that serves files over the protocol (9P or lisafs) defined in the config (internal use only)")
}
// Usage implements subcommands.Command.
@@ -83,7 +83,7 @@ func (*Gofer) Usage() string {
// SetFlags implements subcommands.Command.
func (g *Gofer) SetFlags(f *flag.FlagSet) {
f.StringVar(&g.bundleDir, "bundle", "", "path to the root of the bundle directory, defaults to the current directory")
f.Var(&g.ioFDs, "io-fds", "list of FDs to connect 9P servers. They must follow this order: root first, then mounts as defined in the spec")
f.Var(&g.ioFDs, "io-fds", "list of FDs to connect gofer servers. They must follow this order: root first, then mounts as defined in the spec")
f.BoolVar(&g.applyCaps, "apply-caps", true, "if true, apply capabilities to restrict what the Gofer process can do")
f.BoolVar(&g.setUpRoot, "setup-root", true, "if true, set up an empty root for the process")
f.IntVar(&g.specFD, "spec-fd", -1, "required fd with the container spec")
@@ -160,10 +160,98 @@ func (g *Gofer) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
}
log.Infof("Process chroot'd to %q", root)
// Initialize filters.
if conf.FSGoferHostUDS {
filter.InstallUDSFilters()
}
if conf.Verity {
filter.InstallXattrFilters()
}
if err := filter.Install(); err != nil {
Fatalf("installing seccomp filters: %v", err)
}
if conf.Lisafs {
return g.serveLisafs(spec, conf, root)
}
return g.serve9P(spec, conf, root)
}
func newSocket(ioFD int) *unet.Socket {
socket, err := unet.NewSocket(ioFD)
if err != nil {
Fatalf("creating server on FD %d: %v", ioFD, err)
}
return socket
}
func (g *Gofer) serveLisafs(spec *specs.Spec, conf *config.Config, root string) subcommands.ExitStatus {
type connectionConfig struct {
sock *unet.Socket
readonly bool
}
cfgs := make([]connectionConfig, 0, len(spec.Mounts)+1)
server := fsgofer.NewLisafsServer(fsgofer.Config{
// These are global options. Ignore readonly configuration, that is set on
// a per connection basis.
HostUDS: conf.FSGoferHostUDS,
EnableVerityXattr: conf.Verity,
})
// Start with root mount, then add any other additional mount as needed.
cfgs = append(cfgs, connectionConfig{
sock: newSocket(g.ioFDs[0]),
readonly: spec.Root.Readonly || conf.Overlay,
})
log.Infof("Serving %q mapped to %q on FD %d (ro: %t)", "/", root, g.ioFDs[0], cfgs[0].readonly)
mountIdx := 1 // first one is the root
for _, m := range spec.Mounts {
if !specutils.IsGoferMount(m, conf.VFS2) {
continue
}
if !filepath.IsAbs(m.Destination) {
Fatalf("mount destination must be absolute: %q", m.Destination)
}
if mountIdx >= len(g.ioFDs) {
Fatalf("no FD found for mount. Did you forget --io-fd? FDs: %d, Mount: %+v", len(g.ioFDs), m)
}
cfgs = append(cfgs, connectionConfig{
sock: newSocket(g.ioFDs[mountIdx]),
readonly: isReadonlyMount(m.Options) || conf.Overlay,
})
log.Infof("Serving %q mapped on FD %d (ro: %t)", m.Destination, g.ioFDs[mountIdx], cfgs[mountIdx].readonly)
mountIdx++
}
if mountIdx != len(g.ioFDs) {
Fatalf("too many FDs passed for mounts. mounts: %d, FDs: %d", mountIdx, len(g.ioFDs))
}
cfgs = cfgs[:mountIdx]
for _, cfg := range cfgs {
conn, err := server.CreateConnection(cfg.sock, cfg.readonly)
if err != nil {
Fatalf("starting connection on FD %d for gofer mount failed: %v", cfg.sock.FD(), err)
}
server.StartConnection(conn)
}
server.Wait()
log.Infof("All lisafs servers exited.")
return subcommands.ExitSuccess
}
func (g *Gofer) serve9P(spec *specs.Spec, conf *config.Config, root string) subcommands.ExitStatus {
// Start with root mount, then add any other additional mount as needed.
ats := make([]p9.Attacher, 0, len(spec.Mounts)+1)
ap, err := fsgofer.NewAttachPoint("/", fsgofer.Config{
ROMount: spec.Root.Readonly || conf.Overlay,
HostUDS: conf.FSGoferHostUDS,
EnableVerityXattr: conf.Verity,
})
if err != nil {
@@ -174,7 +262,7 @@ func (g *Gofer) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
mountIdx := 1 // first one is the root
for _, m := range spec.Mounts {
if specutils.Is9PMount(m, conf.VFS2) {
if specutils.IsGoferMount(m, conf.VFS2) {
cfg := fsgofer.Config{
ROMount: isReadonlyMount(m.Options) || conf.Overlay,
HostUDS: conf.FSGoferHostUDS,
@@ -197,26 +285,9 @@ func (g *Gofer) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
Fatalf("too many FDs passed for mounts. mounts: %d, FDs: %d", mountIdx, len(g.ioFDs))
}
if conf.FSGoferHostUDS {
filter.InstallUDSFilters()
}
if conf.Verity {
filter.InstallXattrFilters()
}
if err := filter.Install(); err != nil {
Fatalf("installing seccomp filters: %v", err)
}
runServers(ats, g.ioFDs)
return subcommands.ExitSuccess
}
func runServers(ats []p9.Attacher, ioFDs []int) {
// Run the loops and wait for all to exit.
var wg sync.WaitGroup
for i, ioFD := range ioFDs {
for i, ioFD := range g.ioFDs {
wg.Add(1)
go func(ioFD int, at p9.Attacher) {
socket, err := unet.NewSocket(ioFD)
@@ -232,6 +303,7 @@ func runServers(ats []p9.Attacher, ioFDs []int) {
}
wg.Wait()
log.Infof("All 9P servers exited.")
return subcommands.ExitSuccess
}
func (g *Gofer) writeMounts(mounts []specs.Mount) error {
@@ -362,7 +434,7 @@ func setupRootFS(spec *specs.Spec, conf *config.Config) error {
// creates directories as needed.
func setupMounts(conf *config.Config, mounts []specs.Mount, root, procPath string) error {
for _, m := range mounts {
if !specutils.Is9PMount(m, conf.VFS2) {
if !specutils.IsGoferMount(m, conf.VFS2) {
continue
}
@@ -402,7 +474,7 @@ func setupMounts(conf *config.Config, mounts []specs.Mount, root, procPath strin
func resolveMounts(conf *config.Config, mounts []specs.Mount, root string) ([]specs.Mount, error) {
cleanMounts := make([]specs.Mount, 0, len(mounts))
for _, m := range mounts {
if !specutils.Is9PMount(m, conf.VFS2) {
if !specutils.IsGoferMount(m, conf.VFS2) {
cleanMounts = append(cleanMounts, m)
continue
}
+3
View File
@@ -193,6 +193,9 @@ type Config struct {
// Enables VFS2.
VFS2 bool `flag:"vfs2"`
// Enable lisafs.
Lisafs bool `flag:"lisafs"`
// Enables FUSE usage.
FUSE bool `flag:"fuse"`
+1
View File
@@ -82,6 +82,7 @@ func RegisterFlags() {
flag.Bool("fsgofer-host-uds", false, "allow the gofer to mount Unix Domain Sockets.")
flag.Bool("vfs2", false, "enables VFSv2. This uses the new VFS layer that is faster than the previous one.")
flag.Bool("fuse", false, "TEST ONLY; use while FUSE in VFSv2 is landing. This allows the use of the new experimental FUSE filesystem.")
flag.Bool("lisafs", false, "Enables lisafs protocol instead of 9P. This is only effective with VFS2.")
flag.Bool("cgroupfs", false, "Automatically mount cgroupfs.")
// Flags that control sandbox runtime behavior: network related.
+1 -1
View File
@@ -917,7 +917,7 @@ func (c *Container) createGoferProcess(spec *specs.Spec, conf *config.Config, bu
// Add root mount and then add any other additional mounts.
mountCount := 1
for _, m := range spec.Mounts {
if specutils.Is9PMount(m, conf.VFS2) {
if specutils.IsGoferMount(m, conf.VFS2) {
mountCount++
}
}
+2
View File
@@ -9,11 +9,13 @@ go_library(
"fsgofer_amd64_unsafe.go",
"fsgofer_arm64_unsafe.go",
"fsgofer_unsafe.go",
"lisafs.go",
],
visibility = ["//runsc:__subpackages__"],
deps = [
"//pkg/cleanup",
"//pkg/fd",
"//pkg/lisafs",
"//pkg/log",
"//pkg/p9",
"//pkg/sync",
+52
View File
@@ -0,0 +1,52 @@
// Copyright 2021 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package fsgofer
import (
"gvisor.dev/gvisor/pkg/lisafs"
)
// LisafsServer implements lisafs.ServerImpl for fsgofer.
type LisafsServer struct {
lisafs.Server
config Config
}
var _ lisafs.ServerImpl = (*LisafsServer)(nil)
// NewLisafsServer initializes a new lisafs server for fsgofer.
func NewLisafsServer(config Config) *LisafsServer {
s := &LisafsServer{config: config}
s.Server.Init(s)
return s
}
// Mount implements lisafs.ServerImpl.Mount.
func (s *LisafsServer) Mount(c *lisafs.Connection, mountPath string) (lisafs.ControlFDImpl, lisafs.Inode, error) {
panic("unimplemented")
}
// MaxMessageSize implements lisafs.ServerImpl.MaxMessageSize.
func (s *LisafsServer) MaxMessageSize() uint32 {
return lisafs.MaxMessageSize()
}
// SupportedMessages implements lisafs.ServerImpl.SupportedMessages.
func (s *LisafsServer) SupportedMessages() []lisafs.MID {
return []lisafs.MID{
lisafs.Mount,
lisafs.Channel,
}
}
+2 -2
View File
@@ -332,9 +332,9 @@ func capsFromNames(names []string, skipSet map[linux.Capability]struct{}) (auth.
return auth.CapabilitySetOfMany(caps), nil
}
// Is9PMount returns true if the given mount can be mounted as an external
// IsGoferMount returns true if the given mount can be mounted as an external
// gofer.
func Is9PMount(m specs.Mount, vfs2Enabled bool) bool {
func IsGoferMount(m specs.Mount, vfs2Enabled bool) bool {
MaybeConvertToBindMount(&m)
return m.Type == "bind" && m.Source != "" && IsSupportedDevMount(m, vfs2Enabled)
}