Skip to content

Commit 0267ad0

Browse files
author
Mrunal Patel
committed
Merge pull request opencontainers#340 from dqminh/replace-env-netlink
nsexec: replace usage of environment variable with netlink message
2 parents 39b80c4 + 7d423cb commit 0267ad0

File tree

5 files changed

+265
-67
lines changed

5 files changed

+265
-67
lines changed

libcontainer/container_linux.go

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
package libcontainer
44

55
import (
6+
"bytes"
67
"encoding/json"
78
"fmt"
9+
"io"
810
"io/ioutil"
911
"os"
1012
"os/exec"
@@ -19,6 +21,7 @@ import (
1921
"github.com/opencontainers/runc/libcontainer/cgroups"
2022
"github.com/opencontainers/runc/libcontainer/configs"
2123
"github.com/opencontainers/runc/libcontainer/criurpc"
24+
"github.com/vishvananda/netlink/nl"
2225
)
2326

2427
const stdioFdCount = 3
@@ -218,7 +221,7 @@ func (c *linuxContainer) newParentProcess(p *Process, doInit bool) (parentProces
218221
return nil, newSystemError(err)
219222
}
220223
if !doInit {
221-
return c.newSetnsProcess(p, cmd, parentPipe, childPipe), nil
224+
return c.newSetnsProcess(p, cmd, parentPipe, childPipe)
222225
}
223226
return c.newInitProcess(p, cmd, parentPipe, childPipe)
224227
}
@@ -273,23 +276,24 @@ func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, parentPipe, c
273276
}, nil
274277
}
275278

276-
func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe, childPipe *os.File) *setnsProcess {
277-
cmd.Env = append(cmd.Env,
278-
fmt.Sprintf("_LIBCONTAINER_INITPID=%d", c.initProcess.pid()),
279-
"_LIBCONTAINER_INITTYPE=setns",
280-
)
281-
if p.consolePath != "" {
282-
cmd.Env = append(cmd.Env, "_LIBCONTAINER_CONSOLE_PATH="+p.consolePath)
279+
func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe, childPipe *os.File) (*setnsProcess, error) {
280+
cmd.Env = append(cmd.Env, "_LIBCONTAINER_INITTYPE=setns")
281+
// for setns process, we dont have to set cloneflags as the process namespaces
282+
// will only be set via setns syscall
283+
data, err := c.bootstrapData(0, c.initProcess.pid(), p.consolePath)
284+
if err != nil {
285+
return nil, err
283286
}
284287
// TODO: set on container for process management
285288
return &setnsProcess{
286-
cmd: cmd,
287-
cgroupPaths: c.cgroupManager.GetPaths(),
288-
childPipe: childPipe,
289-
parentPipe: parentPipe,
290-
config: c.newInitConfig(p),
291-
process: p,
292-
}
289+
cmd: cmd,
290+
cgroupPaths: c.cgroupManager.GetPaths(),
291+
childPipe: childPipe,
292+
parentPipe: parentPipe,
293+
config: c.newInitConfig(p),
294+
process: p,
295+
bootstrapData: data,
296+
}, nil
293297
}
294298

295299
func (c *linuxContainer) newInitConfig(process *Process) *initConfig {
@@ -1021,3 +1025,25 @@ func (c *linuxContainer) currentState() (*State, error) {
10211025
}
10221026
return state, nil
10231027
}
1028+
1029+
// bootstrapData encodes the necessary data in netlink binary format as a io.Reader.
1030+
// Consumer can write the data to a bootstrap program such as one that uses
1031+
// nsenter package to bootstrap the container's init process correctly, i.e. with
1032+
// correct namespaces, uid/gid mapping etc.
1033+
func (c *linuxContainer) bootstrapData(cloneFlags uintptr, pid int, consolePath string) (io.Reader, error) {
1034+
// create the netlink message
1035+
r := nl.NewNetlinkRequest(int(InitMsg), 0)
1036+
// write pid
1037+
r.AddData(&Int32msg{
1038+
Type: PidAttr,
1039+
Value: uint32(pid),
1040+
})
1041+
// write console path
1042+
if consolePath != "" {
1043+
r.AddData(&Bytemsg{
1044+
Type: ConsolePathAttr,
1045+
Value: []byte(consolePath),
1046+
})
1047+
}
1048+
return bytes.NewReader(r.Serialize()), nil
1049+
}

