diff options
author | Fabricio Voznika <fvoznika@google.com> | 2019-05-02 17:16:30 -0700 |
---|---|---|
committer | Shentubot <shentubot@google.com> | 2019-05-02 17:17:39 -0700 |
commit | bbb65391143d48a4781e48d0875897a857a69d67 (patch) | |
tree | 7f5786f31ee7efe1cc749d73b6b7547759ebdd4b | |
parent | 2c1c1c9917617ddac0042aa0e7ae14d5032100c5 (diff) |
Add [simple] network support to 'runsc do'
Sandbox always runsc with IP 192.168.10.2 and the peer
network adds 1 to the address (192.168.10.3). Sandbox
IP can be changed using --ip flag.
Here a few examples:
sudo runsc do curl www.google.com
sudo runsc do --ip=10.10.10.2 bash -c "echo 123 | netcat -l -p 8080"
PiperOrigin-RevId: 246421277
Change-Id: I7b3dce4af46a57300350dab41cb27e04e4b6e9da
-rw-r--r-- | runsc/cmd/do.go | 160 | ||||
-rw-r--r-- | runsc/container/container.go | 2 | ||||
-rw-r--r-- | runsc/sandbox/sandbox.go | 2 |
3 files changed, 156 insertions, 8 deletions
diff --git a/runsc/cmd/do.go b/runsc/cmd/do.go index 67d415733..842fe2341 100644 --- a/runsc/cmd/do.go +++ b/runsc/cmd/do.go @@ -21,7 +21,10 @@ import ( "io/ioutil" "math/rand" "os" + "os/exec" "path/filepath" + "strconv" + "strings" "syscall" "flag" @@ -38,6 +41,7 @@ import ( type Do struct { root string cwd string + ip string } // Name implements subcommands.Command.Name. @@ -65,7 +69,8 @@ used for testing only. // SetFlags implements subcommands.Command.SetFlags. func (c *Do) SetFlags(f *flag.FlagSet) { f.StringVar(&c.root, "root", "/", `path to the root directory, defaults to "/"`) - f.StringVar(&c.cwd, "cwd", ".", `path to the current directory, defaults to the current directory`) + f.StringVar(&c.cwd, "cwd", ".", "path to the current directory, defaults to the current directory") + f.StringVar(&c.ip, "ip", "192.168.10.2", "IPv4 address for the sandbox") } // Execute implements subcommands.Command.Execute. @@ -112,6 +117,15 @@ func (c *Do) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) su specutils.LogSpec(spec) + cid := fmt.Sprintf("runsc-%06d", rand.Int31n(1000000)) + if conf.Network != boot.NetworkNone { + clean, err := c.setupNet(cid, spec) + if err != nil { + Fatalf("Error setting up network: %v", err) + } + defer clean() + } + out, err := json.Marshal(spec) if err != nil { Fatalf("Error to marshal spec: %v", err) @@ -130,11 +144,7 @@ func (c *Do) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) su Fatalf("Error write spec: %v", err) } - // No network support yet. - conf.Network = boot.NetworkNone - - id := fmt.Sprintf("runcs-do-%06d", rand.Int31n(1000000)) - ws, err := container.Run(id, spec, conf, tmpDir, "", "", "") + ws, err := container.Run(cid, spec, conf, tmpDir, "", "", "") if err != nil { Fatalf("running container: %v", err) } @@ -155,3 +165,141 @@ func resolvePath(path string) (string, error) { } return path, nil } + +func (c *Do) setupNet(cid string, spec *specs.Spec) (func(), error) { + dev, err := defaultDevice() + if err != nil { + return nil, err + } + peerIP, err := calculatePeerIP(c.ip) + if err != nil { + return nil, err + } + veth, peer := deviceNames(cid) + + cmds := []string{ + fmt.Sprintf("ip link add %s type veth peer name %s", veth, peer), + + // Setup device outside the namespace. + fmt.Sprintf("ip addr add %s/24 dev %s", peerIP, peer), + fmt.Sprintf("ip link set %s up", peer), + + // Setup device inside the namespace. + fmt.Sprintf("ip netns add %s", cid), + fmt.Sprintf("ip link set %s netns %s", veth, cid), + fmt.Sprintf("ip netns exec %s ip addr add %s/24 dev %s", cid, c.ip, veth), + fmt.Sprintf("ip netns exec %s ip link set %s up", cid, veth), + fmt.Sprintf("ip netns exec %s ip link set lo up", cid), + fmt.Sprintf("ip netns exec %s ip route add default via %s", cid, peerIP), + + // Enable network access. + "sysctl -w net.ipv4.ip_forward=1", + fmt.Sprintf("iptables -t nat -A POSTROUTING -s %s -o %s -j MASQUERADE", c.ip, dev), + fmt.Sprintf("iptables -A FORWARD -i %s -o %s -j ACCEPT", dev, peer), + fmt.Sprintf("iptables -A FORWARD -o %s -i %s -j ACCEPT", dev, peer), + } + + for _, cmd := range cmds { + log.Debugf("Run %q", cmd) + args := strings.Split(cmd, " ") + c := exec.Command(args[0], args[1:]...) + if err := c.Run(); err != nil { + return nil, fmt.Errorf("failed to run %q: %v", cmd, err) + } + } + + if err := makeFile("/etc/resolv.conf", "nameserver 8.8.8.8\n", spec); err != nil { + return nil, err + } + if err := makeFile("/etc/hostname", cid+"\n", spec); err != nil { + return nil, err + } + hosts := fmt.Sprintf("127.0.0.1\tlocalhost\n%s\t%s\n", c.ip, cid) + if err := makeFile("/etc/hosts", hosts, spec); err != nil { + return nil, err + } + + if spec.Linux == nil { + spec.Linux = &specs.Linux{} + } + netns := specs.LinuxNamespace{ + Type: specs.NetworkNamespace, + Path: filepath.Join("/var/run/netns", cid), + } + spec.Linux.Namespaces = append(spec.Linux.Namespaces, netns) + + return func() { c.cleanNet(cid, dev) }, nil +} + +func (c *Do) cleanNet(cid, dev string) { + veth, peer := deviceNames(cid) + + cmds := []string{ + fmt.Sprintf("ip link delete %s", peer), + fmt.Sprintf("ip netns delete %s", cid), + + fmt.Sprintf("iptables -t nat -D POSTROUTING -s %s/24 -o %s -j MASQUERADE", c.ip, dev), + fmt.Sprintf("iptables -D FORWARD -i %s -o %s -j ACCEPT", dev, veth), + fmt.Sprintf("iptables -D FORWARD -o %s -i %s -j ACCEPT", dev, veth), + } + + for _, cmd := range cmds { + log.Debugf("Run %q", cmd) + args := strings.Split(cmd, " ") + c := exec.Command(args[0], args[1:]...) + if err := c.Run(); err != nil { + log.Warningf("Failed to run %q: %v", cmd, err) + } + } +} + +func deviceNames(cid string) (string, string) { + // Device name is limited to 15 letters. + return "ve-" + cid, "vp-" + cid + +} + +func defaultDevice() (string, error) { + out, err := exec.Command("ip", "route", "list", "default").CombinedOutput() + if err != nil { + return "", err + } + parts := strings.Split(string(out), " ") + if len(parts) < 5 { + return "", fmt.Errorf("malformed %q output: %q", "ip route list default", string(out)) + } + return parts[4], nil +} + +func makeFile(dest, content string, spec *specs.Spec) error { + tmpFile, err := ioutil.TempFile("", filepath.Base(dest)) + if err != nil { + return err + } + if _, err := tmpFile.WriteString(content); err != nil { + return err + } + spec.Mounts = append(spec.Mounts, specs.Mount{ + Source: tmpFile.Name(), + Destination: dest, + Type: "bind", + Options: []string{"ro"}, + }) + return nil +} + +func calculatePeerIP(ip string) (string, error) { + parts := strings.Split(ip, ".") + if len(parts) != 4 { + return "", fmt.Errorf("invalid IP format %q", ip) + } + n, err := strconv.Atoi(parts[3]) + if err != nil { + return "", fmt.Errorf("invalid IP format %q: %v", ip, err) + } + n++ + if n > 255 { + n = 1 + } + return fmt.Sprintf("%s.%s.%s.%d", parts[0], parts[1], parts[2], n), nil +} diff --git a/runsc/container/container.go b/runsc/container/container.go index 884bbc0fb..3589272f2 100644 --- a/runsc/container/container.go +++ b/runsc/container/container.go @@ -906,7 +906,7 @@ func (c *Container) createGoferProcess(spec *specs.Spec, conf *boot.Config, bund // Start the gofer in the given namespace. log.Debugf("Starting gofer: %s %v", binPath, args) if err := specutils.StartInNS(cmd, nss); err != nil { - return nil, nil, err + return nil, nil, fmt.Errorf("Gofer: %v", err) } log.Infof("Gofer started, PID: %d", cmd.Process.Pid) c.GoferPid = cmd.Process.Pid diff --git a/runsc/sandbox/sandbox.go b/runsc/sandbox/sandbox.go index dac35ca0b..9d8cfa451 100644 --- a/runsc/sandbox/sandbox.go +++ b/runsc/sandbox/sandbox.go @@ -601,7 +601,7 @@ func (s *Sandbox) createSandboxProcess(spec *specs.Spec, conf *boot.Config, bund log.Debugf("Starting sandbox: %s %v", binPath, cmd.Args) log.Debugf("SysProcAttr: %+v", cmd.SysProcAttr) if err := specutils.StartInNS(cmd, nss); err != nil { - return err + return fmt.Errorf("Sandbox: %v", err) } s.child = true s.Pid = cmd.Process.Pid |