Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
Michael Crosby authored on 2017/12/05 04:14:42... | ... |
@@ -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 |
+} |