libcontainer/message_linux.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// +build linux
2+
3+
package libcontainer
4+
5+
import (
6+
"syscall"
7+
8+
"github.com/vishvananda/netlink/nl"
9+
)
10+
11+
// list of known message types we want to send to bootstrap program
12+
// The number is randomly chosen to not conflict with known netlink types
13+
const (
14+
InitMsg uint16 = 62000
15+
PidAttr uint16 = 27281
16+
ConsolePathAttr uint16 = 27282
17+
)
18+
19+
type Int32msg struct {
20+
Type uint16
21+
Value uint32
22+
}
23+
24+
// int32msg has the following representation
25+
// | nlattr len | nlattr type |
26+
// | uint32 value |
27+
func (msg *Int32msg) Serialize() []byte {
28+
buf := make([]byte, msg.Len())
29+
native := nl.NativeEndian()
30+
native.PutUint16(buf[0:2], uint16(msg.Len()))
31+
native.PutUint16(buf[2:4], msg.Type)
32+
native.PutUint32(buf[4:8], msg.Value)
33+
return buf
34+
}
35+
36+
func (msg *Int32msg) Len() int {
37+
return syscall.NLA_HDRLEN + 4
38+
}
39+
40+
// bytemsg has the following representation
41+
// | nlattr len | nlattr type |
42+
// | value | pad |
43+
type Bytemsg struct {
44+
Type uint16
45+
Value []byte
46+
}
47+
48+
func (msg *Bytemsg) Serialize() []byte {
49+
l := msg.Len()
50+
buf := make([]byte, (l+syscall.NLA_ALIGNTO-1) & ^(syscall.NLA_ALIGNTO-1))
51+
native := nl.NativeEndian()
52+
native.PutUint16(buf[0:2], uint16(l))
53+
native.PutUint16(buf[2:4], msg.Type)
54+
copy(buf[4:], msg.Value)
55+
return buf
56+
}
57+
58+
func (msg *Bytemsg) Len() int {
59+
return syscall.NLA_HDRLEN + len(msg.Value) + 1 // null-terminated
60+
}
Lines changed: 71 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
package nsenter
22

33
import (
4+
"bytes"
45
"encoding/json"
5-
"fmt"
6+
"io"
67
"os"
78
"os/exec"
89
"strings"
10+
"syscall"
911
"testing"
12+
13+
"github.com/opencontainers/runc/libcontainer"
14+
"github.com/vishvananda/netlink/nl"
1015
)
1116

