Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
| ... | ... |
@@ -4,7 +4,7 @@ TOMLV_COMMIT=9baf8a8a9f2ed20a8e54160840c492f937eeaf9a |
| 4 | 4 |
|
| 5 | 5 |
# When updating RUNC_COMMIT, also update runc in vendor.conf accordingly |
| 6 | 6 |
RUNC_COMMIT=b2567b37d7b75eb4cf325b77297b140ea686ce8f |
| 7 |
-CONTAINERD_COMMIT=6bff39c643886dfa3d546e83a90a527b64ddeacf |
|
| 7 |
+CONTAINERD_COMMIT=cc969fb42f427a68a8cc6870ef47f17304b83962 |
|
| 8 | 8 |
TINI_COMMIT=949e6facb77383876aeff8a6944dde66b3089574 |
| 9 | 9 |
LIBNETWORK_COMMIT=7b2b1feb1de4817d522cc372af149ff48d25028e |
| 10 | 10 |
VNDR_COMMIT=a6e196d8b4b0cbbdc29aebdb20c59ac6926bb384 |
| ... | ... |
@@ -103,7 +103,7 @@ github.com/googleapis/gax-go da06d194a00e19ce00d9011a13931c3f6f6887c7 |
| 103 | 103 |
google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944 |
| 104 | 104 |
|
| 105 | 105 |
# containerd |
| 106 |
-github.com/containerd/containerd 6bff39c643886dfa3d546e83a90a527b64ddeacf |
|
| 106 |
+github.com/containerd/containerd cc969fb42f427a68a8cc6870ef47f17304b83962 |
|
| 107 | 107 |
github.com/containerd/fifo fbfb6a11ec671efbe94ad1c12c2e98773f19e1e6 |
| 108 | 108 |
github.com/containerd/continuity 35d55c5e8dd23b32037d56cf97174aff3efdfa83 |
| 109 | 109 |
github.com/containerd/cgroups 29da22c6171a4316169f9205ab6c49f59b5b852f |
| ... | ... |
@@ -111,7 +111,7 @@ github.com/containerd/console 84eeaae905fa414d03e07bcd6c8d3f19e7cf180e |
| 111 | 111 |
github.com/containerd/go-runc ed1cbe1fc31f5fb2359d3a54b6330d1a097858b7 |
| 112 | 112 |
github.com/containerd/typeurl f6943554a7e7e88b3c14aad190bf05932da84788 |
| 113 | 113 |
github.com/dmcgowan/go-tar go1.10 |
| 114 |
-github.com/stevvooe/ttrpc 8c92e22ce0c492875ccaac3ab06143a77d8ed0c1 |
|
| 114 |
+github.com/stevvooe/ttrpc 76e68349ad9ab4d03d764c713826d31216715e4f |
|
| 115 | 115 |
|
| 116 | 116 |
# cluster |
| 117 | 117 |
github.com/docker/swarmkit de950a7ed842c7b7e47e9451cde9bf8f96031894 |
| ... | ... |
@@ -15,6 +15,28 @@ containerd is designed to be embedded into a larger system, rather than being us |
| 15 | 15 |
|
| 16 | 16 |
If you are interested in trying out containerd please see our [Getting Started Guide](docs/getting-started.md). |
| 17 | 17 |
|
| 18 |
+## Runtime Requirements |
|
| 19 |
+ |
|
| 20 |
+Runtime requirements for containerd are very minimal. Most interactions with |
|
| 21 |
+the Linux and Windows container feature sets are handled via [runc](https://github.com/opencontainers/runc) and/or |
|
| 22 |
+OS-specific libraries (e.g. [hcsshim](https://github.com/Microsoft/hcsshim) for Microsoft). There are specific features |
|
| 23 |
+used by containerd core code and snapshotters that will require a minimum kernel |
|
| 24 |
+version on Linux. With the understood caveat of distro kernel versioning, a |
|
| 25 |
+reasonable starting point for Linux is a minimum 4.x kernel version. |
|
| 26 |
+ |
|
| 27 |
+The overlay filesystem snapshotter, used by default, uses features that were |
|
| 28 |
+finalized in the 4.x kernel series. If you choose to use btrfs, there may |
|
| 29 |
+be more flexibility in kernel version (minimum recommended is 3.18), but will |
|
| 30 |
+require the btrfs kernel module and btrfs tools to be installed on your Linux |
|
| 31 |
+distribution. |
|
| 32 |
+ |
|
| 33 |
+To use Linux checkpoint and restore features, you will need `criu` installed on |
|
| 34 |
+your system. See more details in [Checkpoint and Restore](#checkpoint-and-restore). |
|
| 35 |
+ |
|
| 36 |
+The current required version of runc is always listed in [RUNC.md](/RUNC.md). |
|
| 37 |
+ |
|
| 38 |
+Build requirements for developers are listed in the [Developer Quick-Start](#developer-quick-start) section. |
|
| 39 |
+ |
|
| 18 | 40 |
## Features |
| 19 | 41 |
|
| 20 | 42 |
### Client |
| ... | ... |
@@ -93,7 +115,6 @@ image, err := client.Pull(context, "docker.io/library/redis:latest", containerd. |
| 93 | 93 |
redis, err := client.NewContainer(context, "redis-master", |
| 94 | 94 |
containerd.WithNewSnapshot("redis-rootfs", image),
|
| 95 | 95 |
containerd.WithNewSpec(oci.WithImageConfig(image)), |
| 96 |
- |
|
| 97 | 96 |
) |
| 98 | 97 |
|
| 99 | 98 |
// use a readonly filesystem with multiple containers |
| ... | ... |
@@ -150,7 +171,7 @@ defer task.Delete(context) |
| 150 | 150 |
err := task.Start(context) |
| 151 | 151 |
``` |
| 152 | 152 |
|
| 153 |
-## Developer Quick-Start |
|
| 153 |
+## Developer Quick Start |
|
| 154 | 154 |
|
| 155 | 155 |
To build the daemon and `ctr` simple test client, the following build system dependencies are required: |
| 156 | 156 |
|
| ... | ... |
@@ -162,11 +162,17 @@ func (c *container) Image(ctx context.Context) (Image, error) {
|
| 162 | 162 |
}, nil |
| 163 | 163 |
} |
| 164 | 164 |
|
| 165 |
-func (c *container) NewTask(ctx context.Context, ioCreate cio.Creation, opts ...NewTaskOpts) (Task, error) {
|
|
| 165 |
+func (c *container) NewTask(ctx context.Context, ioCreate cio.Creation, opts ...NewTaskOpts) (_ Task, err error) {
|
|
| 166 | 166 |
i, err := ioCreate(c.id) |
| 167 | 167 |
if err != nil {
|
| 168 | 168 |
return nil, err |
| 169 | 169 |
} |
| 170 |
+ defer func() {
|
|
| 171 |
+ if err != nil && i != nil {
|
|
| 172 |
+ i.Cancel() |
|
| 173 |
+ i.Close() |
|
| 174 |
+ } |
|
| 175 |
+ }() |
|
| 170 | 176 |
cfg := i.Config() |
| 171 | 177 |
request := &tasks.CreateTaskRequest{
|
| 172 | 178 |
ContainerID: c.id, |
| ... | ... |
@@ -24,7 +24,6 @@ import ( |
| 24 | 24 |
"github.com/opencontainers/image-spec/identity" |
| 25 | 25 |
"github.com/opencontainers/image-spec/specs-go/v1" |
| 26 | 26 |
"github.com/pkg/errors" |
| 27 |
- "golang.org/x/sys/unix" |
|
| 28 | 27 |
) |
| 29 | 28 |
|
| 30 | 29 |
// WithCheckpoint allows a container to be created from the checkpointed information |
| ... | ... |
@@ -193,14 +192,17 @@ func remapRootFS(mounts []mount.Mount, uid, gid uint32) error {
|
| 193 | 193 |
if err != nil {
|
| 194 | 194 |
return err |
| 195 | 195 |
} |
| 196 |
- defer os.RemoveAll(root) |
|
| 196 |
+ defer os.Remove(root) |
|
| 197 | 197 |
for _, m := range mounts {
|
| 198 | 198 |
if err := m.Mount(root); err != nil {
|
| 199 | 199 |
return err |
| 200 | 200 |
} |
| 201 | 201 |
} |
| 202 |
- defer unix.Unmount(root, 0) |
|
| 203 |
- return filepath.Walk(root, incrementFS(root, uid, gid)) |
|
| 202 |
+ err = filepath.Walk(root, incrementFS(root, uid, gid)) |
|
| 203 |
+ if uerr := mount.Unmount(root, 0); err == nil {
|
|
| 204 |
+ err = uerr |
|
| 205 |
+ } |
|
| 206 |
+ return err |
|
| 204 | 207 |
} |
| 205 | 208 |
|
| 206 | 209 |
func incrementFS(root string, uidInc, gidInc uint32) filepath.WalkFunc {
|
| ... | ... |
@@ -62,7 +62,7 @@ func NewStore(root string) (content.Store, error) {
|
| 62 | 62 |
// require labels and should use `NewStore`. `NewLabeledStore` is primarily |
| 63 | 63 |
// useful for tests or standalone implementations. |
| 64 | 64 |
func NewLabeledStore(root string, ls LabelStore) (content.Store, error) {
|
| 65 |
- if err := os.MkdirAll(filepath.Join(root, "ingest"), 0777); err != nil && !os.IsExist(err) {
|
|
| 65 |
+ if err := os.MkdirAll(filepath.Join(root, "ingest"), 0777); err != nil {
|
|
| 66 | 66 |
return nil, err |
| 67 | 67 |
} |
| 68 | 68 |
|
| ... | ... |
@@ -147,7 +147,7 @@ func (i *image) getLayers(ctx context.Context, platform string) ([]rootfs.Layer, |
| 147 | 147 |
|
| 148 | 148 |
manifest, err := images.Manifest(ctx, cs, i.i.Target, platform) |
| 149 | 149 |
if err != nil {
|
| 150 |
- return nil, errors.Wrap(err, "") |
|
| 150 |
+ return nil, err |
|
| 151 | 151 |
} |
| 152 | 152 |
|
| 153 | 153 |
diffIDs, err := i.i.RootFS(ctx, cs, platform) |
| ... | ... |
@@ -187,13 +187,13 @@ func Manifest(ctx context.Context, provider content.Provider, image ocispec.Desc |
| 187 | 187 |
return descs, nil |
| 188 | 188 |
|
| 189 | 189 |
} |
| 190 |
- return nil, errors.Wrap(errdefs.ErrNotFound, "could not resolve manifest") |
|
| 190 |
+ return nil, errors.Wrapf(errdefs.ErrNotFound, "unexpected media type %v for %v", desc.MediaType, desc.Digest) |
|
| 191 | 191 |
}), image); err != nil {
|
| 192 | 192 |
return ocispec.Manifest{}, err
|
| 193 | 193 |
} |
| 194 | 194 |
|
| 195 | 195 |
if m == nil {
|
| 196 |
- return ocispec.Manifest{}, errors.Wrap(errdefs.ErrNotFound, "manifest not found")
|
|
| 196 |
+ return ocispec.Manifest{}, errors.Wrapf(errdefs.ErrNotFound, "manifest %v", image.Digest)
|
|
| 197 | 197 |
} |
| 198 | 198 |
|
| 199 | 199 |
return *m, nil |
| ... | ... |
@@ -257,7 +257,7 @@ func Check(ctx context.Context, provider content.Provider, image ocispec.Descrip |
| 257 | 257 |
return false, []ocispec.Descriptor{image}, nil, []ocispec.Descriptor{image}, nil
|
| 258 | 258 |
} |
| 259 | 259 |
|
| 260 |
- return false, nil, nil, nil, errors.Wrap(err, "image check failed") |
|
| 260 |
+ return false, nil, nil, nil, errors.Wrapf(err, "failed to check image %v", image.Digest) |
|
| 261 | 261 |
} |
| 262 | 262 |
|
| 263 | 263 |
// TODO(stevvooe): It is possible that referenced conponents could have |
| ... | ... |
@@ -272,7 +272,7 @@ func Check(ctx context.Context, provider content.Provider, image ocispec.Descrip |
| 272 | 272 |
missing = append(missing, desc) |
| 273 | 273 |
continue |
| 274 | 274 |
} else {
|
| 275 |
- return false, nil, nil, nil, err |
|
| 275 |
+ return false, nil, nil, nil, errors.Wrapf(err, "failed to check image %v", desc.Digest) |
|
| 276 | 276 |
} |
| 277 | 277 |
} |
| 278 | 278 |
ra.Close() |
| ... | ... |
@@ -75,10 +75,10 @@ type bundle struct {
|
| 75 | 75 |
type ShimOpt func(*bundle, string, *runctypes.RuncOptions) (shim.Config, client.Opt) |
| 76 | 76 |
|
| 77 | 77 |
// ShimRemote is a ShimOpt for connecting and starting a remote shim |
| 78 |
-func ShimRemote(shimBinary, daemonAddress, cgroup string, nonewns, debug bool, exitHandler func()) ShimOpt {
|
|
| 78 |
+func ShimRemote(shimBinary, daemonAddress, cgroup string, debug bool, exitHandler func()) ShimOpt {
|
|
| 79 | 79 |
return func(b *bundle, ns string, ropts *runctypes.RuncOptions) (shim.Config, client.Opt) {
|
| 80 | 80 |
return b.shimConfig(ns, ropts), |
| 81 |
- client.WithStart(shimBinary, b.shimAddress(ns), daemonAddress, cgroup, nonewns, debug, exitHandler) |
|
| 81 |
+ client.WithStart(shimBinary, b.shimAddress(ns), daemonAddress, cgroup, debug, exitHandler) |
|
| 82 | 82 |
} |
| 83 | 83 |
} |
| 84 | 84 |
|
| ... | ... |
@@ -22,6 +22,7 @@ import ( |
| 22 | 22 |
shim "github.com/containerd/containerd/linux/shim/v1" |
| 23 | 23 |
"github.com/containerd/containerd/log" |
| 24 | 24 |
"github.com/containerd/containerd/metadata" |
| 25 |
+ "github.com/containerd/containerd/mount" |
|
| 25 | 26 |
"github.com/containerd/containerd/namespaces" |
| 26 | 27 |
"github.com/containerd/containerd/platforms" |
| 27 | 28 |
"github.com/containerd/containerd/plugin" |
| ... | ... |
@@ -78,17 +79,6 @@ type Config struct {
|
| 78 | 78 |
NoShim bool `toml:"no_shim"` |
| 79 | 79 |
// Debug enable debug on the shim |
| 80 | 80 |
ShimDebug bool `toml:"shim_debug"` |
| 81 |
- // ShimNoMountNS prevents the runtime from putting shims into their own mount namespace. |
|
| 82 |
- // |
|
| 83 |
- // Putting the shim in its own mount namespace ensure that any mounts made |
|
| 84 |
- // by it in order to get the task rootfs ready will be undone regardless |
|
| 85 |
- // on how the shim dies. |
|
| 86 |
- // |
|
| 87 |
- // NOTE: This should only be used in kernel older than 3.18 to avoid shims |
|
| 88 |
- // from causing a DoS in their parent namespace due to having a copy of |
|
| 89 |
- // mounts previously there which would prevent unlink, rename and remove |
|
| 90 |
- // operations on those mountpoints. |
|
| 91 |
- ShimNoMountNS bool `toml:"shim_no_newns"` |
|
| 92 | 81 |
} |
| 93 | 82 |
|
| 94 | 83 |
// New returns a configured runtime |
| ... | ... |
@@ -226,8 +216,7 @@ func (r *Runtime) Create(ctx context.Context, id string, opts runtime.CreateOpts |
| 226 | 226 |
}).Warn("failed to clen up after killed shim")
|
| 227 | 227 |
} |
| 228 | 228 |
} |
| 229 |
- shimopt = ShimRemote(r.config.Shim, r.address, cgroup, |
|
| 230 |
- r.config.ShimNoMountNS, r.config.ShimDebug, exitHandler) |
|
| 229 |
+ shimopt = ShimRemote(r.config.Shim, r.address, cgroup, r.config.ShimDebug, exitHandler) |
|
| 231 | 230 |
} |
| 232 | 231 |
|
| 233 | 232 |
s, err := bundle.NewShimClient(ctx, namespace, shimopt, ropts) |
| ... | ... |
@@ -486,7 +475,7 @@ func (r *Runtime) terminate(ctx context.Context, bundle *bundle, ns, id string) |
| 486 | 486 |
}); err != nil {
|
| 487 | 487 |
log.G(ctx).WithError(err).Warnf("delete runtime state %s", id)
|
| 488 | 488 |
} |
| 489 |
- if err := unix.Unmount(filepath.Join(bundle.path, "rootfs"), 0); err != nil {
|
|
| 489 |
+ if err := mount.Unmount(filepath.Join(bundle.path, "rootfs"), 0); err != nil {
|
|
| 490 | 490 |
log.G(ctx).WithError(err).WithFields(logrus.Fields{
|
| 491 | 491 |
"path": bundle.path, |
| 492 | 492 |
"id": id, |
| ... | ... |
@@ -34,7 +34,7 @@ var empty = &ptypes.Empty{}
|
| 34 | 34 |
type Opt func(context.Context, shim.Config) (shimapi.ShimService, io.Closer, error) |
| 35 | 35 |
|
| 36 | 36 |
// WithStart executes a new shim process |
| 37 |
-func WithStart(binary, address, daemonAddress, cgroup string, nonewns, debug bool, exitHandler func()) Opt {
|
|
| 37 |
+func WithStart(binary, address, daemonAddress, cgroup string, debug bool, exitHandler func()) Opt {
|
|
| 38 | 38 |
return func(ctx context.Context, config shim.Config) (_ shimapi.ShimService, _ io.Closer, err error) {
|
| 39 | 39 |
socket, err := newSocket(address) |
| 40 | 40 |
if err != nil {
|
| ... | ... |
@@ -47,7 +47,7 @@ func WithStart(binary, address, daemonAddress, cgroup string, nonewns, debug boo |
| 47 | 47 |
} |
| 48 | 48 |
defer f.Close() |
| 49 | 49 |
|
| 50 |
- cmd := newCommand(binary, daemonAddress, nonewns, debug, config, f) |
|
| 50 |
+ cmd := newCommand(binary, daemonAddress, debug, config, f) |
|
| 51 | 51 |
ec, err := reaper.Default.Start(cmd) |
| 52 | 52 |
if err != nil {
|
| 53 | 53 |
return nil, nil, errors.Wrapf(err, "failed to start shim") |
| ... | ... |
@@ -87,7 +87,7 @@ func WithStart(binary, address, daemonAddress, cgroup string, nonewns, debug boo |
| 87 | 87 |
} |
| 88 | 88 |
} |
| 89 | 89 |
|
| 90 |
-func newCommand(binary, daemonAddress string, nonewns, debug bool, config shim.Config, socket *os.File) *exec.Cmd {
|
|
| 90 |
+func newCommand(binary, daemonAddress string, debug bool, config shim.Config, socket *os.File) *exec.Cmd {
|
|
| 91 | 91 |
selfExe, err := os.Executable() |
| 92 | 92 |
if err != nil {
|
| 93 | 93 |
panic(err) |
| ... | ... |
@@ -117,7 +117,7 @@ func newCommand(binary, daemonAddress string, nonewns, debug bool, config shim.C |
| 117 | 117 |
// make sure the shim can be re-parented to system init |
| 118 | 118 |
// and is cloned in a new mount namespace because the overlay/filesystems |
| 119 | 119 |
// will be mounted by the shim |
| 120 |
- cmd.SysProcAttr = getSysProcAttr(nonewns) |
|
| 120 |
+ cmd.SysProcAttr = getSysProcAttr() |
|
| 121 | 121 |
cmd.ExtraFiles = append(cmd.ExtraFiles, socket) |
| 122 | 122 |
if debug {
|
| 123 | 123 |
cmd.Stdout = os.Stdout |
| ... | ... |
@@ -10,14 +10,10 @@ import ( |
| 10 | 10 |
"github.com/pkg/errors" |
| 11 | 11 |
) |
| 12 | 12 |
|
| 13 |
-func getSysProcAttr(nonewns bool) *syscall.SysProcAttr {
|
|
| 14 |
- attr := syscall.SysProcAttr{
|
|
| 13 |
+func getSysProcAttr() *syscall.SysProcAttr {
|
|
| 14 |
+ return &syscall.SysProcAttr{
|
|
| 15 | 15 |
Setpgid: true, |
| 16 | 16 |
} |
| 17 |
- if !nonewns {
|
|
| 18 |
- attr.Cloneflags = syscall.CLONE_NEWNS |
|
| 19 |
- } |
|
| 20 |
- return &attr |
|
| 21 | 17 |
} |
| 22 | 18 |
|
| 23 | 19 |
func setCgroup(cgroupPath string, cmd *exec.Cmd) error {
|
| ... | ... |
@@ -7,8 +7,8 @@ import ( |
| 7 | 7 |
"path/filepath" |
| 8 | 8 |
|
| 9 | 9 |
shimapi "github.com/containerd/containerd/linux/shim/v1" |
| 10 |
+ "github.com/containerd/containerd/mount" |
|
| 10 | 11 |
ptypes "github.com/gogo/protobuf/types" |
| 11 |
- "golang.org/x/sys/unix" |
|
| 12 | 12 |
) |
| 13 | 13 |
|
| 14 | 14 |
// NewLocal returns a shim client implementation for issue commands to a shim |
| ... | ... |
@@ -32,7 +32,7 @@ func (c *local) Start(ctx context.Context, in *shimapi.StartRequest) (*shimapi.S |
| 32 | 32 |
|
| 33 | 33 |
func (c *local) Delete(ctx context.Context, in *ptypes.Empty) (*shimapi.DeleteResponse, error) {
|
| 34 | 34 |
// make sure we unmount the containers rootfs for this local |
| 35 |
- if err := unix.Unmount(filepath.Join(c.s.config.Path, "rootfs"), 0); err != nil {
|
|
| 35 |
+ if err := mount.Unmount(filepath.Join(c.s.config.Path, "rootfs"), 0); err != nil {
|
|
| 36 | 36 |
return nil, err |
| 37 | 37 |
} |
| 38 | 38 |
return c.s.Delete(ctx, in) |
| ... | ... |
@@ -37,12 +37,12 @@ func (s *containerStore) Get(ctx context.Context, id string) (containers.Contain |
| 37 | 37 |
|
| 38 | 38 |
bkt := getContainerBucket(s.tx, namespace, id) |
| 39 | 39 |
if bkt == nil {
|
| 40 |
- return containers.Container{}, errors.Wrapf(errdefs.ErrNotFound, "bucket name %q:%q", namespace, id)
|
|
| 40 |
+ return containers.Container{}, errors.Wrapf(errdefs.ErrNotFound, "container %q in namespace %q", id, namespace)
|
|
| 41 | 41 |
} |
| 42 | 42 |
|
| 43 | 43 |
container := containers.Container{ID: id}
|
| 44 | 44 |
if err := readContainer(&container, bkt); err != nil {
|
| 45 |
- return containers.Container{}, errors.Wrapf(err, "failed to read container %v", id)
|
|
| 45 |
+ return containers.Container{}, errors.Wrapf(err, "failed to read container %q", id)
|
|
| 46 | 46 |
} |
| 47 | 47 |
|
| 48 | 48 |
return container, nil |
| ... | ... |
@@ -61,7 +61,7 @@ func (s *containerStore) List(ctx context.Context, fs ...string) ([]containers.C |
| 61 | 61 |
|
| 62 | 62 |
bkt := getContainersBucket(s.tx, namespace) |
| 63 | 63 |
if bkt == nil {
|
| 64 |
- return nil, nil |
|
| 64 |
+ return nil, nil // empty store |
|
| 65 | 65 |
} |
| 66 | 66 |
|
| 67 | 67 |
var m []containers.Container |
| ... | ... |
@@ -73,7 +73,7 @@ func (s *containerStore) List(ctx context.Context, fs ...string) ([]containers.C |
| 73 | 73 |
container := containers.Container{ID: string(k)}
|
| 74 | 74 |
|
| 75 | 75 |
if err := readContainer(&container, cbkt); err != nil {
|
| 76 |
- return errors.Wrap(err, "failed to read container") |
|
| 76 |
+ return errors.Wrapf(err, "failed to read container %q", string(k)) |
|
| 77 | 77 |
} |
| 78 | 78 |
|
| 79 | 79 |
if filter.Match(adaptContainer(container)) {
|
| ... | ... |
@@ -113,7 +113,7 @@ func (s *containerStore) Create(ctx context.Context, container containers.Contai |
| 113 | 113 |
container.CreatedAt = time.Now().UTC() |
| 114 | 114 |
container.UpdatedAt = container.CreatedAt |
| 115 | 115 |
if err := writeContainer(cbkt, &container); err != nil {
|
| 116 |
- return containers.Container{}, errors.Wrap(err, "failed to write container")
|
|
| 116 |
+ return containers.Container{}, errors.Wrapf(err, "failed to write container %q", container.ID)
|
|
| 117 | 117 |
} |
| 118 | 118 |
|
| 119 | 119 |
return container, nil |
| ... | ... |
@@ -131,7 +131,7 @@ func (s *containerStore) Update(ctx context.Context, container containers.Contai |
| 131 | 131 |
|
| 132 | 132 |
bkt := getContainersBucket(s.tx, namespace) |
| 133 | 133 |
if bkt == nil {
|
| 134 |
- return containers.Container{}, errors.Wrapf(errdefs.ErrNotFound, "container %q", container.ID)
|
|
| 134 |
+ return containers.Container{}, errors.Wrapf(errdefs.ErrNotFound, "cannot update container %q in namespace %q", container.ID, namespace)
|
|
| 135 | 135 |
} |
| 136 | 136 |
|
| 137 | 137 |
cbkt := bkt.Bucket([]byte(container.ID)) |
| ... | ... |
@@ -141,7 +141,7 @@ func (s *containerStore) Update(ctx context.Context, container containers.Contai |
| 141 | 141 |
|
| 142 | 142 |
var updated containers.Container |
| 143 | 143 |
if err := readContainer(&updated, cbkt); err != nil {
|
| 144 |
- return updated, errors.Wrapf(err, "failed to read container from bucket") |
|
| 144 |
+ return updated, errors.Wrapf(err, "failed to read container %q", container.ID) |
|
| 145 | 145 |
} |
| 146 | 146 |
createdat := updated.CreatedAt |
| 147 | 147 |
updated.ID = container.ID |
| ... | ... |
@@ -211,7 +211,7 @@ func (s *containerStore) Update(ctx context.Context, container containers.Contai |
| 211 | 211 |
updated.CreatedAt = createdat |
| 212 | 212 |
updated.UpdatedAt = time.Now().UTC() |
| 213 | 213 |
if err := writeContainer(cbkt, &updated); err != nil {
|
| 214 |
- return containers.Container{}, errors.Wrap(err, "failed to write container")
|
|
| 214 |
+ return containers.Container{}, errors.Wrapf(err, "failed to write container %q", container.ID)
|
|
| 215 | 215 |
} |
| 216 | 216 |
|
| 217 | 217 |
return updated, nil |
| ... | ... |
@@ -225,7 +225,7 @@ func (s *containerStore) Delete(ctx context.Context, id string) error {
|
| 225 | 225 |
|
| 226 | 226 |
bkt := getContainersBucket(s.tx, namespace) |
| 227 | 227 |
if bkt == nil {
|
| 228 |
- return errors.Wrapf(errdefs.ErrNotFound, "cannot delete container %v, bucket not present", id) |
|
| 228 |
+ return errors.Wrapf(errdefs.ErrNotFound, "cannot delete container %q in namespace %q", id, namespace) |
|
| 229 | 229 |
} |
| 230 | 230 |
|
| 231 | 231 |
if err := bkt.DeleteBucket([]byte(id)); err == bolt.ErrBucketNotFound {
|
| ... | ... |
@@ -236,7 +236,7 @@ func (s *containerStore) Delete(ctx context.Context, id string) error {
|
| 236 | 236 |
|
| 237 | 237 |
func validateContainer(container *containers.Container) error {
|
| 238 | 238 |
if err := identifiers.Validate(container.ID); err != nil {
|
| 239 |
- return errors.Wrapf(err, "container.ID validation error") |
|
| 239 |
+ return errors.Wrap(err, "container.ID") |
|
| 240 | 240 |
} |
| 241 | 241 |
|
| 242 | 242 |
for k := range container.Extensions {
|
| ... | ... |
@@ -138,7 +138,7 @@ func (m *DB) Init(ctx context.Context) error {
|
| 138 | 138 |
if err := m.migrate(tx); err != nil {
|
| 139 | 139 |
return errors.Wrapf(err, "failed to migrate to %s.%d", m.schema, m.version) |
| 140 | 140 |
} |
| 141 |
- log.G(ctx).WithField("d", time.Now().Sub(t0)).Debugf("database migration to %s.%d finished", m.schema, m.version)
|
|
| 141 |
+ log.G(ctx).WithField("d", time.Now().Sub(t0)).Debugf("finished database migration to %s.%d", m.schema, m.version)
|
|
| 142 | 142 |
} |
| 143 | 143 |
} |
| 144 | 144 |
|
| ... | ... |
@@ -269,7 +269,7 @@ func (m *DB) GarbageCollect(ctx context.Context) (stats GCStats, err error) {
|
| 269 | 269 |
stats.SnapshotD = map[string]time.Duration{}
|
| 270 | 270 |
wg.Add(len(m.dirtySS)) |
| 271 | 271 |
for snapshotterName := range m.dirtySS {
|
| 272 |
- log.G(ctx).WithField("snapshotter", snapshotterName).Debug("scheduling snapshotter cleanup")
|
|
| 272 |
+ log.G(ctx).WithField("snapshotter", snapshotterName).Debug("schedule snapshotter cleanup")
|
|
| 273 | 273 |
go func(snapshotterName string) {
|
| 274 | 274 |
st1 := time.Now() |
| 275 | 275 |
m.cleanupSnapshotter(snapshotterName) |
| ... | ... |
@@ -286,7 +286,7 @@ func (m *DB) GarbageCollect(ctx context.Context) (stats GCStats, err error) {
|
| 286 | 286 |
|
| 287 | 287 |
if m.dirtyCS {
|
| 288 | 288 |
wg.Add(1) |
| 289 |
- log.G(ctx).Debug("scheduling content cleanup")
|
|
| 289 |
+ log.G(ctx).Debug("schedule content cleanup")
|
|
| 290 | 290 |
go func() {
|
| 291 | 291 |
ct1 := time.Now() |
| 292 | 292 |
m.cleanupContent() |
| ... | ... |
@@ -301,7 +301,7 @@ func remove(ctx context.Context, tx *bolt.Tx, node gc.Node) error {
|
| 301 | 301 |
cbkt = cbkt.Bucket(bucketKeyObjectBlob) |
| 302 | 302 |
} |
| 303 | 303 |
if cbkt != nil {
|
| 304 |
- log.G(ctx).WithField("key", node.Key).Debug("delete content")
|
|
| 304 |
+ log.G(ctx).WithField("key", node.Key).Debug("remove content")
|
|
| 305 | 305 |
return cbkt.DeleteBucket([]byte(node.Key)) |
| 306 | 306 |
} |
| 307 | 307 |
case ResourceSnapshot: |
| ... | ... |
@@ -313,7 +313,7 @@ func remove(ctx context.Context, tx *bolt.Tx, node gc.Node) error {
|
| 313 | 313 |
} |
| 314 | 314 |
ssbkt := sbkt.Bucket([]byte(parts[0])) |
| 315 | 315 |
if ssbkt != nil {
|
| 316 |
- log.G(ctx).WithField("key", parts[1]).WithField("snapshotter", parts[0]).Debug("delete snapshot")
|
|
| 316 |
+ log.G(ctx).WithField("key", parts[1]).WithField("snapshotter", parts[0]).Debug("remove snapshot")
|
|
| 317 | 317 |
return ssbkt.DeleteBucket([]byte(parts[1])) |
| 318 | 318 |
} |
| 319 | 319 |
} |
| ... | ... |
@@ -359,7 +359,8 @@ func (s *snapshotter) Commit(ctx context.Context, name, key string, opts ...snap |
| 359 | 359 |
return update(ctx, s.db, func(tx *bolt.Tx) error {
|
| 360 | 360 |
bkt := getSnapshotterBucket(tx, ns, s.name) |
| 361 | 361 |
if bkt == nil {
|
| 362 |
- return errors.Wrapf(errdefs.ErrNotFound, "snapshot %v does not exist", key) |
|
| 362 |
+ return errors.Wrapf(errdefs.ErrNotFound, |
|
| 363 |
+ "can not find snapshotter %q", s.name) |
|
| 363 | 364 |
} |
| 364 | 365 |
|
| 365 | 366 |
bbkt, err := bkt.CreateBucket([]byte(name)) |
| ... | ... |
@@ -722,7 +723,7 @@ func (s *snapshotter) pruneBranch(ctx context.Context, node *treeNode) error {
|
| 722 | 722 |
if !errdefs.IsFailedPrecondition(err) {
|
| 723 | 723 |
return err |
| 724 | 724 |
} |
| 725 |
- logger.WithError(err).WithField("key", node.info.Name).Warnf("snapshot removal failed")
|
|
| 725 |
+ logger.WithError(err).WithField("key", node.info.Name).Warnf("failed to remove snapshot")
|
|
| 726 | 726 |
} else {
|
| 727 | 727 |
logger.WithField("key", node.info.Name).Debug("removed snapshot")
|
| 728 | 728 |
} |
| ... | ... |
@@ -2,7 +2,9 @@ package mount |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"strings" |
| 5 |
+ "time" |
|
| 5 | 6 |
|
| 7 |
+ "github.com/pkg/errors" |
|
| 6 | 8 |
"golang.org/x/sys/unix" |
| 7 | 9 |
) |
| 8 | 10 |
|
| ... | ... |
@@ -42,8 +44,27 @@ func (m *Mount) Mount(target string) error {
|
| 42 | 42 |
} |
| 43 | 43 |
|
| 44 | 44 |
// Unmount the provided mount path with the flags |
| 45 |
-func Unmount(mount string, flags int) error {
|
|
| 46 |
- return unix.Unmount(mount, flags) |
|
| 45 |
+func Unmount(target string, flags int) error {
|
|
| 46 |
+ if err := unmount(target, flags); err != nil && err != unix.EINVAL {
|
|
| 47 |
+ return err |
|
| 48 |
+ } |
|
| 49 |
+ return nil |
|
| 50 |
+} |
|
| 51 |
+ |
|
| 52 |
+func unmount(target string, flags int) error {
|
|
| 53 |
+ for i := 0; i < 50; i++ {
|
|
| 54 |
+ if err := unix.Unmount(target, flags); err != nil {
|
|
| 55 |
+ switch err {
|
|
| 56 |
+ case unix.EBUSY: |
|
| 57 |
+ time.Sleep(50 * time.Millisecond) |
|
| 58 |
+ continue |
|
| 59 |
+ default: |
|
| 60 |
+ return err |
|
| 61 |
+ } |
|
| 62 |
+ } |
|
| 63 |
+ return nil |
|
| 64 |
+ } |
|
| 65 |
+ return errors.Wrapf(unix.EBUSY, "failed to unmount target %s", target) |
|
| 47 | 66 |
} |
| 48 | 67 |
|
| 49 | 68 |
// UnmountAll repeatedly unmounts the given mount point until there |
| ... | ... |
@@ -51,7 +72,7 @@ func Unmount(mount string, flags int) error {
|
| 51 | 51 |
// useful for undoing a stack of mounts on the same mount point. |
| 52 | 52 |
func UnmountAll(mount string, flags int) error {
|
| 53 | 53 |
for {
|
| 54 |
- if err := Unmount(mount, flags); err != nil {
|
|
| 54 |
+ if err := unmount(mount, flags); err != nil {
|
|
| 55 | 55 |
// EINVAL is returned if the target is not a |
| 56 | 56 |
// mount point, indicating that we are |
| 57 | 57 |
// done. It can also indicate a few other |
| ... | ... |
@@ -12,12 +12,11 @@ import ( |
| 12 | 12 |
"strconv" |
| 13 | 13 |
"strings" |
| 14 | 14 |
|
| 15 |
- "golang.org/x/sys/unix" |
|
| 16 |
- |
|
| 17 | 15 |
"github.com/containerd/containerd/containers" |
| 18 | 16 |
"github.com/containerd/containerd/content" |
| 19 | 17 |
"github.com/containerd/containerd/fs" |
| 20 | 18 |
"github.com/containerd/containerd/images" |
| 19 |
+ "github.com/containerd/containerd/mount" |
|
| 21 | 20 |
"github.com/containerd/containerd/namespaces" |
| 22 | 21 |
"github.com/opencontainers/image-spec/specs-go/v1" |
| 23 | 22 |
"github.com/opencontainers/runc/libcontainer/user" |
| ... | ... |
@@ -101,7 +100,7 @@ func WithImageConfig(image Image) SpecOpts {
|
| 101 | 101 |
parts := strings.Split(config.User, ":") |
| 102 | 102 |
switch len(parts) {
|
| 103 | 103 |
case 1: |
| 104 |
- v, err := strconv.ParseUint(parts[0], 0, 10) |
|
| 104 |
+ v, err := strconv.Atoi(parts[0]) |
|
| 105 | 105 |
if err != nil {
|
| 106 | 106 |
// if we cannot parse as a uint they try to see if it is a username |
| 107 | 107 |
if err := WithUsername(config.User)(ctx, client, c, s); err != nil {
|
| ... | ... |
@@ -113,13 +112,13 @@ func WithImageConfig(image Image) SpecOpts {
|
| 113 | 113 |
return err |
| 114 | 114 |
} |
| 115 | 115 |
case 2: |
| 116 |
- v, err := strconv.ParseUint(parts[0], 0, 10) |
|
| 116 |
+ v, err := strconv.Atoi(parts[0]) |
|
| 117 | 117 |
if err != nil {
|
| 118 |
- return err |
|
| 118 |
+ return errors.Wrapf(err, "parse uid %s", parts[0]) |
|
| 119 | 119 |
} |
| 120 | 120 |
uid := uint32(v) |
| 121 |
- if v, err = strconv.ParseUint(parts[1], 0, 10); err != nil {
|
|
| 122 |
- return err |
|
| 121 |
+ if v, err = strconv.Atoi(parts[1]); err != nil {
|
|
| 122 |
+ return errors.Wrapf(err, "parse gid %s", parts[1]) |
|
| 123 | 123 |
} |
| 124 | 124 |
gid := uint32(v) |
| 125 | 125 |
s.Process.User.UID, s.Process.User.GID = uid, gid |
| ... | ... |
@@ -260,7 +259,7 @@ func WithUIDGID(uid, gid uint32) SpecOpts {
|
| 260 | 260 |
// or uid is not found in /etc/passwd, it sets gid to be the same with |
| 261 | 261 |
// uid, and not returns error. |
| 262 | 262 |
func WithUserID(uid uint32) SpecOpts {
|
| 263 |
- return func(ctx context.Context, client Client, c *containers.Container, s *specs.Spec) error {
|
|
| 263 |
+ return func(ctx context.Context, client Client, c *containers.Container, s *specs.Spec) (err error) {
|
|
| 264 | 264 |
if c.Snapshotter == "" {
|
| 265 | 265 |
return errors.Errorf("no snapshotter set for container")
|
| 266 | 266 |
} |
| ... | ... |
@@ -276,13 +275,19 @@ func WithUserID(uid uint32) SpecOpts {
|
| 276 | 276 |
if err != nil {
|
| 277 | 277 |
return err |
| 278 | 278 |
} |
| 279 |
- defer os.RemoveAll(root) |
|
| 279 |
+ defer os.Remove(root) |
|
| 280 | 280 |
for _, m := range mounts {
|
| 281 | 281 |
if err := m.Mount(root); err != nil {
|
| 282 | 282 |
return err |
| 283 | 283 |
} |
| 284 | 284 |
} |
| 285 |
- defer unix.Unmount(root, 0) |
|
| 285 |
+ defer func() {
|
|
| 286 |
+ if uerr := mount.Unmount(root, 0); uerr != nil {
|
|
| 287 |
+ if err == nil {
|
|
| 288 |
+ err = uerr |
|
| 289 |
+ } |
|
| 290 |
+ } |
|
| 291 |
+ }() |
|
| 286 | 292 |
ppath, err := fs.RootPath(root, "/etc/passwd") |
| 287 | 293 |
if err != nil {
|
| 288 | 294 |
return err |
| ... | ... |
@@ -317,7 +322,7 @@ func WithUserID(uid uint32) SpecOpts {
|
| 317 | 317 |
// does not exist, or the username is not found in /etc/passwd, |
| 318 | 318 |
// it returns error. |
| 319 | 319 |
func WithUsername(username string) SpecOpts {
|
| 320 |
- return func(ctx context.Context, client Client, c *containers.Container, s *specs.Spec) error {
|
|
| 320 |
+ return func(ctx context.Context, client Client, c *containers.Container, s *specs.Spec) (err error) {
|
|
| 321 | 321 |
if c.Snapshotter == "" {
|
| 322 | 322 |
return errors.Errorf("no snapshotter set for container")
|
| 323 | 323 |
} |
| ... | ... |
@@ -333,13 +338,19 @@ func WithUsername(username string) SpecOpts {
|
| 333 | 333 |
if err != nil {
|
| 334 | 334 |
return err |
| 335 | 335 |
} |
| 336 |
- defer os.RemoveAll(root) |
|
| 336 |
+ defer os.Remove(root) |
|
| 337 | 337 |
for _, m := range mounts {
|
| 338 | 338 |
if err := m.Mount(root); err != nil {
|
| 339 | 339 |
return err |
| 340 | 340 |
} |
| 341 | 341 |
} |
| 342 |
- defer unix.Unmount(root, 0) |
|
| 342 |
+ defer func() {
|
|
| 343 |
+ if uerr := mount.Unmount(root, 0); uerr != nil {
|
|
| 344 |
+ if err == nil {
|
|
| 345 |
+ err = uerr |
|
| 346 |
+ } |
|
| 347 |
+ } |
|
| 348 |
+ }() |
|
| 343 | 349 |
ppath, err := fs.RootPath(root, "/etc/passwd") |
| 344 | 350 |
if err != nil {
|
| 345 | 351 |
return err |
| ... | ... |
@@ -60,3 +60,11 @@ func WithTTY(width, height int) SpecOpts {
|
| 60 | 60 |
return nil |
| 61 | 61 |
} |
| 62 | 62 |
} |
| 63 |
+ |
|
| 64 |
+// WithUsername sets the username on the process |
|
| 65 |
+func WithUsername(username string) SpecOpts {
|
|
| 66 |
+ return func(ctx context.Context, client Client, c *containers.Container, s *specs.Spec) error {
|
|
| 67 |
+ s.Process.User.Username = username |
|
| 68 |
+ return nil |
|
| 69 |
+ } |
|
| 70 |
+} |
| ... | ... |
@@ -55,10 +55,10 @@ func ApplyLayer(ctx context.Context, layer Layer, chain []digest.Digest, sn snap |
| 55 | 55 |
|
| 56 | 56 |
_, err := sn.Stat(ctx, chainID.String()) |
| 57 | 57 |
if err == nil {
|
| 58 |
- log.G(ctx).Debugf("Extraction not needed, layer snapshot exists")
|
|
| 58 |
+ log.G(ctx).Debugf("Extraction not needed, layer snapshot %s exists", chainID)
|
|
| 59 | 59 |
return false, nil |
| 60 | 60 |
} else if !errdefs.IsNotFound(err) {
|
| 61 |
- return false, errors.Wrap(err, "failed to stat snapshot") |
|
| 61 |
+ return false, errors.Wrapf(err, "failed to stat snapshot %s", chainID) |
|
| 62 | 62 |
} |
| 63 | 63 |
|
| 64 | 64 |
key := fmt.Sprintf("extract-%s %s", uniquePart(), chainID)
|
| ... | ... |
@@ -67,7 +67,7 @@ func ApplyLayer(ctx context.Context, layer Layer, chain []digest.Digest, sn snap |
| 67 | 67 |
mounts, err := sn.Prepare(ctx, key, parent.String(), opts...) |
| 68 | 68 |
if err != nil {
|
| 69 | 69 |
//TODO: If is snapshot exists error, retry |
| 70 |
- return false, errors.Wrap(err, "failed to prepare extraction layer") |
|
| 70 |
+ return false, errors.Wrapf(err, "failed to prepare extraction snapshot %q", key) |
|
| 71 | 71 |
} |
| 72 | 72 |
defer func() {
|
| 73 | 73 |
if err != nil {
|
| ... | ... |
@@ -89,7 +89,7 @@ func ApplyLayer(ctx context.Context, layer Layer, chain []digest.Digest, sn snap |
| 89 | 89 |
|
| 90 | 90 |
if err = sn.Commit(ctx, chainID.String(), key, opts...); err != nil {
|
| 91 | 91 |
if !errdefs.IsAlreadyExists(err) {
|
| 92 |
- return false, errors.Wrapf(err, "failed to commit snapshot %s", parent) |
|
| 92 |
+ return false, errors.Wrapf(err, "failed to commit snapshot %s", key) |
|
| 93 | 93 |
} |
| 94 | 94 |
|
| 95 | 95 |
// Destination already exists, cleanup key and return without error |
| ... | ... |
@@ -49,6 +49,8 @@ func (l *TaskList) Get(ctx context.Context, id string) (Task, error) {
|
| 49 | 49 |
|
| 50 | 50 |
// GetAll tasks under a namespace |
| 51 | 51 |
func (l *TaskList) GetAll(ctx context.Context) ([]Task, error) {
|
| 52 |
+ l.mu.Lock() |
|
| 53 |
+ defer l.mu.Unlock() |
|
| 52 | 54 |
namespace, err := namespaces.NamespaceRequired(ctx) |
| 53 | 55 |
if err != nil {
|
| 54 | 56 |
return nil, err |
| ... | ... |
@@ -277,7 +277,7 @@ func (t *task) Delete(ctx context.Context, opts ...ProcessDeleteOpts) (*ExitStat |
| 277 | 277 |
return &ExitStatus{code: r.ExitStatus, exitedAt: r.ExitedAt}, nil
|
| 278 | 278 |
} |
| 279 | 279 |
|
| 280 |
-func (t *task) Exec(ctx context.Context, id string, spec *specs.Process, ioCreate cio.Creation) (Process, error) {
|
|
| 280 |
+func (t *task) Exec(ctx context.Context, id string, spec *specs.Process, ioCreate cio.Creation) (_ Process, err error) {
|
|
| 281 | 281 |
if id == "" {
|
| 282 | 282 |
return nil, errors.Wrapf(errdefs.ErrInvalidArgument, "exec id must not be empty") |
| 283 | 283 |
} |
| ... | ... |
@@ -285,6 +285,12 @@ func (t *task) Exec(ctx context.Context, id string, spec *specs.Process, ioCreat |
| 285 | 285 |
if err != nil {
|
| 286 | 286 |
return nil, err |
| 287 | 287 |
} |
| 288 |
+ defer func() {
|
|
| 289 |
+ if err != nil && i != nil {
|
|
| 290 |
+ i.Cancel() |
|
| 291 |
+ i.Close() |
|
| 292 |
+ } |
|
| 293 |
+ }() |
|
| 288 | 294 |
any, err := typeurl.MarshalAny(spec) |
| 289 | 295 |
if err != nil {
|
| 290 | 296 |
return nil, err |
| ... | ... |
@@ -41,4 +41,4 @@ github.com/boltdb/bolt e9cf4fae01b5a8ff89d0ec6b32f0d9c9f79aefdd |
| 41 | 41 |
google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944 |
| 42 | 42 |
golang.org/x/text 19e51611da83d6be54ddafce4a4af510cb3e9ea4 |
| 43 | 43 |
github.com/dmcgowan/go-tar go1.10 |
| 44 |
-github.com/stevvooe/ttrpc 8c92e22ce0c492875ccaac3ab06143a77d8ed0c1 |
|
| 44 |
+github.com/stevvooe/ttrpc 76e68349ad9ab4d03d764c713826d31216715e4f |
| 45 | 45 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,23 @@ |
| 0 |
+package ttrpc |
|
| 1 |
+ |
|
| 2 |
+import "github.com/pkg/errors" |
|
| 3 |
+ |
|
| 4 |
+type serverConfig struct {
|
|
| 5 |
+ handshaker Handshaker |
|
| 6 |
+} |
|
| 7 |
+ |
|
| 8 |
+type ServerOpt func(*serverConfig) error |
|
| 9 |
+ |
|
| 10 |
+// WithServerHandshaker can be passed to NewServer to ensure that the |
|
| 11 |
+// handshaker is called before every connection attempt. |
|
| 12 |
+// |
|
| 13 |
+// Only one handshaker is allowed per server. |
|
| 14 |
+func WithServerHandshaker(handshaker Handshaker) ServerOpt {
|
|
| 15 |
+ return func(c *serverConfig) error {
|
|
| 16 |
+ if c.handshaker != nil {
|
|
| 17 |
+ return errors.New("only one handshaker allowed per server")
|
|
| 18 |
+ } |
|
| 19 |
+ c.handshaker = handshaker |
|
| 20 |
+ return nil |
|
| 21 |
+ } |
|
| 22 |
+} |
| 0 | 23 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,34 @@ |
| 0 |
+package ttrpc |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "context" |
|
| 4 |
+ "net" |
|
| 5 |
+) |
|
| 6 |
+ |
|
| 7 |
+// Handshaker defines the interface for connection handshakes performed on the |
|
| 8 |
+// server or client when first connecting. |
|
| 9 |
+type Handshaker interface {
|
|
| 10 |
+ // Handshake should confirm or decorate a connection that may be incoming |
|
| 11 |
+ // to a server or outgoing from a client. |
|
| 12 |
+ // |
|
| 13 |
+ // If this returns without an error, the caller should use the connection |
|
| 14 |
+ // in place of the original connection. |
|
| 15 |
+ // |
|
| 16 |
+ // The second return value can contain credential specific data, such as |
|
| 17 |
+ // unix socket credentials or TLS information. |
|
| 18 |
+ // |
|
| 19 |
+ // While we currently only have implementations on the server-side, this |
|
| 20 |
+ // interface should be sufficient to implement similar handshakes on the |
|
| 21 |
+ // client-side. |
|
| 22 |
+ Handshake(ctx context.Context, conn net.Conn) (net.Conn, interface{}, error)
|
|
| 23 |
+} |
|
| 24 |
+ |
|
| 25 |
+type handshakerFunc func(ctx context.Context, conn net.Conn) (net.Conn, interface{}, error)
|
|
| 26 |
+ |
|
| 27 |
+func (fn handshakerFunc) Handshake(ctx context.Context, conn net.Conn) (net.Conn, interface{}, error) {
|
|
| 28 |
+ return fn(ctx, conn) |
|
| 29 |
+} |
|
| 30 |
+ |
|
| 31 |
+func noopHandshake(ctx context.Context, conn net.Conn) (net.Conn, interface{}, error) {
|
|
| 32 |
+ return conn, nil, nil |
|
| 33 |
+} |
| ... | ... |
@@ -2,6 +2,7 @@ package ttrpc |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"context" |
| 5 |
+ "io" |
|
| 5 | 6 |
"math/rand" |
| 6 | 7 |
"net" |
| 7 | 8 |
"sync" |
| ... | ... |
@@ -19,6 +20,7 @@ var ( |
| 19 | 19 |
) |
| 20 | 20 |
|
| 21 | 21 |
type Server struct {
|
| 22 |
+ config *serverConfig |
|
| 22 | 23 |
services *serviceSet |
| 23 | 24 |
codec codec |
| 24 | 25 |
|
| ... | ... |
@@ -28,13 +30,21 @@ type Server struct {
|
| 28 | 28 |
done chan struct{} // marks point at which we stop serving requests
|
| 29 | 29 |
} |
| 30 | 30 |
|
| 31 |
-func NewServer() *Server {
|
|
| 31 |
+func NewServer(opts ...ServerOpt) (*Server, error) {
|
|
| 32 |
+ config := &serverConfig{}
|
|
| 33 |
+ for _, opt := range opts {
|
|
| 34 |
+ if err := opt(config); err != nil {
|
|
| 35 |
+ return nil, err |
|
| 36 |
+ } |
|
| 37 |
+ } |
|
| 38 |
+ |
|
| 32 | 39 |
return &Server{
|
| 40 |
+ config: config, |
|
| 33 | 41 |
services: newServiceSet(), |
| 34 | 42 |
done: make(chan struct{}),
|
| 35 | 43 |
listeners: make(map[net.Listener]struct{}),
|
| 36 | 44 |
connections: make(map[*serverConn]struct{}),
|
| 37 |
- } |
|
| 45 |
+ }, nil |
|
| 38 | 46 |
} |
| 39 | 47 |
|
| 40 | 48 |
func (s *Server) Register(name string, methods map[string]Method) {
|
| ... | ... |
@@ -46,10 +56,15 @@ func (s *Server) Serve(l net.Listener) error {
|
| 46 | 46 |
defer s.closeListener(l) |
| 47 | 47 |
|
| 48 | 48 |
var ( |
| 49 |
- ctx = context.Background() |
|
| 50 |
- backoff time.Duration |
|
| 49 |
+ ctx = context.Background() |
|
| 50 |
+ backoff time.Duration |
|
| 51 |
+ handshaker = s.config.handshaker |
|
| 51 | 52 |
) |
| 52 | 53 |
|
| 54 |
+ if handshaker == nil {
|
|
| 55 |
+ handshaker = handshakerFunc(noopHandshake) |
|
| 56 |
+ } |
|
| 57 |
+ |
|
| 53 | 58 |
for {
|
| 54 | 59 |
conn, err := l.Accept() |
| 55 | 60 |
if err != nil {
|
| ... | ... |
@@ -82,7 +97,15 @@ func (s *Server) Serve(l net.Listener) error {
|
| 82 | 82 |
} |
| 83 | 83 |
|
| 84 | 84 |
backoff = 0 |
| 85 |
- sc := s.newConn(conn) |
|
| 85 |
+ |
|
| 86 |
+ approved, handshake, err := handshaker.Handshake(ctx, conn) |
|
| 87 |
+ if err != nil {
|
|
| 88 |
+ log.L.WithError(err).Errorf("ttrpc: refusing connection after handshake")
|
|
| 89 |
+ conn.Close() |
|
| 90 |
+ continue |
|
| 91 |
+ } |
|
| 92 |
+ |
|
| 93 |
+ sc := s.newConn(approved, handshake) |
|
| 86 | 94 |
go sc.run(ctx) |
| 87 | 95 |
} |
| 88 | 96 |
} |
| ... | ... |
@@ -205,11 +228,12 @@ func (cs connState) String() string {
|
| 205 | 205 |
} |
| 206 | 206 |
} |
| 207 | 207 |
|
| 208 |
-func (s *Server) newConn(conn net.Conn) *serverConn {
|
|
| 208 |
+func (s *Server) newConn(conn net.Conn, handshake interface{}) *serverConn {
|
|
| 209 | 209 |
c := &serverConn{
|
| 210 |
- server: s, |
|
| 211 |
- conn: conn, |
|
| 212 |
- shutdown: make(chan struct{}),
|
|
| 210 |
+ server: s, |
|
| 211 |
+ conn: conn, |
|
| 212 |
+ handshake: handshake, |
|
| 213 |
+ shutdown: make(chan struct{}),
|
|
| 213 | 214 |
} |
| 214 | 215 |
c.setState(connStateIdle) |
| 215 | 216 |
s.addConnection(c) |
| ... | ... |
@@ -217,9 +241,10 @@ func (s *Server) newConn(conn net.Conn) *serverConn {
|
| 217 | 217 |
} |
| 218 | 218 |
|
| 219 | 219 |
type serverConn struct {
|
| 220 |
- server *Server |
|
| 221 |
- conn net.Conn |
|
| 222 |
- state atomic.Value |
|
| 220 |
+ server *Server |
|
| 221 |
+ conn net.Conn |
|
| 222 |
+ handshake interface{} // data from handshake, not used for now
|
|
| 223 |
+ state atomic.Value |
|
| 223 | 224 |
|
| 224 | 225 |
shutdownOnce sync.Once |
| 225 | 226 |
shutdown chan struct{} // forced shutdown, used by close
|
| ... | ... |
@@ -406,7 +431,7 @@ func (c *serverConn) run(sctx context.Context) {
|
| 406 | 406 |
// branch. Basically, it means that we are no longer receiving |
| 407 | 407 |
// requests due to a terminal error. |
| 408 | 408 |
recvErr = nil // connection is now "closing" |
| 409 |
- if err != nil {
|
|
| 409 |
+ if err != nil && err != io.EOF {
|
|
| 410 | 410 |
log.L.WithError(err).Error("error receiving message")
|
| 411 | 411 |
} |
| 412 | 412 |
case <-shutdown: |
| 413 | 413 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,92 @@ |
| 0 |
+package ttrpc |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "context" |
|
| 4 |
+ "net" |
|
| 5 |
+ "os" |
|
| 6 |
+ "syscall" |
|
| 7 |
+ |
|
| 8 |
+ "github.com/pkg/errors" |
|
| 9 |
+ "golang.org/x/sys/unix" |
|
| 10 |
+) |
|
| 11 |
+ |
|
| 12 |
+type UnixCredentialsFunc func(*unix.Ucred) error |
|
| 13 |
+ |
|
| 14 |
+func (fn UnixCredentialsFunc) Handshake(ctx context.Context, conn net.Conn) (net.Conn, interface{}, error) {
|
|
| 15 |
+ uc, err := requireUnixSocket(conn) |
|
| 16 |
+ if err != nil {
|
|
| 17 |
+ return nil, nil, errors.Wrap(err, "ttrpc.UnixCredentialsFunc: require unix socket") |
|
| 18 |
+ } |
|
| 19 |
+ |
|
| 20 |
+ rs, err := uc.SyscallConn() |
|
| 21 |
+ if err != nil {
|
|
| 22 |
+ return nil, nil, errors.Wrap(err, "ttrpc.UnixCredentialsFunc: (net.UnixConn).SyscallConn failed") |
|
| 23 |
+ } |
|
| 24 |
+ var ( |
|
| 25 |
+ ucred *unix.Ucred |
|
| 26 |
+ ucredErr error |
|
| 27 |
+ ) |
|
| 28 |
+ if err := rs.Control(func(fd uintptr) {
|
|
| 29 |
+ ucred, ucredErr = unix.GetsockoptUcred(int(fd), unix.SOL_SOCKET, unix.SO_PEERCRED) |
|
| 30 |
+ }); err != nil {
|
|
| 31 |
+ return nil, nil, errors.Wrapf(err, "ttrpc.UnixCredentialsFunc: (*syscall.RawConn).Control failed") |
|
| 32 |
+ } |
|
| 33 |
+ |
|
| 34 |
+ if ucredErr != nil {
|
|
| 35 |
+ return nil, nil, errors.Wrapf(err, "ttrpc.UnixCredentialsFunc: failed to retrieve socket peer credentials") |
|
| 36 |
+ } |
|
| 37 |
+ |
|
| 38 |
+ if err := fn(ucred); err != nil {
|
|
| 39 |
+ return nil, nil, errors.Wrapf(err, "ttrpc.UnixCredentialsFunc: credential check failed") |
|
| 40 |
+ } |
|
| 41 |
+ |
|
| 42 |
+ return uc, ucred, nil |
|
| 43 |
+} |
|
| 44 |
+ |
|
| 45 |
+// UnixSocketRequireUidGid requires specific *effective* UID/GID, rather than the real UID/GID. |
|
| 46 |
+// |
|
| 47 |
+// For example, if a daemon binary is owned by the root (UID 0) with SUID bit but running as an |
|
| 48 |
+// unprivileged user (UID 1001), the effective UID becomes 0, and the real UID becomes 1001. |
|
| 49 |
+// So calling this function with uid=0 allows a connection from effective UID 0 but rejects |
|
| 50 |
+// a connection from effective UID 1001. |
|
| 51 |
+// |
|
| 52 |
+// See socket(7), SO_PEERCRED: "The returned credentials are those that were in effect at the time of the call to connect(2) or socketpair(2)." |
|
| 53 |
+func UnixSocketRequireUidGid(uid, gid int) UnixCredentialsFunc {
|
|
| 54 |
+ return func(ucred *unix.Ucred) error {
|
|
| 55 |
+ return requireUidGid(ucred, uid, gid) |
|
| 56 |
+ } |
|
| 57 |
+} |
|
| 58 |
+ |
|
| 59 |
+func UnixSocketRequireRoot() UnixCredentialsFunc {
|
|
| 60 |
+ return UnixSocketRequireUidGid(0, 0) |
|
| 61 |
+} |
|
| 62 |
+ |
|
| 63 |
+// UnixSocketRequireSameUser resolves the current effective unix user and returns a |
|
| 64 |
+// UnixCredentialsFunc that will validate incoming unix connections against the |
|
| 65 |
+// current credentials. |
|
| 66 |
+// |
|
| 67 |
+// This is useful when using abstract sockets that are accessible by all users. |
|
| 68 |
+func UnixSocketRequireSameUser() UnixCredentialsFunc {
|
|
| 69 |
+ euid, egid := os.Geteuid(), os.Getegid() |
|
| 70 |
+ return UnixSocketRequireUidGid(euid, egid) |
|
| 71 |
+} |
|
| 72 |
+ |
|
| 73 |
+func requireRoot(ucred *unix.Ucred) error {
|
|
| 74 |
+ return requireUidGid(ucred, 0, 0) |
|
| 75 |
+} |
|
| 76 |
+ |
|
| 77 |
+func requireUidGid(ucred *unix.Ucred, uid, gid int) error {
|
|
| 78 |
+ if (uid != -1 && uint32(uid) != ucred.Uid) || (gid != -1 && uint32(gid) != ucred.Gid) {
|
|
| 79 |
+ return errors.Wrap(syscall.EPERM, "ttrpc: invalid credentials") |
|
| 80 |
+ } |
|
| 81 |
+ return nil |
|
| 82 |
+} |
|
| 83 |
+ |
|
| 84 |
+func requireUnixSocket(conn net.Conn) (*net.UnixConn, error) {
|
|
| 85 |
+ uc, ok := conn.(*net.UnixConn) |
|
| 86 |
+ if !ok {
|
|
| 87 |
+ return nil, errors.New("a unix socket connection is required")
|
|
| 88 |
+ } |
|
| 89 |
+ |
|
| 90 |
+ return uc, nil |
|
| 91 |
+} |