Merge pull request #1343 from ibuildthecloud/rootless

Create pidns for rootless
This commit is contained in:
Erik Wilson 2020-02-24 15:05:52 -07:00 committed by GitHub
commit 4210800648
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 961 additions and 1032 deletions

4
go.mod
View File

@ -82,7 +82,6 @@ require (
github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4 // indirect
github.com/go-bindata/go-bindata v3.1.2+incompatible
github.com/go-sql-driver/mysql v1.4.1
github.com/gofrs/flock v0.7.1 // indirect
github.com/gogo/googleapis v1.3.0 // indirect
github.com/google/tcpproxy v0.0.0-20180808230851-dfa16c61dad2
github.com/gorilla/mux v1.7.3
@ -103,11 +102,10 @@ require (
github.com/rancher/remotedialer v0.2.0
github.com/rancher/wrangler v0.4.0
github.com/rancher/wrangler-api v0.4.0
github.com/rootless-containers/rootlesskit v0.6.0
github.com/rootless-containers/rootlesskit v0.7.2
github.com/sirupsen/logrus v1.4.2
github.com/spf13/pflag v1.0.5
github.com/tchap/go-patricia v2.3.0+incompatible // indirect
github.com/theckman/go-flock v0.7.1 // indirect
github.com/urfave/cli v1.22.2
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586
golang.org/x/net v0.0.0-20191204025024-5ee1b9f4859a

10
go.sum
View File

@ -489,7 +489,9 @@ github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI=
github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/insomniacslk/dhcp v0.0.0-20190712084813-dc1a53400564/go.mod h1:CfMdguCK66I5DAUJgGKyNz8aB6vO5dZzkm9Xep6WGvw=
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA=
github.com/jamescun/tuntap v0.0.0-20190712092105-cb1fb277045c/go.mod h1:zzwpsgcYhzzIP5WyF8g9ivCv38cY9uAV9Gu0m3lThhE=
github.com/jefferai/jsonx v1.0.0/go.mod h1:OGmqmi2tTeI/PS+qQfBDToLHHJIy/RMp24fPo8vFvoQ=
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
github.com/jetstack/cert-manager v0.7.2/go.mod h1:nbddmhjWxYGt04bxvwVGUSeLhZ2PCyNvd7MpXdq+yWY=
@ -614,6 +616,7 @@ github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:F
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/moby/vpnkit v0.3.1-0.20190720080441-7dd3dcce7d3d/go.mod h1:KyjUrL9cb6ZSNNAUwZfqRjhwwgJ3BJN+kXh0t43WTUQ=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -778,8 +781,8 @@ github.com/robfig/cron v1.1.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfm
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rootless-containers/rootlesskit v0.6.0 h1:L7DxVAlaNhg4M/+i2GCl24kRkXO5q81C/lu4jlMz3bE=
github.com/rootless-containers/rootlesskit v0.6.0/go.mod h1:HO7iU3+dH2N6yQL4DcUGQpzVZ7e7VYWNdtTAOR/P3FM=
github.com/rootless-containers/rootlesskit v0.7.2 h1:gcWQ9/GN98ne1AqnoeOgQ8e6qpKd3BuB4ug+2h95Fr0=
github.com/rootless-containers/rootlesskit v0.7.2/go.mod h1:r9YL5mKRIdnwcYk4G8E5CSc9MDeFtgYmhfE4CSvDGYA=
github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c h1:ht7N4d/B7Ezf58nvMNVF3OlvDlz9pp+WHVcRNS0nink=
github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto=
github.com/russross/blackfriday v0.0.0-20170610170232-067529f716f4/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
@ -859,6 +862,7 @@ github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhV
github.com/timakin/bodyclose v0.0.0-20190721030226-87058b9bfcec/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8 h1:ndzgwNDnKIqyCvHTXaCqh9KlOWKvBry6nuXMJmonVsE=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/u-root/u-root v5.0.0+incompatible/go.mod h1:RYkpo8pTHrNjW08opNd/U6p/RJE7K0D8fXO0d47+3YY=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ultraware/funlen v0.0.1/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA=
github.com/ultraware/funlen v0.0.2/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA=
@ -966,6 +970,7 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -1012,6 +1017,7 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View File

@ -17,6 +17,8 @@ func setupMounts(stateDir string) error {
{"/var/run", ""},
{"/var/log", filepath.Join(stateDir, "logs")},
{"/var/lib/cni", filepath.Join(stateDir, "cni")},
{"/var/lib/kubelet", filepath.Join(stateDir, "kubelet")},
{"/etc/rancher", filepath.Join(stateDir, "etc", "rancher")},
}
for _, v := range mountMap {

View File

@ -89,7 +89,8 @@ func createParentOpt(stateDir string) (*parent.Opt, error) {
}
opt := &parent.Opt{
StateDir: stateDir,
StateDir: stateDir,
CreatePIDNS: true,
}
mtu := 0
@ -102,7 +103,7 @@ func createParentOpt(stateDir string) (*parent.Opt, error) {
if _, err := exec.LookPath(binary); err != nil {
return nil, err
}
opt.NetworkDriver = slirp4netns.NewParentDriver(binary, mtu, ipnet, disableHostLoopback, "")
opt.NetworkDriver = slirp4netns.NewParentDriver(binary, mtu, ipnet, disableHostLoopback, "", false, false)
opt.PortDriver, err = portbuiltin.NewParentDriver(&logrusDebugWriter{}, stateDir)
if err != nil {
return nil, err
@ -130,5 +131,7 @@ func createChildOpt() (*child.Opt, error) {
opt.PortDriver = portbuiltin.NewChildDriver(&logrusDebugWriter{})
opt.CopyUpDirs = []string{"/etc", "/run", "/var/lib"}
opt.CopyUpDriver = tmpfssymlink.NewChildDriver()
opt.MountProcfs = true
opt.Reaper = true
return opt, nil
}

View File

@ -17,7 +17,7 @@ var (
all = "_all_"
)
func Register(ctx context.Context, serviceController coreClients.ServiceController, httpsPort int) error {
func Register(ctx context.Context, serviceController coreClients.ServiceController, enabled bool, httpsPort int) error {
var (
err error
rootlessClient client.Client
@ -41,6 +41,7 @@ func Register(ctx context.Context, serviceController coreClients.ServiceControll
}
h := &handler{
enabled: enabled,
rootlessClient: rootlessClient,
serviceClient: serviceController,
serviceCache: serviceController.Cache(),
@ -54,6 +55,7 @@ func Register(ctx context.Context, serviceController coreClients.ServiceControll
}
type handler struct {
enabled bool
rootlessClient client.Client
serviceClient coreClients.ServiceController
serviceCache coreClients.ServiceCache
@ -122,6 +124,11 @@ func (h *handler) toBindPorts() (map[int]int, error) {
toBindPorts := map[int]int{
h.httpsPort: h.httpsPort,
}
if !h.enabled {
return toBindPorts, nil
}
for _, svc := range svcs {
for _, ingress := range svc.Status.LoadBalancer.Ingress {
if ingress.IP == "" {

View File

@ -151,8 +151,8 @@ func masterControllers(ctx context.Context, sc *Context, config *Config) error {
return err
}
if !config.DisableServiceLB && config.Rootless {
return rootlessports.Register(ctx, sc.Core.Core().V1().Service(), config.ControlConfig.HTTPSPort)
if config.Rootless {
return rootlessports.Register(ctx, sc.Core.Core().V1().Service(), !config.DisableServiceLB, config.ControlConfig.HTTPSPort)
}
return nil

View File

@ -1,231 +0,0 @@
// +build !windows
package idtools // import "github.com/docker/docker/pkg/idtools"
import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"sync"
"syscall"
"github.com/docker/docker/pkg/system"
"github.com/opencontainers/runc/libcontainer/user"
)
var (
entOnce sync.Once
getentCmd string
)
func mkdirAs(path string, mode os.FileMode, owner Identity, mkAll, chownExisting bool) error {
// make an array containing the original path asked for, plus (for mkAll == true)
// all path components leading up to the complete path that don't exist before we MkdirAll
// so that we can chown all of them properly at the end. If chownExisting is false, we won't
// chown the full directory path if it exists
var paths []string
stat, err := system.Stat(path)
if err == nil {
if !stat.IsDir() {
return &os.PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR}
}
if !chownExisting {
return nil
}
// short-circuit--we were called with an existing directory and chown was requested
return lazyChown(path, owner.UID, owner.GID, stat)
}
if os.IsNotExist(err) {
paths = []string{path}
}
if mkAll {
// walk back to "/" looking for directories which do not exist
// and add them to the paths array for chown after creation
dirPath := path
for {
dirPath = filepath.Dir(dirPath)
if dirPath == "/" {
break
}
if _, err := os.Stat(dirPath); err != nil && os.IsNotExist(err) {
paths = append(paths, dirPath)
}
}
if err := system.MkdirAll(path, mode); err != nil {
return err
}
} else {
if err := os.Mkdir(path, mode); err != nil && !os.IsExist(err) {
return err
}
}
// even if it existed, we will chown the requested path + any subpaths that
// didn't exist when we called MkdirAll
for _, pathComponent := range paths {
if err := lazyChown(pathComponent, owner.UID, owner.GID, nil); err != nil {
return err
}
}
return nil
}
// CanAccess takes a valid (existing) directory and a uid, gid pair and determines
// if that uid, gid pair has access (execute bit) to the directory
func CanAccess(path string, pair Identity) bool {
statInfo, err := system.Stat(path)
if err != nil {
return false
}
fileMode := os.FileMode(statInfo.Mode())
permBits := fileMode.Perm()
return accessible(statInfo.UID() == uint32(pair.UID),
statInfo.GID() == uint32(pair.GID), permBits)
}
func accessible(isOwner, isGroup bool, perms os.FileMode) bool {
if isOwner && (perms&0100 == 0100) {
return true
}
if isGroup && (perms&0010 == 0010) {
return true
}
if perms&0001 == 0001 {
return true
}
return false
}
// LookupUser uses traditional local system files lookup (from libcontainer/user) on a username,
// followed by a call to `getent` for supporting host configured non-files passwd and group dbs
func LookupUser(username string) (user.User, error) {
// first try a local system files lookup using existing capabilities
usr, err := user.LookupUser(username)
if err == nil {
return usr, nil
}
// local files lookup failed; attempt to call `getent` to query configured passwd dbs
usr, err = getentUser(fmt.Sprintf("%s %s", "passwd", username))
if err != nil {
return user.User{}, err
}
return usr, nil
}
// LookupUID uses traditional local system files lookup (from libcontainer/user) on a uid,
// followed by a call to `getent` for supporting host configured non-files passwd and group dbs
func LookupUID(uid int) (user.User, error) {
// first try a local system files lookup using existing capabilities
usr, err := user.LookupUid(uid)
if err == nil {
return usr, nil
}
// local files lookup failed; attempt to call `getent` to query configured passwd dbs
return getentUser(fmt.Sprintf("%s %d", "passwd", uid))
}
func getentUser(args string) (user.User, error) {
reader, err := callGetent(args)
if err != nil {
return user.User{}, err
}
users, err := user.ParsePasswd(reader)
if err != nil {
return user.User{}, err
}
if len(users) == 0 {
return user.User{}, fmt.Errorf("getent failed to find passwd entry for %q", strings.Split(args, " ")[1])
}
return users[0], nil
}
// LookupGroup uses traditional local system files lookup (from libcontainer/user) on a group name,
// followed by a call to `getent` for supporting host configured non-files passwd and group dbs
func LookupGroup(groupname string) (user.Group, error) {
// first try a local system files lookup using existing capabilities
group, err := user.LookupGroup(groupname)
if err == nil {
return group, nil
}
// local files lookup failed; attempt to call `getent` to query configured group dbs
return getentGroup(fmt.Sprintf("%s %s", "group", groupname))
}
// LookupGID uses traditional local system files lookup (from libcontainer/user) on a group ID,
// followed by a call to `getent` for supporting host configured non-files passwd and group dbs
func LookupGID(gid int) (user.Group, error) {
// first try a local system files lookup using existing capabilities
group, err := user.LookupGid(gid)
if err == nil {
return group, nil
}
// local files lookup failed; attempt to call `getent` to query configured group dbs
return getentGroup(fmt.Sprintf("%s %d", "group", gid))
}
func getentGroup(args string) (user.Group, error) {
reader, err := callGetent(args)
if err != nil {
return user.Group{}, err
}
groups, err := user.ParseGroup(reader)
if err != nil {
return user.Group{}, err
}
if len(groups) == 0 {
return user.Group{}, fmt.Errorf("getent failed to find groups entry for %q", strings.Split(args, " ")[1])
}
return groups[0], nil
}
func callGetent(args string) (io.Reader, error) {
entOnce.Do(func() { getentCmd, _ = resolveBinary("getent") })
// if no `getent` command on host, can't do anything else
if getentCmd == "" {
return nil, fmt.Errorf("")
}
out, err := execCmd(getentCmd, args)
if err != nil {
exitCode, errC := system.GetExitCode(err)
if errC != nil {
return nil, err
}
switch exitCode {
case 1:
return nil, fmt.Errorf("getent reported invalid parameters/database unknown")
case 2:
terms := strings.Split(args, " ")
return nil, fmt.Errorf("getent unable to find entry %q in %s database", terms[1], terms[0])
case 3:
return nil, fmt.Errorf("getent database doesn't support enumeration")
default:
return nil, err
}
}
return bytes.NewReader(out), nil
}
// lazyChown performs a chown only if the uid/gid don't match what's requested
// Normally a Chown is a no-op if uid/gid match, but in some cases this can still cause an error, e.g. if the
// dir is on an NFS share, so don't call chown unless we absolutely must.
func lazyChown(p string, uid, gid int, stat *system.StatT) error {
if stat == nil {
var err error
stat, err = system.Stat(p)
if err != nil {
return err
}
}
if stat.UID() == uint32(uid) && stat.GID() == uint32(gid) {
return nil
}
return os.Chown(p, uid, gid)
}

View File

@ -1,25 +0,0 @@
package idtools // import "github.com/docker/docker/pkg/idtools"
import (
"os"
"github.com/docker/docker/pkg/system"
)
// This is currently a wrapper around MkdirAll, however, since currently
// permissions aren't set through this path, the identity isn't utilized.
// Ownership is handled elsewhere, but in the future could be support here
// too.
func mkdirAs(path string, mode os.FileMode, owner Identity, mkAll, chownExisting bool) error {
if err := system.MkdirAll(path, mode); err != nil {
return err
}
return nil
}
// CanAccess takes a valid (existing) directory and a uid, gid pair and determines
// if that uid, gid pair has access (execute bit) to the directory
// Windows does not require/support this function, so always return true
func CanAccess(path string, identity Identity) bool {
return true
}

View File

@ -1,164 +0,0 @@
package idtools // import "github.com/docker/docker/pkg/idtools"
import (
"fmt"
"regexp"
"sort"
"strconv"
"strings"
"sync"
)
// add a user and/or group to Linux /etc/passwd, /etc/group using standard
// Linux distribution commands:
// adduser --system --shell /bin/false --disabled-login --disabled-password --no-create-home --group <username>
// useradd -r -s /bin/false <username>
var (
once sync.Once
userCommand string
cmdTemplates = map[string]string{
"adduser": "--system --shell /bin/false --no-create-home --disabled-login --disabled-password --group %s",
"useradd": "-r -s /bin/false %s",
"usermod": "-%s %d-%d %s",
}
idOutRegexp = regexp.MustCompile(`uid=([0-9]+).*gid=([0-9]+)`)
// default length for a UID/GID subordinate range
defaultRangeLen = 65536
defaultRangeStart = 100000
userMod = "usermod"
)
// AddNamespaceRangesUser takes a username and uses the standard system
// utility to create a system user/group pair used to hold the
// /etc/sub{uid,gid} ranges which will be used for user namespace
// mapping ranges in containers.
func AddNamespaceRangesUser(name string) (int, int, error) {
if err := addUser(name); err != nil {
return -1, -1, fmt.Errorf("Error adding user %q: %v", name, err)
}
// Query the system for the created uid and gid pair
out, err := execCmd("id", name)
if err != nil {
return -1, -1, fmt.Errorf("Error trying to find uid/gid for new user %q: %v", name, err)
}
matches := idOutRegexp.FindStringSubmatch(strings.TrimSpace(string(out)))
if len(matches) != 3 {
return -1, -1, fmt.Errorf("Can't find uid, gid from `id` output: %q", string(out))
}
uid, err := strconv.Atoi(matches[1])
if err != nil {
return -1, -1, fmt.Errorf("Can't convert found uid (%s) to int: %v", matches[1], err)
}
gid, err := strconv.Atoi(matches[2])
if err != nil {
return -1, -1, fmt.Errorf("Can't convert found gid (%s) to int: %v", matches[2], err)
}
// Now we need to create the subuid/subgid ranges for our new user/group (system users
// do not get auto-created ranges in subuid/subgid)
if err := createSubordinateRanges(name); err != nil {
return -1, -1, fmt.Errorf("Couldn't create subordinate ID ranges: %v", err)
}
return uid, gid, nil
}
func addUser(userName string) error {
once.Do(func() {
// set up which commands are used for adding users/groups dependent on distro
if _, err := resolveBinary("adduser"); err == nil {
userCommand = "adduser"
} else if _, err := resolveBinary("useradd"); err == nil {
userCommand = "useradd"
}
})
if userCommand == "" {
return fmt.Errorf("Cannot add user; no useradd/adduser binary found")
}
args := fmt.Sprintf(cmdTemplates[userCommand], userName)
out, err := execCmd(userCommand, args)
if err != nil {
return fmt.Errorf("Failed to add user with error: %v; output: %q", err, string(out))
}
return nil
}
func createSubordinateRanges(name string) error {
// first, we should verify that ranges weren't automatically created
// by the distro tooling
ranges, err := parseSubuid(name)
if err != nil {
return fmt.Errorf("Error while looking for subuid ranges for user %q: %v", name, err)
}
if len(ranges) == 0 {
// no UID ranges; let's create one
startID, err := findNextUIDRange()
if err != nil {
return fmt.Errorf("Can't find available subuid range: %v", err)
}
out, err := execCmd(userMod, fmt.Sprintf(cmdTemplates[userMod], "v", startID, startID+defaultRangeLen-1, name))
if err != nil {
return fmt.Errorf("Unable to add subuid range to user: %q; output: %s, err: %v", name, out, err)
}
}
ranges, err = parseSubgid(name)
if err != nil {
return fmt.Errorf("Error while looking for subgid ranges for user %q: %v", name, err)
}
if len(ranges) == 0 {
// no GID ranges; let's create one
startID, err := findNextGIDRange()
if err != nil {
return fmt.Errorf("Can't find available subgid range: %v", err)
}
out, err := execCmd(userMod, fmt.Sprintf(cmdTemplates[userMod], "w", startID, startID+defaultRangeLen-1, name))
if err != nil {
return fmt.Errorf("Unable to add subgid range to user: %q; output: %s, err: %v", name, out, err)
}
}
return nil
}
func findNextUIDRange() (int, error) {
ranges, err := parseSubuid("ALL")
if err != nil {
return -1, fmt.Errorf("Couldn't parse all ranges in /etc/subuid file: %v", err)
}
sort.Sort(ranges)
return findNextRangeStart(ranges)
}
func findNextGIDRange() (int, error) {
ranges, err := parseSubgid("ALL")
if err != nil {
return -1, fmt.Errorf("Couldn't parse all ranges in /etc/subgid file: %v", err)
}
sort.Sort(ranges)
return findNextRangeStart(ranges)
}
func findNextRangeStart(rangeList ranges) (int, error) {
startID := defaultRangeStart
for _, arange := range rangeList {
if wouldOverlap(arange, startID) {
startID = arange.Start + arange.Length
}
}
return startID, nil
}
func wouldOverlap(arange subIDRange, ID int) bool {
low := ID
high := ID + defaultRangeLen
if (low >= arange.Start && low <= arange.Start+arange.Length) ||
(high <= arange.Start+arange.Length && high >= arange.Start) {
return true
}
return false
}

View File

@ -1,12 +0,0 @@
// +build !linux
package idtools // import "github.com/docker/docker/pkg/idtools"
import "fmt"
// AddNamespaceRangesUser takes a name and finds an unused uid, gid pair
// and calls the appropriate helper function to add the group and then
// the user to the group in /etc/group and /etc/passwd respectively.
func AddNamespaceRangesUser(name string) (int, int, error) {
return -1, -1, fmt.Errorf("No support for adding users or groups on this OS")
}

View File

@ -1,32 +0,0 @@
// +build !windows
package idtools // import "github.com/docker/docker/pkg/idtools"
import (
"fmt"
"os/exec"
"path/filepath"
"strings"
)
func resolveBinary(binname string) (string, error) {
binaryPath, err := exec.LookPath(binname)
if err != nil {
return "", err
}
resolvedPath, err := filepath.EvalSymlinks(binaryPath)
if err != nil {
return "", err
}
//only return no error if the final resolved binary basename
//matches what was searched for
if filepath.Base(resolvedPath) == binname {
return resolvedPath, nil
}
return "", fmt.Errorf("Binary %q does not resolve to a binary of that name in $PATH (%q)", binname, resolvedPath)
}
func execCmd(cmd, args string) ([]byte, error) {
execCmd := exec.Command(cmd, strings.Split(args, " ")...)
return execCmd.CombinedOutput()
}

View File

@ -4,6 +4,7 @@ import (
"io/ioutil"
"os"
"os/exec"
"os/signal"
"runtime"
"strconv"
"syscall"
@ -43,39 +44,33 @@ func mountSysfs() error {
return errors.Wrap(err, "creating a directory under /tmp")
}
defer os.RemoveAll(tmp)
cmds := [][]string{{"mount", "--rbind", "/sys/fs/cgroup", tmp}}
if err := common.Execs(os.Stderr, os.Environ(), cmds); err != nil {
return errors.Wrapf(err, "executing %v", cmds)
cgroupDir := "/sys/fs/cgroup"
if err := unix.Mount(cgroupDir, tmp, "", uintptr(unix.MS_BIND|unix.MS_REC), ""); err != nil {
return errors.Wrapf(err, "failed to create bind mount on %s", cgroupDir)
}
cmds = [][]string{{"mount", "-t", "sysfs", "none", "/sys"}}
if err := common.Execs(os.Stderr, os.Environ(), cmds); err != nil {
if err := unix.Mount("none", "/sys", "sysfs", 0, ""); err != nil {
// when the sysfs in the parent namespace is RO,
// we can't mount RW sysfs even in the child namespace.
// https://github.com/rootless-containers/rootlesskit/pull/23#issuecomment-429292632
// https://github.com/torvalds/linux/blob/9f203e2f2f065cd74553e6474f0ae3675f39fb0f/fs/namespace.c#L3326-L3328
cmdsRo := [][]string{{"mount", "-t", "sysfs", "-o", "ro", "none", "/sys"}}
logrus.Warnf("failed to mount sysfs (%v), falling back to read-only mount (%v): %v",
cmds, cmdsRo, err)
if err := common.Execs(os.Stderr, os.Environ(), cmdsRo); err != nil {
logrus.Warnf("failed to mount sysfs, falling back to read-only mount: %v", err)
if err := unix.Mount("none", "/sys", "sysfs", uintptr(unix.MS_RDONLY), ""); err != nil {
// when /sys/firmware is masked, even RO sysfs can't be mounted
logrus.Warnf("failed to mount sysfs (%v): %v", cmdsRo, err)
logrus.Warnf("failed to mount sysfs: %v", err)
}
}
cmds = [][]string{{"mount", "-n", "--move", tmp, "/sys/fs/cgroup"}}
if err := common.Execs(os.Stderr, os.Environ(), cmds); err != nil {
return errors.Wrapf(err, "executing %v", cmds)
if err := unix.Mount(tmp, cgroupDir, "", uintptr(unix.MS_MOVE), ""); err != nil {
return errors.Wrapf(err, "failed to move mount point from %s to %s", tmp, cgroupDir)
}
return nil
}
func mountProcfs() error {
cmds := [][]string{{"mount", "-t", "proc", "none", "/proc"}}
if err := common.Execs(os.Stderr, os.Environ(), cmds); err != nil {
cmdsRo := [][]string{{"mount", "-t", "proc", "-o", "ro", "none", "/proc"}}
logrus.Warnf("failed to mount procfs (%v), falling back to read-only mount (%v): %v",
cmds, cmdsRo, err)
if err := common.Execs(os.Stderr, os.Environ(), cmdsRo); err != nil {
logrus.Warnf("failed to mount procfs (%v): %v", cmdsRo, err)
if err := unix.Mount("none", "/proc", "proc", 0, ""); err != nil {
logrus.Warnf("failed to mount procfs, falling back to read-only mount: %v", err)
if err := unix.Mount("none", "/proc", "proc", uintptr(unix.MS_RDONLY), ""); err != nil {
logrus.Warnf("failed to mount procfs: %v", err)
}
}
return nil
@ -171,6 +166,7 @@ type Opt struct {
CopyUpDirs []string
PortDriver port.ChildDriver
MountProcfs bool // needs to be set if (and only if) parent.Opt.CreatePIDNS is set
Reaper bool
}
func Child(opt Opt) error {
@ -242,8 +238,14 @@ func Child(opt Opt) error {
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return errors.Wrapf(err, "command %v exited", opt.TargetCmd)
if opt.Reaper {
if err := runAndReap(cmd); err != nil {
return errors.Wrapf(err, "command %v exited", opt.TargetCmd)
}
} else {
if err := cmd.Run(); err != nil {
return errors.Wrapf(err, "command %v exited", opt.TargetCmd)
}
}
if opt.PortDriver != nil {
portQuitCh <- struct{}{}
@ -251,3 +253,27 @@ func Child(opt Opt) error {
}
return nil
}
func runAndReap(cmd *exec.Cmd) error {
c := make(chan os.Signal, 32)
signal.Notify(c, syscall.SIGCHLD)
if err := cmd.Start(); err != nil {
return err
}
result := make(chan error)
go func() {
defer close(result)
for range c {
for {
if pid, err := syscall.Wait4(-1, nil, syscall.WNOHANG, nil); err != nil || pid <= 0 {
break
} else {
if pid == cmd.Process.Pid {
result <- cmd.Wait()
}
}
}
}
}()
return <-result
}

View File

@ -6,9 +6,9 @@ import (
"os"
"path/filepath"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
"github.com/rootless-containers/rootlesskit/pkg/common"
"github.com/pkg/errors"
)
// generateEtcHosts makes sure the current hostname is resolved into
@ -56,11 +56,9 @@ func mountEtcHosts(tempDir string) error {
if err := ioutil.WriteFile(myEtcHosts, newEtcHosts, 0644); err != nil {
return errors.Wrapf(err, "writing %s", myEtcHosts)
}
cmds := [][]string{
{"mount", "--bind", myEtcHosts, "/etc/hosts"},
}
if err := common.Execs(os.Stderr, os.Environ(), cmds); err != nil {
return errors.Wrapf(err, "executing %v", cmds)
if err := unix.Mount(myEtcHosts, "/etc/hosts", "", uintptr(unix.MS_BIND), ""); err != nil {
return errors.Wrapf(err, "failed to create bind mount /etc/hosts for %s", myEtcHosts)
}
return nil
}

View File

@ -1,13 +1,12 @@
package child
import (
"golang.org/x/sys/unix"
"io/ioutil"
"os"
"path/filepath"
"github.com/pkg/errors"
"github.com/rootless-containers/rootlesskit/pkg/common"
)
func generateResolvConf(dns string) []byte {
@ -36,11 +35,9 @@ func mountResolvConf(tempDir, dns string) error {
if err := ioutil.WriteFile(myResolvConf, generateResolvConf(dns), 0644); err != nil {
return errors.Wrapf(err, "writing %s", myResolvConf)
}
cmds := [][]string{
{"mount", "--bind", myResolvConf, "/etc/resolv.conf"},
}
if err := common.Execs(os.Stderr, os.Environ(), cmds); err != nil {
return errors.Wrapf(err, "executing %v", cmds)
if err := unix.Mount(myResolvConf, "/etc/resolv.conf", "", uintptr(unix.MS_BIND), ""); err != nil {
return errors.Wrapf(err, "failed to create bind mount /etc/resolv.conf for %s", myResolvConf)
}
return nil
}

View File

@ -5,9 +5,10 @@ import (
"os"
"path/filepath"
"golang.org/x/sys/unix"
"github.com/pkg/errors"
"github.com/rootless-containers/rootlesskit/pkg/common"
"github.com/rootless-containers/rootlesskit/pkg/copyup"
)
@ -33,24 +34,23 @@ func (d *childDriver) CopyUp(dirs []string) ([]string, error) {
// TODO: we can support copy-up /tmp by changing bind0TempDir
return copied, errors.New("/tmp cannot be copied up")
}
cmds := [][]string{
// TODO: read-only bind (does not work well for /run)
{"mount", "--rbind", d, bind0},
{"mount", "-n", "-t", "tmpfs", "none", d},
if err := unix.Mount(d, bind0, "", uintptr(unix.MS_BIND|unix.MS_REC), ""); err != nil {
return copied, errors.Wrapf(err, "failed to create bind mount on %s", d)
}
if err := common.Execs(os.Stderr, os.Environ(), cmds); err != nil {
return copied, errors.Wrapf(err, "executing %v", cmds)
if err := unix.Mount("none", d, "tmpfs", 0, ""); err != nil {
return copied, errors.Wrapf(err, "failed to mount tmpfs on %s", d)
}
bind1, err := ioutil.TempDir(d, ".ro")
if err != nil {
return copied, errors.Wrapf(err, "creating a directory under %s", d)
}
cmds = [][]string{
{"mount", "-n", "--move", bind0, bind1},
}
if err := common.Execs(os.Stderr, os.Environ(), cmds); err != nil {
return copied, errors.Wrapf(err, "executing %v", cmds)
if err := unix.Mount(bind0, bind1, "", uintptr(unix.MS_MOVE), ""); err != nil {
return copied, errors.Wrapf(err, "failed to move mount point from %s to %s", bind0, bind1)
}
files, err := ioutil.ReadDir(bind1)
if err != nil {
return copied, errors.Wrapf(err, "reading dir %s", bind1)

View File

@ -3,12 +3,16 @@ package slirp4netns
import (
"context"
"net"
"os"
"os/exec"
"strconv"
"strings"
"syscall"
"time"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
"github.com/rootless-containers/rootlesskit/pkg/common"
"github.com/rootless-containers/rootlesskit/pkg/network"
@ -16,13 +20,62 @@ import (
"github.com/rootless-containers/rootlesskit/pkg/network/parentutils"
)
type Features struct {
// SupportsCIDR --cidr (v0.3.0)
SupportsCIDR bool
// SupportsDisableHostLoopback --disable-host-loopback (v0.3.0)
SupportsDisableHostLoopback bool
// SupportsAPISocket --api-socket (v0.3.0)
SupportsAPISocket bool
// SupportsEnableSandbox --enable-sandbox (v0.4.0)
SupportsEnableSandbox bool
// SupportsEnableSeccomp --enable-seccomp (v0.4.0)
SupportsEnableSeccomp bool
// KernelSupportsSeccomp whether the kernel supports slirp4netns --enable-seccomp
KernelSupportsEnableSeccomp bool
}
func DetectFeatures(binary string) (*Features, error) {
if binary == "" {
return nil, errors.New("got empty slirp4netns binary")
}
realBinary, err := exec.LookPath(binary)
if err != nil {
return nil, errors.Wrapf(err, "slirp4netns binary %q is not installed", binary)
}
cmd := exec.Command(realBinary, "--help")
cmd.Env = os.Environ()
b, err := cmd.CombinedOutput()
s := string(b)
if err != nil {
return nil, errors.Wrapf(err,
"command \"%s --help\" failed, make sure slirp4netns v0.2.0+ is installed: %q",
realBinary, s)
}
kernelSupportsEnableSeccomp := false
if unix.Prctl(unix.PR_GET_SECCOMP, 0, 0, 0, 0) != unix.EINVAL {
kernelSupportsEnableSeccomp = unix.Prctl(unix.PR_SET_SECCOMP, unix.SECCOMP_MODE_FILTER, 0, 0, 0) != unix.EINVAL
}
f := Features{
SupportsCIDR: strings.Contains(s, "--cidr"),
SupportsDisableHostLoopback: strings.Contains(s, "--disable-host-loopback"),
SupportsAPISocket: strings.Contains(s, "--api-socket"),
SupportsEnableSandbox: strings.Contains(s, "--enable-sandbox"),
SupportsEnableSeccomp: strings.Contains(s, "--enable-seccomp"),
KernelSupportsEnableSeccomp: kernelSupportsEnableSeccomp,
}
return &f, nil
}
// NewParentDriver instantiates new parent driver.
// ipnet is supported only for slirp4netns v0.3.0+.
// ipnet MUST be nil for slirp4netns < v0.3.0.
//
// disableHostLoopback is supported only for slirp4netns v0.3.0+
// apiSocketPath is supported only for slirp4netns v0.3.0+
func NewParentDriver(binary string, mtu int, ipnet *net.IPNet, disableHostLoopback bool, apiSocketPath string) network.ParentDriver {
// enableSandbox is supported only for slirp4netns v0.4.0+
// enableSeccomp is supported only for slirp4netns v0.4.0+
func NewParentDriver(binary string, mtu int, ipnet *net.IPNet, disableHostLoopback bool, apiSocketPath string, enableSandbox, enableSeccomp bool) network.ParentDriver {
if binary == "" {
panic("got empty slirp4netns binary")
}
@ -38,6 +91,8 @@ func NewParentDriver(binary string, mtu int, ipnet *net.IPNet, disableHostLoopba
ipnet: ipnet,
disableHostLoopback: disableHostLoopback,
apiSocketPath: apiSocketPath,
enableSandbox: enableSandbox,
enableSeccomp: enableSeccomp,
}
}
@ -47,6 +102,8 @@ type parentDriver struct {
ipnet *net.IPNet
disableHostLoopback bool
apiSocketPath string
enableSandbox bool
enableSeccomp bool
}
func (d *parentDriver) MTU() int {
@ -60,7 +117,14 @@ func (d *parentDriver) ConfigureNetwork(childPID int, stateDir string) (*common.
return nil, common.Seq(cleanups), errors.Wrapf(err, "setting up tap %s", tap)
}
ctx, cancel := context.WithCancel(context.Background())
opts := []string{"--mtu", strconv.Itoa(d.mtu)}
readyR, readyW, err := os.Pipe()
if err != nil {
return nil, common.Seq(cleanups), err
}
defer readyR.Close()
defer readyW.Close()
// -r: readyFD
opts := []string{"--mtu", strconv.Itoa(d.mtu), "-r", "3"}
if d.disableHostLoopback {
opts = append(opts, "--disable-host-loopback")
}
@ -70,10 +134,17 @@ func (d *parentDriver) ConfigureNetwork(childPID int, stateDir string) (*common.
if d.apiSocketPath != "" {
opts = append(opts, "--api-socket", d.apiSocketPath)
}
if d.enableSandbox {
opts = append(opts, "--enable-sandbox")
}
if d.enableSeccomp {
opts = append(opts, "--enable-seccomp")
}
cmd := exec.CommandContext(ctx, d.binary, append(opts, []string{strconv.Itoa(childPID), tap}...)...)
cmd.SysProcAttr = &syscall.SysProcAttr{
Pdeathsig: syscall.SIGKILL,
}
cmd.ExtraFiles = append(cmd.ExtraFiles, readyW)
cleanups = append(cleanups, func() error {
logrus.Debugf("killing slirp4netns")
cancel()
@ -84,6 +155,10 @@ func (d *parentDriver) ConfigureNetwork(childPID int, stateDir string) (*common.
if err := cmd.Start(); err != nil {
return nil, common.Seq(cleanups), errors.Wrapf(err, "executing %v", cmd)
}
if err := waitForReadyFD(cmd.Process.Pid, readyR); err != nil {
return nil, common.Seq(cleanups), errors.Wrapf(err, "waiting for ready fd (%v)", cmd)
}
netmsg := common.NetworkMessage{
Dev: tap,
MTU: d.mtu,
@ -115,6 +190,41 @@ func (d *parentDriver) ConfigureNetwork(childPID int, stateDir string) (*common.
return &netmsg, common.Seq(cleanups), nil
}
// waitForReady is from libpod
// https://github.com/containers/libpod/blob/e6b843312b93ddaf99d0ef94a7e60ff66bc0eac8/libpod/networking_linux.go#L272-L308
func waitForReadyFD(cmdPid int, r *os.File) error {
b := make([]byte, 16)
for {
if err := r.SetDeadline(time.Now().Add(1 * time.Second)); err != nil {
return errors.Wrapf(err, "error setting slirp4netns pipe timeout")
}
if _, err := r.Read(b); err == nil {
break
} else {
if os.IsTimeout(err) {
// Check if the process is still running.
var status syscall.WaitStatus
pid, err := syscall.Wait4(cmdPid, &status, syscall.WNOHANG, nil)
if err != nil {
return errors.Wrapf(err, "failed to read slirp4netns process status")
}
if pid != cmdPid {
continue
}
if status.Exited() {
return errors.New("slirp4netns failed")
}
if status.Signaled() {
return errors.New("slirp4netns killed by signal")
}
continue
}
return errors.Wrapf(err, "failed to read from slirp4netns sync pipe")
}
}
return nil
}
func NewChildDriver() network.ChildDriver {
return &childDriver{}
}

View File

@ -1,9 +1,11 @@
package idtools // import "github.com/docker/docker/pkg/idtools"
// Package idtools is forked from https://github.com/moby/moby/tree/c12f09bf99b54f274a5ae241dd154fa74020cbab/pkg/idtools
package idtools
import (
"bufio"
"fmt"
"os"
"sort"
"strconv"
"strings"
)
@ -33,28 +35,6 @@ const (
subgidFileName = "/etc/subgid"
)
// MkdirAllAndChown creates a directory (include any along the path) and then modifies
// ownership to the requested uid/gid. If the directory already exists, this
// function will still change ownership to the requested uid/gid pair.
func MkdirAllAndChown(path string, mode os.FileMode, owner Identity) error {
return mkdirAs(path, mode, owner, true, true)
}
// MkdirAndChown creates a directory and then modifies ownership to the requested uid/gid.
// If the directory already exists, this function still changes ownership.
// Note that unlike os.Mkdir(), this function does not return IsExist error
// in case path already exists.
func MkdirAndChown(path string, mode os.FileMode, owner Identity) error {
return mkdirAs(path, mode, owner, false, true)
}
// MkdirAllAndChownNew creates a directory (include any along the path) and then modifies
// ownership ONLY of newly created directories to the requested uid/gid. If the
// directories along the path exist, no change of ownership will be performed
func MkdirAllAndChownNew(path string, mode os.FileMode, owner Identity) error {
return mkdirAs(path, mode, owner, true, false)
}
// GetRootUIDGID retrieves the remapped root uid/gid pair from the set of maps.
// If the maps are empty, then the root uid/gid will default to "real" 0/0
func GetRootUIDGID(uidMap, gidMap []IDMap) (int, int, error) {
@ -202,6 +182,8 @@ func (i *IdentityMapping) GIDs() []IDMap {
func createIDMap(subidRanges ranges) []IDMap {
idMap := []IDMap{}
// sort the ranges by lowest ID first
sort.Sort(subidRanges)
containerID := 0
for _, idrange := range subidRanges {
idMap = append(idMap, IDMap{

View File

@ -12,7 +12,6 @@ import (
"strconv"
"syscall"
"github.com/docker/docker/pkg/idtools"
"github.com/gorilla/mux"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@ -22,6 +21,7 @@ import (
"github.com/rootless-containers/rootlesskit/pkg/common"
"github.com/rootless-containers/rootlesskit/pkg/msgutil"
"github.com/rootless-containers/rootlesskit/pkg/network"
"github.com/rootless-containers/rootlesskit/pkg/parent/idtools"
"github.com/rootless-containers/rootlesskit/pkg/port"
)
@ -103,10 +103,6 @@ func Parent(opt Opt) error {
if err := cmd.Start(); err != nil {
return errors.Wrap(err, "failed to start the child")
}
childPIDPath := filepath.Join(opt.StateDir, StateFileChildPID)
if err := ioutil.WriteFile(childPIDPath, []byte(strconv.Itoa(cmd.Process.Pid)), 0444); err != nil {
return errors.Wrapf(err, "failed to write the child PID %d to %s", cmd.Process.Pid, childPIDPath)
}
if err := setupUIDGIDMap(cmd.Process.Pid); err != nil {
return errors.Wrap(err, "failed to setup UID/GID map")
}
@ -176,6 +172,12 @@ func Parent(opt Opt) error {
logrus.Debugf("published port %v", st)
}
}
// after child is fully configured, write PID to child_pid file
childPIDPath := filepath.Join(opt.StateDir, StateFileChildPID)
if err := ioutil.WriteFile(childPIDPath, []byte(strconv.Itoa(cmd.Process.Pid)), 0444); err != nil {
return errors.Wrapf(err, "failed to write the child PID %d to %s", cmd.Process.Pid, childPIDPath)
}
// listens the API
apiSockPath := filepath.Join(opt.StateDir, StateFileAPISock)
apiCloser, err := listenServeAPI(apiSockPath, &router.Backend{PortDriver: opt.PortDriver})

View File

@ -1,487 +1,14 @@
package builtin
import (
"context"
"fmt"
"io"
"io/ioutil"
"net"
"os"
"path/filepath"
"sync"
"syscall"
"time"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
"github.com/rootless-containers/rootlesskit/pkg/msgutil"
"github.com/rootless-containers/rootlesskit/pkg/port"
"github.com/rootless-containers/rootlesskit/pkg/port/portutil"
"github.com/rootless-containers/rootlesskit/pkg/port/builtin/child"
"github.com/rootless-containers/rootlesskit/pkg/port/builtin/parent"
)
const (
opaqueKeySocketPath = "builtin.socketpath"
opaqueKeyChildReadyPipePath = "builtin.readypipepath"
var (
NewParentDriver func(logWriter io.Writer, stateDir string) (port.ParentDriver, error) = parent.NewDriver
NewChildDriver func(logWriter io.Writer) port.ChildDriver = child.NewDriver
)
// NewParentDriver for builtin driver.
func NewParentDriver(logWriter io.Writer, stateDir string) (port.ParentDriver, error) {
// TODO: consider using socketpair FD instead of socket file
socketPath := filepath.Join(stateDir, ".bp.sock")
childReadyPipePath := filepath.Join(stateDir, ".bp-ready.pipe")
// remove the path just incase the previous rootlesskit instance crashed
if err := os.RemoveAll(childReadyPipePath); err != nil {
return nil, errors.Wrapf(err, "cannot remove %s", childReadyPipePath)
}
if err := syscall.Mkfifo(childReadyPipePath, 0600); err != nil {
return nil, errors.Wrapf(err, "cannot mkfifo %s", childReadyPipePath)
}
d := driver{
logWriter: logWriter,
socketPath: socketPath,
childReadyPipePath: childReadyPipePath,
ports: make(map[int]*port.Status, 0),
stoppers: make(map[int]func() error, 0),
nextID: 1,
}
return &d, nil
}
type driver struct {
logWriter io.Writer
socketPath string
childReadyPipePath string
mu sync.Mutex
ports map[int]*port.Status
stoppers map[int]func() error
nextID int
}
func (d *driver) OpaqueForChild() map[string]string {
return map[string]string{
opaqueKeySocketPath: d.socketPath,
opaqueKeyChildReadyPipePath: d.childReadyPipePath,
}
}
func (d *driver) RunParentDriver(initComplete chan struct{}, quit <-chan struct{}, _ *port.ChildContext) error {
childReadyPipeR, err := os.OpenFile(d.childReadyPipePath, os.O_RDONLY, os.ModeNamedPipe)
if err != nil {
return err
}
if _, err = ioutil.ReadAll(childReadyPipeR); err != nil {
return err
}
childReadyPipeR.Close()
var dialer net.Dialer
conn, err := dialer.Dial("unix", d.socketPath)
if err != nil {
return err
}
err = initiate(conn.(*net.UnixConn))
conn.Close()
if err != nil {
return err
}
initComplete <- struct{}{}
<-quit
return nil
}
func (d *driver) AddPort(ctx context.Context, spec port.Spec) (*port.Status, error) {
d.mu.Lock()
err := portutil.ValidatePortSpec(spec, d.ports)
d.mu.Unlock()
if err != nil {
return nil, err
}
routineStopCh := make(chan struct{})
routineStop := func() error {
close(routineStopCh)
return nil // FIXME
}
switch spec.Proto {
case "tcp":
err = startTCPRoutines(d.socketPath, spec, routineStopCh, d.logWriter)
case "udp":
err = startUDPRoutines(d.socketPath, spec, routineStopCh, d.logWriter)
default:
// NOTREACHED
return nil, errors.New("spec was not validated?")
}
if err != nil {
return nil, err
}
d.mu.Lock()
id := d.nextID
st := port.Status{
ID: id,
Spec: spec,
}
d.ports[id] = &st
d.stoppers[id] = routineStop
d.nextID++
d.mu.Unlock()
return &st, nil
}
func (d *driver) ListPorts(ctx context.Context) ([]port.Status, error) {
var ports []port.Status
d.mu.Lock()
for _, p := range d.ports {
ports = append(ports, *p)
}
d.mu.Unlock()
return ports, nil
}
func (d *driver) RemovePort(ctx context.Context, id int) error {
d.mu.Lock()
defer d.mu.Unlock()
stop, ok := d.stoppers[id]
if !ok {
return errors.Errorf("unknown id: %d", id)
}
err := stop()
delete(d.stoppers, id)
delete(d.ports, id)
return err
}
func initiate(c *net.UnixConn) error {
req := request{
Type: requestTypeInit,
}
if _, err := msgutil.MarshalToWriter(c, &req); err != nil {
return err
}
if err := c.CloseWrite(); err != nil {
return err
}
var rep reply
if _, err := msgutil.UnmarshalFromReader(c, &rep); err != nil {
return err
}
return c.CloseRead()
}
func connectToChild(socketPath string, spec port.Spec) (int, error) {
var dialer net.Dialer
conn, err := dialer.Dial("unix", socketPath)
if err != nil {
return 0, err
}
defer conn.Close()
c := conn.(*net.UnixConn)
req := request{
Type: requestTypeConnect,
Proto: spec.Proto,
Port: spec.ChildPort,
}
if _, err := msgutil.MarshalToWriter(c, &req); err != nil {
return 0, err
}
if err := c.CloseWrite(); err != nil {
return 0, err
}
oobSpace := unix.CmsgSpace(4)
oob := make([]byte, oobSpace)
_, oobN, _, _, err := c.ReadMsgUnix(nil, oob)
if err != nil {
return 0, err
}
if oobN != oobSpace {
return 0, errors.Errorf("expected OOB space %d, got %d", oobSpace, oobN)
}
oob = oob[:oobN]
fd, err := parseFDFromOOB(oob)
if err != nil {
return 0, err
}
if err := c.CloseRead(); err != nil {
return 0, err
}
return fd, nil
}
func connectToChildWithRetry(socketPath string, spec port.Spec, retries int) (int, error) {
for i := 0; i < retries; i++ {
fd, err := connectToChild(socketPath, spec)
if i == retries-1 && err != nil {
return 0, err
}
if err == nil {
return fd, err
}
// TODO: backoff
time.Sleep(time.Duration(i*5) * time.Millisecond)
}
// NOT REACHED
return 0, errors.New("reached max retry")
}
func parseFDFromOOB(oob []byte) (int, error) {
scms, err := unix.ParseSocketControlMessage(oob)
if err != nil {
return 0, err
}
if len(scms) != 1 {
return 0, errors.Errorf("unexpected scms: %v", scms)
}
scm := scms[0]
fds, err := unix.ParseUnixRights(&scm)
if err != nil {
return 0, err
}
if len(fds) != 1 {
return 0, errors.Errorf("unexpected fds: %v", fds)
}
return fds[0], nil
}
func startTCPRoutines(socketPath string, spec port.Spec, stopCh <-chan struct{}, logWriter io.Writer) error {
ln, err := net.Listen("tcp", fmt.Sprintf("%s:%d", spec.ParentIP, spec.ParentPort))
if err != nil {
fmt.Fprintf(logWriter, "listen: %v\n", err)
return err
}
newConns := make(chan net.Conn)
go func() {
for {
c, err := ln.Accept()
if err != nil {
fmt.Fprintf(logWriter, "accept: %v\n", err)
close(newConns)
return
}
newConns <- c
}
}()
go func() {
defer ln.Close()
for {
select {
case c, ok := <-newConns:
if !ok {
return
}
go func() {
if err := copyConnToChild(c, socketPath, spec, stopCh); err != nil {
fmt.Fprintf(logWriter, "copyConnToChild: %v\n", err)
return
}
}()
case <-stopCh:
return
}
}
}()
// no wait
return nil
}
func startUDPRoutines(socketPath string, spec port.Spec, stopCh <-chan struct{}, logWriter io.Writer) error {
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", spec.ParentIP, spec.ParentPort))
if err != nil {
return err
}
c, err := net.ListenUDP("udp", addr)
if err != nil {
return err
}
go func() {
if err := copyConnToChild(c, socketPath, spec, stopCh); err != nil {
fmt.Fprintf(logWriter, "copyConnToChild: %v\n", err)
return
}
}()
// no wait
return nil
}
func copyConnToChild(c net.Conn, socketPath string, spec port.Spec, stopCh <-chan struct{}) error {
defer c.Close()
// get fd from the child as an SCM_RIGHTS cmsg
fd, err := connectToChildWithRetry(socketPath, spec, 10)
if err != nil {
return err
}
f := os.NewFile(uintptr(fd), "")
defer f.Close()
fc, err := net.FileConn(f)
if err != nil {
return err
}
defer fc.Close()
bicopy(c, fc, stopCh)
return nil
}
// bicopy is based on libnetwork/cmd/proxy/tcp_proxy.go .
// NOTE: sendfile(2) cannot be used for sockets
func bicopy(x, y net.Conn, quit <-chan struct{}) {
var wg sync.WaitGroup
var broker = func(to, from net.Conn) {
io.Copy(to, from)
if fromTCP, ok := from.(*net.TCPConn); ok {
fromTCP.CloseRead()
}
if toTCP, ok := to.(*net.TCPConn); ok {
toTCP.CloseWrite()
}
wg.Done()
}
wg.Add(2)
go broker(x, y)
go broker(y, x)
finish := make(chan struct{})
go func() {
wg.Wait()
close(finish)
}()
select {
case <-quit:
case <-finish:
}
x.Close()
y.Close()
<-finish
}
const (
requestTypeInit = "init"
requestTypeConnect = "connect"
)
// request and response are encoded as JSON with uint32le length header.
type request struct {
Type string // "init" or "connect"
Proto string // "tcp" or "udp"
Port int
}
// may contain FD as OOB
type reply struct {
Error string
}
func NewChildDriver(logWriter io.Writer) port.ChildDriver {
return &childDriver{
logWriter: logWriter,
}
}
type childDriver struct {
logWriter io.Writer
}
func (d *childDriver) RunChildDriver(opaque map[string]string, quit <-chan struct{}) error {
socketPath := opaque[opaqueKeySocketPath]
if socketPath == "" {
return errors.New("socket path not set")
}
childReadyPipePath := opaque[opaqueKeyChildReadyPipePath]
if childReadyPipePath == "" {
return errors.New("child ready pipe path not set")
}
childReadyPipeW, err := os.OpenFile(childReadyPipePath, os.O_WRONLY, os.ModeNamedPipe)
if err != nil {
return err
}
ln, err := net.ListenUnix("unix", &net.UnixAddr{
Name: socketPath,
Net: "unix",
})
if err != nil {
return err
}
// write nothing, just close
if err = childReadyPipeW.Close(); err != nil {
return err
}
stopAccept := make(chan struct{}, 1)
go func() {
<-quit
stopAccept <- struct{}{}
ln.Close()
}()
for {
c, err := ln.AcceptUnix()
if err != nil {
select {
case <-stopAccept:
return nil
default:
}
return err
}
go func() {
if rerr := d.routine(c); rerr != nil {
rep := reply{
Error: rerr.Error(),
}
msgutil.MarshalToWriter(c, &rep)
}
c.Close()
}()
}
return nil
}
func (d *childDriver) routine(c *net.UnixConn) error {
var req request
if _, err := msgutil.UnmarshalFromReader(c, &req); err != nil {
return err
}
switch req.Type {
case requestTypeInit:
return d.handleConnectInit(c, &req)
case requestTypeConnect:
return d.handleConnectRequest(c, &req)
default:
return errors.Errorf("unknown request type %q", req.Type)
}
}
func (d *childDriver) handleConnectInit(c *net.UnixConn, req *request) error {
_, err := msgutil.MarshalToWriter(c, nil)
return err
}
func (d *childDriver) handleConnectRequest(c *net.UnixConn, req *request) error {
switch req.Proto {
case "tcp":
case "udp":
default:
return errors.Errorf("unknown proto: %q", req.Proto)
}
var dialer net.Dialer
targetConn, err := dialer.Dial(req.Proto, fmt.Sprintf("127.0.0.1:%d", req.Port))
if err != nil {
return err
}
defer targetConn.Close() // no effect on duplicated FD
targetConnFiler, ok := targetConn.(filer)
if !ok {
return errors.Errorf("unknown target connection: %+v", targetConn)
}
targetConnFile, err := targetConnFiler.File()
if err != nil {
return err
}
oob := unix.UnixRights(int(targetConnFile.Fd()))
f, err := c.File()
if err != nil {
return err
}
err = unix.Sendmsg(int(f.Fd()), []byte("dummy"), oob, nil, 0)
return err
}
// filer is implemented by *net.TCPConn and *net.UDPConn
type filer interface {
File() (f *os.File, err error)
}

View File

@ -0,0 +1,134 @@
package child
import (
"fmt"
"io"
"net"
"os"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
"github.com/rootless-containers/rootlesskit/pkg/msgutil"
"github.com/rootless-containers/rootlesskit/pkg/port"
"github.com/rootless-containers/rootlesskit/pkg/port/builtin/msg"
opaquepkg "github.com/rootless-containers/rootlesskit/pkg/port/builtin/opaque"
)
func NewDriver(logWriter io.Writer) port.ChildDriver {
return &childDriver{
logWriter: logWriter,
}
}
type childDriver struct {
logWriter io.Writer
}
func (d *childDriver) RunChildDriver(opaque map[string]string, quit <-chan struct{}) error {
socketPath := opaque[opaquepkg.SocketPath]
if socketPath == "" {
return errors.New("socket path not set")
}
childReadyPipePath := opaque[opaquepkg.ChildReadyPipePath]
if childReadyPipePath == "" {
return errors.New("child ready pipe path not set")
}
childReadyPipeW, err := os.OpenFile(childReadyPipePath, os.O_WRONLY, os.ModeNamedPipe)
if err != nil {
return err
}
ln, err := net.ListenUnix("unix", &net.UnixAddr{
Name: socketPath,
Net: "unix",
})
if err != nil {
return err
}
// write nothing, just close
if err = childReadyPipeW.Close(); err != nil {
return err
}
stopAccept := make(chan struct{}, 1)
go func() {
<-quit
stopAccept <- struct{}{}
ln.Close()
}()
for {
c, err := ln.AcceptUnix()
if err != nil {
select {
case <-stopAccept:
return nil
default:
}
return err
}
go func() {
if rerr := d.routine(c); rerr != nil {
rep := msg.Reply{
Error: rerr.Error(),
}
msgutil.MarshalToWriter(c, &rep)
}
c.Close()
}()
}
return nil
}
func (d *childDriver) routine(c *net.UnixConn) error {
var req msg.Request
if _, err := msgutil.UnmarshalFromReader(c, &req); err != nil {
return err
}
switch req.Type {
case msg.RequestTypeInit:
return d.handleConnectInit(c, &req)
case msg.RequestTypeConnect:
return d.handleConnectRequest(c, &req)
default:
return errors.Errorf("unknown request type %q", req.Type)
}
}
func (d *childDriver) handleConnectInit(c *net.UnixConn, req *msg.Request) error {
_, err := msgutil.MarshalToWriter(c, nil)
return err
}
func (d *childDriver) handleConnectRequest(c *net.UnixConn, req *msg.Request) error {
switch req.Proto {
case "tcp":
case "udp":
default:
return errors.Errorf("unknown proto: %q", req.Proto)
}
var dialer net.Dialer
targetConn, err := dialer.Dial(req.Proto, fmt.Sprintf("127.0.0.1:%d", req.Port))
if err != nil {
return err
}
defer targetConn.Close() // no effect on duplicated FD
targetConnFiler, ok := targetConn.(filer)
if !ok {
return errors.Errorf("unknown target connection: %+v", targetConn)
}
targetConnFile, err := targetConnFiler.File()
if err != nil {
return err
}
oob := unix.UnixRights(int(targetConnFile.Fd()))
f, err := c.File()
if err != nil {
return err
}
err = unix.Sendmsg(int(f.Fd()), []byte("dummy"), oob, nil, 0)
return err
}
// filer is implemented by *net.TCPConn and *net.UDPConn
type filer interface {
File() (f *os.File, err error)
}

View File

@ -0,0 +1,129 @@
package msg
import (
"net"
"time"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
"github.com/rootless-containers/rootlesskit/pkg/msgutil"
"github.com/rootless-containers/rootlesskit/pkg/port"
)
const (
RequestTypeInit = "init"
RequestTypeConnect = "connect"
)
// Request and Response are encoded as JSON with uint32le length header.
type Request struct {
Type string // "init" or "connect"
Proto string // "tcp" or "udp"
Port int
}
// Reply may contain FD as OOB
type Reply struct {
Error string
}
// Initiate sends "init" request to the child UNIX socket.
func Initiate(c *net.UnixConn) error {
req := Request{
Type: RequestTypeInit,
}
if _, err := msgutil.MarshalToWriter(c, &req); err != nil {
return err
}
if err := c.CloseWrite(); err != nil {
return err
}
var rep Reply
if _, err := msgutil.UnmarshalFromReader(c, &rep); err != nil {
return err
}
return c.CloseRead()
}
// ConnectToChild connects to the child UNIX socket, and obtains TCP or UDP socket FD
// that corresponds to the port spec.
func ConnectToChild(c *net.UnixConn, spec port.Spec) (int, error) {
req := Request{
Type: RequestTypeConnect,
Proto: spec.Proto,
Port: spec.ChildPort,
}
if _, err := msgutil.MarshalToWriter(c, &req); err != nil {
return 0, err
}
if err := c.CloseWrite(); err != nil {
return 0, err
}
oobSpace := unix.CmsgSpace(4)
oob := make([]byte, oobSpace)
_, oobN, _, _, err := c.ReadMsgUnix(nil, oob)
if err != nil {
return 0, err
}
if oobN != oobSpace {
return 0, errors.Errorf("expected OOB space %d, got %d", oobSpace, oobN)
}
oob = oob[:oobN]
fd, err := parseFDFromOOB(oob)
if err != nil {
return 0, err
}
if err := c.CloseRead(); err != nil {
return 0, err
}
return fd, nil
}
// ConnectToChildWithSocketPath wraps ConnectToChild
func ConnectToChildWithSocketPath(socketPath string, spec port.Spec) (int, error) {
var dialer net.Dialer
conn, err := dialer.Dial("unix", socketPath)
if err != nil {
return 0, err
}
defer conn.Close()
c := conn.(*net.UnixConn)
return ConnectToChild(c, spec)
}
// ConnectToChildWithRetry retries ConnectToChild every (i*5) milliseconds.
func ConnectToChildWithRetry(socketPath string, spec port.Spec, retries int) (int, error) {
for i := 0; i < retries; i++ {
fd, err := ConnectToChildWithSocketPath(socketPath, spec)
if i == retries-1 && err != nil {
return 0, err
}
if err == nil {
return fd, err
}
// TODO: backoff
time.Sleep(time.Duration(i*5) * time.Millisecond)
}
// NOT REACHED
return 0, errors.New("reached max retry")
}
func parseFDFromOOB(oob []byte) (int, error) {
scms, err := unix.ParseSocketControlMessage(oob)
if err != nil {
return 0, err
}
if len(scms) != 1 {
return 0, errors.Errorf("unexpected scms: %v", scms)
}
scm := scms[0]
fds, err := unix.ParseUnixRights(&scm)
if err != nil {
return 0, err
}
if len(fds) != 1 {
return 0, errors.Errorf("unexpected fds: %v", fds)
}
return fds[0], nil
}

View File

@ -0,0 +1,6 @@
package opaque
const (
SocketPath = "builtin.socketpath"
ChildReadyPipePath = "builtin.readypipepath"
)

View File

@ -0,0 +1,145 @@
package parent
import (
"context"
"io"
"io/ioutil"
"net"
"os"
"path/filepath"
"sync"
"syscall"
"github.com/pkg/errors"
"github.com/rootless-containers/rootlesskit/pkg/port"
"github.com/rootless-containers/rootlesskit/pkg/port/builtin/msg"
"github.com/rootless-containers/rootlesskit/pkg/port/builtin/opaque"
"github.com/rootless-containers/rootlesskit/pkg/port/builtin/parent/tcp"
"github.com/rootless-containers/rootlesskit/pkg/port/builtin/parent/udp"
"github.com/rootless-containers/rootlesskit/pkg/port/portutil"
)
// NewDriver for builtin driver.
func NewDriver(logWriter io.Writer, stateDir string) (port.ParentDriver, error) {
// TODO: consider using socketpair FD instead of socket file
socketPath := filepath.Join(stateDir, ".bp.sock")
childReadyPipePath := filepath.Join(stateDir, ".bp-ready.pipe")
// remove the path just in case the previous rootlesskit instance crashed
if err := os.RemoveAll(childReadyPipePath); err != nil {
return nil, errors.Wrapf(err, "cannot remove %s", childReadyPipePath)
}
if err := syscall.Mkfifo(childReadyPipePath, 0600); err != nil {
return nil, errors.Wrapf(err, "cannot mkfifo %s", childReadyPipePath)
}
d := driver{
logWriter: logWriter,
socketPath: socketPath,
childReadyPipePath: childReadyPipePath,
ports: make(map[int]*port.Status, 0),
stoppers: make(map[int]func() error, 0),
nextID: 1,
}
return &d, nil
}
type driver struct {
logWriter io.Writer
socketPath string
childReadyPipePath string
mu sync.Mutex
ports map[int]*port.Status
stoppers map[int]func() error
nextID int
}
func (d *driver) OpaqueForChild() map[string]string {
return map[string]string{
opaque.SocketPath: d.socketPath,
opaque.ChildReadyPipePath: d.childReadyPipePath,
}
}
func (d *driver) RunParentDriver(initComplete chan struct{}, quit <-chan struct{}, _ *port.ChildContext) error {
childReadyPipeR, err := os.OpenFile(d.childReadyPipePath, os.O_RDONLY, os.ModeNamedPipe)
if err != nil {
return err
}
if _, err = ioutil.ReadAll(childReadyPipeR); err != nil {
return err
}
childReadyPipeR.Close()
var dialer net.Dialer
conn, err := dialer.Dial("unix", d.socketPath)
if err != nil {
return err
}
err = msg.Initiate(conn.(*net.UnixConn))
conn.Close()
if err != nil {
return err
}
initComplete <- struct{}{}
<-quit
return nil
}
func (d *driver) AddPort(ctx context.Context, spec port.Spec) (*port.Status, error) {
d.mu.Lock()
err := portutil.ValidatePortSpec(spec, d.ports)
d.mu.Unlock()
if err != nil {
return nil, err
}
routineStopCh := make(chan struct{})
routineStop := func() error {
close(routineStopCh)
return nil // FIXME
}
switch spec.Proto {
case "tcp":
err = tcp.Run(d.socketPath, spec, routineStopCh, d.logWriter)
case "udp":
err = udp.Run(d.socketPath, spec, routineStopCh, d.logWriter)
default:
// NOTREACHED
return nil, errors.New("spec was not validated?")
}
if err != nil {
return nil, err
}
d.mu.Lock()
id := d.nextID
st := port.Status{
ID: id,
Spec: spec,
}
d.ports[id] = &st
d.stoppers[id] = routineStop
d.nextID++
d.mu.Unlock()
return &st, nil
}
func (d *driver) ListPorts(ctx context.Context) ([]port.Status, error) {
var ports []port.Status
d.mu.Lock()
for _, p := range d.ports {
ports = append(ports, *p)
}
d.mu.Unlock()
return ports, nil
}
func (d *driver) RemovePort(ctx context.Context, id int) error {
d.mu.Lock()
defer d.mu.Unlock()
stop, ok := d.stoppers[id]
if !ok {
return errors.Errorf("unknown id: %d", id)
}
err := stop()
delete(d.stoppers, id)
delete(d.ports, id)
return err
}

View File

@ -0,0 +1,104 @@
package tcp
import (
"fmt"
"io"
"net"
"os"
"sync"
"github.com/rootless-containers/rootlesskit/pkg/port"
"github.com/rootless-containers/rootlesskit/pkg/port/builtin/msg"
)
func Run(socketPath string, spec port.Spec, stopCh <-chan struct{}, logWriter io.Writer) error {
ln, err := net.Listen("tcp", fmt.Sprintf("%s:%d", spec.ParentIP, spec.ParentPort))
if err != nil {
fmt.Fprintf(logWriter, "listen: %v\n", err)
return err
}
newConns := make(chan net.Conn)
go func() {
for {
c, err := ln.Accept()
if err != nil {
fmt.Fprintf(logWriter, "accept: %v\n", err)
close(newConns)
return
}
newConns <- c
}
}()
go func() {
defer ln.Close()
for {
select {
case c, ok := <-newConns:
if !ok {
return
}
go func() {
if err := copyConnToChild(c, socketPath, spec, stopCh); err != nil {
fmt.Fprintf(logWriter, "copyConnToChild: %v\n", err)
return
}
}()
case <-stopCh:
return
}
}
}()
// no wait
return nil
}
func copyConnToChild(c net.Conn, socketPath string, spec port.Spec, stopCh <-chan struct{}) error {
defer c.Close()
// get fd from the child as an SCM_RIGHTS cmsg
fd, err := msg.ConnectToChildWithRetry(socketPath, spec, 10)
if err != nil {
return err
}
f := os.NewFile(uintptr(fd), "")
defer f.Close()
fc, err := net.FileConn(f)
if err != nil {
return err
}
defer fc.Close()
bicopy(c, fc, stopCh)
return nil
}
// bicopy is based on libnetwork/cmd/proxy/tcp_proxy.go .
// NOTE: sendfile(2) cannot be used for sockets
func bicopy(x, y net.Conn, quit <-chan struct{}) {
var wg sync.WaitGroup
var broker = func(to, from net.Conn) {
io.Copy(to, from)
if fromTCP, ok := from.(*net.TCPConn); ok {
fromTCP.CloseRead()
}
if toTCP, ok := to.(*net.TCPConn); ok {
toTCP.CloseWrite()
}
wg.Done()
}
wg.Add(2)
go broker(x, y)
go broker(y, x)
finish := make(chan struct{})
go func() {
wg.Wait()
close(finish)
}()
select {
case <-quit:
case <-finish:
}
x.Close()
y.Close()
<-finish
}

View File

@ -0,0 +1,60 @@
package udp
import (
"fmt"
"io"
"net"
"os"
"github.com/pkg/errors"
"github.com/rootless-containers/rootlesskit/pkg/port"
"github.com/rootless-containers/rootlesskit/pkg/port/builtin/msg"
"github.com/rootless-containers/rootlesskit/pkg/port/builtin/parent/udp/udpproxy"
)
func Run(socketPath string, spec port.Spec, stopCh <-chan struct{}, logWriter io.Writer) error {
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", spec.ParentIP, spec.ParentPort))
if err != nil {
return err
}
c, err := net.ListenUDP("udp", addr)
if err != nil {
return err
}
udpp := &udpproxy.UDPProxy{
LogWriter: logWriter,
Listener: c,
BackendDial: func() (*net.UDPConn, error) {
// get fd from the child as an SCM_RIGHTS cmsg
fd, err := msg.ConnectToChildWithRetry(socketPath, spec, 10)
if err != nil {
return nil, err
}
f := os.NewFile(uintptr(fd), "")
defer f.Close()
fc, err := net.FileConn(f)
if err != nil {
return nil, err
}
uc, ok := fc.(*net.UDPConn)
if !ok {
return nil, errors.Errorf("file conn doesn't implement *net.UDPConn: %+v", fc)
}
return uc, nil
},
}
go udpp.Run()
go func() {
for {
select {
case <-stopCh:
// udpp.Close closes ln as well
udpp.Close()
return
}
}
}()
// no wait
return nil
}

View File

@ -0,0 +1,150 @@
// Package udpproxy is from https://raw.githubusercontent.com/docker/libnetwork/fec6476dfa21380bf8ee4d74048515d968c1ee63/cmd/proxy/udp_proxy.go
package udpproxy
import (
"encoding/binary"
"fmt"
"io"
"net"
"strings"
"sync"
"syscall"
"time"
)
const (
// UDPConnTrackTimeout is the timeout used for UDP connection tracking
UDPConnTrackTimeout = 90 * time.Second
// UDPBufSize is the buffer size for the UDP proxy
UDPBufSize = 65507
)
// A net.Addr where the IP is split into two fields so you can use it as a key
// in a map:
type connTrackKey struct {
IPHigh uint64
IPLow uint64
Port int
}
func newConnTrackKey(addr *net.UDPAddr) *connTrackKey {
if len(addr.IP) == net.IPv4len {
return &connTrackKey{
IPHigh: 0,
IPLow: uint64(binary.BigEndian.Uint32(addr.IP)),
Port: addr.Port,
}
}
return &connTrackKey{
IPHigh: binary.BigEndian.Uint64(addr.IP[:8]),
IPLow: binary.BigEndian.Uint64(addr.IP[8:]),
Port: addr.Port,
}
}
type connTrackMap map[connTrackKey]*net.UDPConn
// UDPProxy is proxy for which handles UDP datagrams.
// From libnetwork udp_proxy.go .
type UDPProxy struct {
LogWriter io.Writer
Listener *net.UDPConn
BackendDial func() (*net.UDPConn, error)
connTrackTable connTrackMap
connTrackLock sync.Mutex
}
func (proxy *UDPProxy) replyLoop(proxyConn *net.UDPConn, clientAddr *net.UDPAddr, clientKey *connTrackKey) {
defer func() {
proxy.connTrackLock.Lock()
delete(proxy.connTrackTable, *clientKey)
proxy.connTrackLock.Unlock()
proxyConn.Close()
}()
readBuf := make([]byte, UDPBufSize)
for {
proxyConn.SetReadDeadline(time.Now().Add(UDPConnTrackTimeout))
again:
read, err := proxyConn.Read(readBuf)
if err != nil {
if err, ok := err.(*net.OpError); ok && err.Err == syscall.ECONNREFUSED {
// This will happen if the last write failed
// (e.g: nothing is actually listening on the
// proxied port on the container), ignore it
// and continue until UDPConnTrackTimeout
// expires:
goto again
}
return
}
for i := 0; i != read; {
written, err := proxy.Listener.WriteToUDP(readBuf[i:read], clientAddr)
if err != nil {
return
}
i += written
}
}
}
// Run starts forwarding the traffic using UDP.
func (proxy *UDPProxy) Run() {
proxy.connTrackTable = make(connTrackMap)
readBuf := make([]byte, UDPBufSize)
for {
read, from, err := proxy.Listener.ReadFromUDP(readBuf)
if err != nil {
// NOTE: Apparently ReadFrom doesn't return
// ECONNREFUSED like Read do (see comment in
// UDPProxy.replyLoop)
if !isClosedError(err) {
fmt.Fprintf(proxy.LogWriter, "Stopping proxy on udp: %v\n", err)
}
break
}
fromKey := newConnTrackKey(from)
proxy.connTrackLock.Lock()
proxyConn, hit := proxy.connTrackTable[*fromKey]
if !hit {
proxyConn, err = proxy.BackendDial()
if err != nil {
fmt.Fprintf(proxy.LogWriter, "Can't proxy a datagram to udp: %v\n", err)
proxy.connTrackLock.Unlock()
continue
}
proxy.connTrackTable[*fromKey] = proxyConn
go proxy.replyLoop(proxyConn, from, fromKey)
}
proxy.connTrackLock.Unlock()
for i := 0; i != read; {
written, err := proxyConn.Write(readBuf[i:read])
if err != nil {
fmt.Fprintf(proxy.LogWriter, "Can't proxy a datagram to udp: %v\n", err)
break
}
i += written
}
}
}
// Close stops forwarding the traffic.
func (proxy *UDPProxy) Close() {
proxy.Listener.Close()
proxy.connTrackLock.Lock()
defer proxy.connTrackLock.Unlock()
for _, conn := range proxy.connTrackTable {
conn.Close()
}
}
func isClosedError(err error) bool {
/* This comparison is ugly, but unfortunately, net.go doesn't export errClosing.
* See:
* http://golang.org/src/pkg/net/net.go
* https://code.google.com/p/go/issues/detail?id=4337
* https://groups.google.com/forum/#!msg/golang-nuts/0_aaCvBmOcM/SptmDyX1XJMJ
*/
return strings.HasSuffix(err.Error(), "use of closed network connection")
}

11
vendor/modules.txt vendored
View File

@ -383,7 +383,6 @@ github.com/docker/docker/api/types/volume
github.com/docker/docker/client
github.com/docker/docker/daemon/logger/jsonfilelog/jsonlog
github.com/docker/docker/errdefs
github.com/docker/docker/pkg/idtools
github.com/docker/docker/pkg/jsonmessage
github.com/docker/docker/pkg/longpath
github.com/docker/docker/pkg/mount
@ -774,7 +773,7 @@ github.com/rancher/wrangler-api/pkg/generated/controllers/rbac
github.com/rancher/wrangler-api/pkg/generated/controllers/rbac/v1
# github.com/robfig/cron v1.1.0
github.com/robfig/cron
# github.com/rootless-containers/rootlesskit v0.6.0
# github.com/rootless-containers/rootlesskit v0.7.2
github.com/rootless-containers/rootlesskit/pkg/api/client
github.com/rootless-containers/rootlesskit/pkg/api/router
github.com/rootless-containers/rootlesskit/pkg/child
@ -787,8 +786,16 @@ github.com/rootless-containers/rootlesskit/pkg/network/iputils
github.com/rootless-containers/rootlesskit/pkg/network/parentutils
github.com/rootless-containers/rootlesskit/pkg/network/slirp4netns
github.com/rootless-containers/rootlesskit/pkg/parent
github.com/rootless-containers/rootlesskit/pkg/parent/idtools
github.com/rootless-containers/rootlesskit/pkg/port
github.com/rootless-containers/rootlesskit/pkg/port/builtin
github.com/rootless-containers/rootlesskit/pkg/port/builtin/child
github.com/rootless-containers/rootlesskit/pkg/port/builtin/msg
github.com/rootless-containers/rootlesskit/pkg/port/builtin/opaque
github.com/rootless-containers/rootlesskit/pkg/port/builtin/parent
github.com/rootless-containers/rootlesskit/pkg/port/builtin/parent/tcp
github.com/rootless-containers/rootlesskit/pkg/port/builtin/parent/udp
github.com/rootless-containers/rootlesskit/pkg/port/builtin/parent/udp/udpproxy
github.com/rootless-containers/rootlesskit/pkg/port/portutil
# github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c
github.com/rubiojr/go-vhd/vhd