1217
type pid struct {
@@ -15,24 +20,30 @@ type pid struct {
1520

1621
func TestNsenterAlivePid(t *testing.T) {
1722
args := []string{"nsenter-exec"}
18-
r, w, err := os.Pipe()
23+
parent, child, err := newPipe()
1924
if err != nil {
2025
t.Fatalf("failed to create pipe %v", err)
2126
}
2227

2328
cmd := &exec.Cmd{
2429
Path: os.Args[0],
2530
Args: args,
26-
ExtraFiles: []*os.File{w},
27-
Env: []string{fmt.Sprintf("_LIBCONTAINER_INITPID=%d", os.Getpid()), "_LIBCONTAINER_INITPIPE=3"},
31+
ExtraFiles: []*os.File{child},
32+
Env: []string{"_LIBCONTAINER_INITTYPE=setns", "_LIBCONTAINER_INITPIPE=3"},
2833
}
2934

3035
if err := cmd.Start(); err != nil {
3136
t.Fatalf("nsenter failed to start %v", err)
3237
}
33-
w.Close()
34-
35-
decoder := json.NewDecoder(r)
38+
r := nl.NewNetlinkRequest(int(libcontainer.InitMsg), 0)
39+
r.AddData(&libcontainer.Int32msg{
40+
Type: libcontainer.PidAttr,
41+
Value: uint32(os.Getpid()),
42+
})
43+
if _, err := io.Copy(parent, bytes.NewReader(r.Serialize())); err != nil {
44+
t.Fatal(err)
45+
}
46+
decoder := json.NewDecoder(parent)
3647
var pid *pid
3748

3849
if err := decoder.Decode(&pid); err != nil {
@@ -51,34 +62,67 @@ func TestNsenterAlivePid(t *testing.T) {
5162

5263
func TestNsenterInvalidPid(t *testing.T) {
5364
args := []string{"nsenter-exec"}
65+
parent, child, err := newPipe()
66+
if err != nil {
67+
t.Fatalf("failed to create pipe %v", err)
68+
}
5469

5570
cmd := &exec.Cmd{
56-
Path: os.Args[0],
57-
Args: args,
58-
Env: []string{"_LIBCONTAINER_INITPID=-1"},
71+
Path: os.Args[0],
72+
Args: args,
73+
ExtraFiles: []*os.File{child},
74+
Env: []string{"_LIBCONTAINER_INITTYPE=setns", "_LIBCONTAINER_INITPIPE=3"},
75+
}
76+
77+
if err := cmd.Start(); err != nil {
78+
t.Fatal("nsenter exits with a zero exit status")
79+
}
80+
r := nl.NewNetlinkRequest(int(libcontainer.InitMsg), 0)
81+
r.AddData(&libcontainer.Int32msg{
82+
Type: libcontainer.PidAttr,
83+
Value: 0,
84+
})
85+
if _, err := io.Copy(parent, bytes.NewReader(r.Serialize())); err != nil {
86+
t.Fatal(err)
5987
}
6088

61-
err := cmd.Run()
62-
if err == nil {
89+
if err := cmd.Wait(); err == nil {
6390
t.Fatal("nsenter exits with a zero exit status")
6491
}
6592
}
6693

6794
func TestNsenterDeadPid(t *testing.T) {
68-
dead_cmd := exec.Command("true")
69-
if err := dead_cmd.Run(); err != nil {
95+
deadCmd := exec.Command("true")
96+
if err := deadCmd.Run(); err != nil {
7097
t.Fatal(err)
7198
}
7299
args := []string{"nsenter-exec"}
100+
parent, child, err := newPipe()
101+
if err != nil {
102+
t.Fatalf("failed to create pipe %v", err)
103+
}
73104

74105
cmd := &exec.Cmd{
75-
Path: os.Args[0],
76-
Args: args,
77-
Env: []string{fmt.Sprintf("_LIBCONTAINER_INITPID=%d", dead_cmd.Process.Pid)},
106+
Path: os.Args[0],
107+
Args: args,
108+
ExtraFiles: []*os.File{child},
109+
Env: []string{"_LIBCONTAINER_INITTYPE=setns", "_LIBCONTAINER_INITPIPE=3"},
78110
}
79111

80-
err := cmd.Run()
81-
if err == nil {
112+
if err := cmd.Start(); err != nil {
113+
t.Fatal("nsenter exits with a zero exit status")
114+
}
115+
116+
r := nl.NewNetlinkRequest(int(libcontainer.InitMsg), 0)
117+
r.AddData(&libcontainer.Int32msg{
118+
Type: libcontainer.PidAttr,
119+
Value: uint32(deadCmd.Process.Pid),
120+
})
121+
if _, err := io.Copy(parent, bytes.NewReader(r.Serialize())); err != nil {
122+
t.Fatal(err)
123+
}
124+
125+
if err := cmd.Wait(); err == nil {
82126
t.Fatal("nsenter exits with a zero exit status")
83127
}
84128
}
@@ -89,3 +133,11 @@ func init() {
89133
}
90134
return
91135
}
136+
137+
func newPipe() (parent *os.File, child *os.File, err error) {
138+
fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0)
139+
if err != nil {
140+
return nil, nil, err
141+
}
142+
return os.NewFile(uintptr(fds[1]), "parent"), os.NewFile(uintptr(fds[0]), "child"), nil
143+
}

0 commit comments

Comments
 (0)