├── .gitignore ├── example-conf └── 10-slirp.json ├── vendor.conf ├── .travis.yml ├── pid.go ├── Dockerfile ├── integration-test └── test.sh ├── slirp_test.go ├── pid_test.go ├── README.md ├── main.go ├── slirp.go ├── Makefile └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | vendor/ 3 | slirp-cni-plugin 4 | slirp 5 | -------------------------------------------------------------------------------- /example-conf/10-slirp.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "slirp", 3 | "type": "slirp", 4 | "mtu": 1500 5 | } -------------------------------------------------------------------------------- /vendor.conf: -------------------------------------------------------------------------------- 1 | github.com/containernetworking/cni v0.6.0 2 | 3 | # Test dependencies 4 | github.com/stretchr/testify v1.2.2 5 | github.com/davecgh/go-spew v1.1.1 6 | github.com/pmezard/go-difflib v1.0.0 -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | 3 | services: 4 | - docker 5 | 6 | script: 7 | - make check 8 | 9 | deploy: 10 | provider: releases 11 | api_key: 12 | secure: j3ubi4jVqhjJkSaA1z869n45KA78Qj90+V4JJlZ46rGERalDoKLl6guRVQgXX38oc/pKySi54RHZc9zDMwbf9qPm2zCfOkdbGA7gXpCH+S3yrMddPfrbtPnTrwtw4YeQ+5yt8sZngi3pHWukwnV5ZSKqXBlSo7jfRF3PVa5ZQCOeK4GmLaady7iT/20Iuiyqhii0DlH2zFdDBgBq0sJBzKRx/2UlaMa5NZjTwtN37IIZNItm5XrcAWYAvSNw4FFIMeAlfCnRffeMKxQ6urGM0aTtaTHHl00wBIiK7lsPZbiOvQyNFx5FLcGajUsKGH10LWgjSvgwlgImBlWfPhL+4ff03mBgGZYWJC/0kNNMvXtbNFXoImRTKheJjxWhJjcOEQzoaDfgs4USp1d+fl/xJ69fXL4hiUPq/qcw8pXfZh5NL28SWL9noQYQfM/XkdsQ+ElvAvJHzctzpNlc5lFjtFRLySk4/05NuxAG/3gI9eHhgLRWJcAZuis2n2Y8GcgDMt4AxHeRnBKnSXFh6SrUnecrO4EThUznngD2BPvIYkgubxIuglV6i8A5nAdb2rcakjTZ9tfSoXPlL4BKDbti/ir5M8sIg0yQOl9pHbDY2BehATbVsoabzVHEvPT30wxA/nNckbbs6I5ZKakh9pBodozDeGPNFOhn6vTkTMuUp1A= 13 | file: slirp 14 | skip_cleanup: true 15 | on: 16 | repo: mgoltzsche/slirp-cni-plugin 17 | tags: true 18 | -------------------------------------------------------------------------------- /pid.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | "path/filepath" 8 | "strconv" 9 | "syscall" 10 | ) 11 | 12 | func writeSlirpPIDFile(pidFile string, slirpPID int) (err error) { 13 | if err = ioutil.WriteFile(pidFile, []byte(strconv.Itoa(slirpPID)), 0600); err != nil { 14 | return fmt.Errorf("error writing slirp PID file: %v", err) 15 | } 16 | return 17 | } 18 | 19 | func getSlirpPID(pidFile string) (slirpPID int, err error) { 20 | b, err := ioutil.ReadFile(pidFile) 21 | if err != nil { 22 | if os.IsNotExist(err) { 23 | return 0, nil 24 | } 25 | return 26 | } 27 | slirpPID, err = strconv.Atoi(string(b)) 28 | if err != nil || !pidExists(slirpPID) { 29 | return 0, nil 30 | } 31 | return 32 | } 33 | 34 | func deleteSlirpPIDFile(pidFile string) (err error) { 35 | if e := os.Remove(pidFile); e != nil { 36 | if !os.IsNotExist(e) { 37 | err = fmt.Errorf("remove slirp PID file: %v", e) 38 | } 39 | } 40 | return 41 | } 42 | 43 | func slirpPIDFile(containerID, ifName string) string { 44 | return filepath.Join("/dev/shm", "slirp4netns-"+containerID+"-"+ifName) 45 | } 46 | 47 | func pidExists(pid int) bool { 48 | proc, err := os.FindProcess(pid) 49 | if err != nil { 50 | return false 51 | } 52 | err = proc.Signal(syscall.Signal(0)) 53 | return err == nil 54 | } 55 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:alpine3.8 AS build-base 2 | RUN apk add --update --no-cache gcc musl-dev make git 3 | 4 | FROM build-base AS slirp4netns 5 | RUN apk add --update --no-cache git autoconf automake linux-headers 6 | ARG SLIRP4NETNS_VERSION 7 | WORKDIR / 8 | RUN git clone https://github.com/rootless-containers/slirp4netns.git \ 9 | && cd slirp4netns \ 10 | && git checkout $SLIRP4NETNS_VERSION 11 | WORKDIR /slirp4netns 12 | RUN ./autogen.sh \ 13 | && LDFLAGS=-static ./configure --prefix=/usr \ 14 | && make 15 | 16 | FROM build-base AS cnitool 17 | ADD vendor/github.com/containernetworking/cni /work/src/github.com/containernetworking/cni 18 | ARG LDFLAGS="-extldflags '-static'" 19 | ARG CGO_ENABLED=0 20 | ARG GOOS=linux 21 | ENV GOPATH /work 22 | WORKDIR /work 23 | RUN go build -o /cnitool -a -ldflags "${LDFLAGS}" github.com/containernetworking/cni/cnitool \ 24 | && rm -rf /root/.cache/* 25 | 26 | FROM build-base AS liteide 27 | ARG LITEIDE_PKGS="g++ qt5-qttools qt5-qtbase-dev qt5-qtbase-x11 qt5-qtwebkit xkeyboard-config libcanberra-gtk3 adwaita-icon-theme ttf-dejavu" 28 | RUN apk add --update --no-cache ${LITEIDE_PKGS} || /usr/lib/qt5/bin/qmake -help >/dev/null 29 | RUN git clone https://github.com/visualfc/liteide.git \ 30 | && cd liteide/build \ 31 | && ./update_pkg.sh && QTDIR=/usr/lib/qt5 ./build_linux.sh \ 32 | && rm -rf /usr/local/bin \ 33 | && ln -s `pwd`/liteide/bin /usr/local/bin -------------------------------------------------------------------------------- /integration-test/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Must be run in the repository directory. 4 | 5 | export PATH="$(pwd)/build/bin:$PATH" \ 6 | CNI_PATH="$(pwd):$CNI_PATH" \ 7 | NETCONFPATH=$(pwd)/example-conf 8 | 9 | TMPDIR=$(mktemp -d) || exit 1 10 | 11 | cd $TMPDIR && 12 | mkfifo cpipe && 13 | mkfifo hpipe || (rm -rf "$TMPDIR"; false) || exit 1 14 | 15 | # Create user/net/mount namespaces 16 | unshare --user --map-root-user --net --mount sh -c " 17 | timeout -t1 echo '=> netns is ready' > cpipe && 18 | timeout -t3 cat hpipe && 19 | ip addr show eth0 && 20 | wget --spider http://example.org && 21 | timeout -t1 echo '=> connectivity verified' > cpipe && 22 | timeout -t3 cat hpipe && 23 | (! ip addr show eth0) && 24 | timeout -t1 echo '=> iface deletion verified' > cpipe && 25 | timeout -t3 cat hpipe && 26 | wget --spider http://example.org 27 | " & 28 | PID=$! 29 | NETNS=/proc/$PID/ns/net 30 | 31 | # Add interface to netns 32 | timeout -t3 cat cpipe && 33 | cnitool add slirp $NETNS && 34 | echo '=> iface initialized' > hpipe && 35 | timeout -t3 cat cpipe && 36 | 37 | # Remove interface from netns 38 | cnitool del slirp $NETNS && 39 | echo '=> iface deleted' > hpipe && 40 | timeout -t3 cat cpipe && 41 | 42 | # Add interface again 43 | cnitool add slirp $NETNS && 44 | echo '=> iface added again' > hpipe && 45 | wait $PID 46 | STATUS=$? 47 | 48 | # Clean up 49 | cnitool del slirp $NETNS || STATUS=1 50 | rm -rf "$TMPDIR" 51 | 52 | [ $STATUS -eq 0 ] && echo '=> INTEGRATION TEST SUCCEEDED' || '=> INTEGRATION TEST FAILED' 53 | 54 | exit $STATUS -------------------------------------------------------------------------------- /slirp_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "os/exec" 8 | "syscall" 9 | "testing" 10 | "time" 11 | 12 | "github.com/stretchr/testify/assert" 13 | "github.com/stretchr/testify/require" 14 | ) 15 | 16 | func TestStopSlirp(t *testing.T) { 17 | for _, c := range [][]string{ 18 | // Should terminate process gracefully 19 | {"/bin/sleep", "60"}, 20 | // Should kill process after termination timeout 21 | {"/bin/sh", "-c", "sig() { echo signal received but refusing to terminate; sleep 60; }; trap sig 2 3 15; sleep 60 & wait"}, 22 | } { 23 | fmt.Printf("Process: %+v\n", c) 24 | cmd := exec.Command(c[0], c[1:]...) 25 | cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} 26 | stdout, err := cmd.StdoutPipe() 27 | require.NoError(t, err) 28 | stderr, err := cmd.StderrPipe() 29 | require.NoError(t, err) 30 | go func() { 31 | io.Copy(os.Stdout, stdout) 32 | }() 33 | go func() { 34 | io.Copy(os.Stderr, stderr) 35 | }() 36 | err = cmd.Start() 37 | require.NoError(t, err) 38 | pid := cmd.Process.Pid 39 | defer syscall.Kill(pid, syscall.SIGKILL) 40 | go func() { 41 | cmd.Wait() 42 | }() 43 | time.Sleep(time.Duration(300 * time.Millisecond)) 44 | startTime := time.Now() 45 | stopCh := make(chan error) 46 | go func() { 47 | stopCh <- stopSlirp(pid) 48 | }() 49 | select { 50 | case err = <-stopCh: 51 | require.NoError(t, err, "stopSlirp()") 52 | fmt.Println("stopSlirp() returned after", time.Since(startTime).String()) 53 | err = syscall.Kill(pid, syscall.Signal(0)) 54 | assert.True(t, err == syscall.ESRCH, "process has not been terminated") 55 | case <-time.After(time.Duration(7 * time.Second)): 56 | t.Errorf("timed out waiting for stopSlirp() to return") 57 | t.FailNow() 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /pid_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "strconv" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestSlirpPIDFile(t *testing.T) { 14 | assert.Equal(t, "/dev/shm/slirp4netns-cid-tap0", slirpPIDFile("cid", "tap0")) 15 | } 16 | 17 | func TestPidExists(t *testing.T) { 18 | for _, pid := range []int{32768, -1} { 19 | assert.False(t, pidExists(pid), "pidExists(%d) returned true for non-existing PID", pid) 20 | } 21 | assert.True(t, pidExists(os.Getpid()), "pidExists(ownPID) returned false for existing PID") 22 | } 23 | 24 | func TestWriteSlirpPIDFile(t *testing.T) { 25 | pidFile := "/dev/shm/slirptest-cid-tap0" 26 | defer os.Remove(pidFile) 27 | for _, pid := range []int{123, 12345} { 28 | err := writeSlirpPIDFile(pidFile, pid) 29 | require.NoError(t, err) 30 | b, err := ioutil.ReadFile(pidFile) 31 | require.NoError(t, err) 32 | assert.Equal(t, strconv.Itoa(pid), string(b)) 33 | } 34 | } 35 | 36 | func TestDeleteSlirpPIDFile(t *testing.T) { 37 | pidFile := "/dev/shm/slirptest-cid-tap0" 38 | defer os.Remove(pidFile) 39 | err := ioutil.WriteFile(pidFile, []byte("123"), 0644) 40 | require.NoError(t, err, "create") 41 | 42 | for range [2]int{} { 43 | err = deleteSlirpPIDFile(pidFile) 44 | require.NoError(t, err, "delete") 45 | _, err = os.Stat(pidFile) 46 | if !assert.True(t, os.IsNotExist(err), "pid file %s should not exist but stat returned error: %v", pidFile, err) { 47 | t.FailNow() 48 | } 49 | } 50 | } 51 | 52 | func TestGetSlirpPID(t *testing.T) { 53 | // Write mocked PID files 54 | currPID := strconv.Itoa(os.Getpid()) 55 | for _, content := range []string{currPID, "32768", "-1", "Invalid", ""} { 56 | pidFile := "/dev/shm/slirptest-cid" + content + "-tap0" 57 | err := ioutil.WriteFile(pidFile, []byte(content), 0644) 58 | require.NoError(t, err) 59 | defer os.Remove(pidFile) 60 | } 61 | 62 | // Assert PID file lookup returns valid PID 63 | currPIDFile := "/dev/shm/slirptest-cid" + currPID + "-tap0" 64 | pid, err := getSlirpPID(currPIDFile) 65 | require.NoError(t, err, "get current PID from file") 66 | assert.Equal(t, os.Getpid(), pid, "current PID") 67 | 68 | // Assert non-existing/invalid pid file returns 0 69 | // to be able to continue operations after processes have been killed 70 | // (this pid file does not serve as a lock) 71 | for _, cid := range []string{"nonExisting", "cidInvalid", ""} { 72 | pidFile := "/dev/shm/slirptest-" + cid + "-tap0" 73 | pid, err := getSlirpPID(pidFile) 74 | require.NoError(t, err, "lookup non-existing/invalid PID file of %s", cid) 75 | assert.Equal(t, 0, pid, "lookup of non-existing/invalid PID file should return 0") 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | slirp-cni-plugin [![Build Status](https://travis-ci.org/mgoltzsche/slirp-cni-plugin.svg?branch=master)](https://travis-ci.org/mgoltzsche/slirp-cni-plugin) 2 | = 3 | 4 | A [CNI](https://github.com/containernetworking/cni) plugin that provides 5 | container networking for unprivileged users ("slirp") using [slirp4netns](https://github.com/rootless-containers/slirp4netns). 6 | 7 | 8 | ## Build 9 | Build the plugin using make and docker: 10 | ``` 11 | git clone https://github.com/mgoltzsche/slirp-cni-plugin.git 12 | cd slirp-cni-plugin 13 | make slirp 14 | ``` 15 | 16 | In order to run the examples below you can also build the dependencies 17 | [slirp4netns](https://github.com/rootless-containers/slirp4netns) and 18 | [cnitool](https://github.com/containernetworking/cni/tree/master/cnitool) (written to `build/bin`): 19 | ``` 20 | make slirp4netns cnitool 21 | ``` 22 | 23 | 24 | ## Plugin configuration 25 | 26 | ### JSON configuration file 27 | An example configuration file can be found [here](example-conf/10-slirp.json). 28 | 29 | | Field | Default | Description | 30 | | ------ | ------- | ----------- | 31 | | `name` | | The network/configuration file's name | 32 | | `type` | | Name used to lookup the plugin binary _(must be `slirp` to make the CNI runtime use this plugin)_ | 33 | | `mtu` | `1500` | Maximum Transmission Unit _(1499 < MTU < 65522)_ | 34 | 35 | Nothing but the MTU can be configured since slirp4netns provides sufficient 36 | [defaults](https://github.com/rootless-containers/slirp4netns/blob/master/slirp4netns.1.md#description). 37 | Thus the `ipam` CNI plugin configuration is also not supported. 38 | 39 | ### Environment variables 40 | To make the plugin use a specific [slirp4netns](https://github.com/rootless-containers/slirp4netns) 41 | binary set the `SLIRP4NETNS` environment variable. 42 | Otherwise the plugin will lookup slirp4netns in the `PATH`. 43 | 44 | 45 | ## Usage 46 | This example shows how to create namespaces and add a [slirp network](example-conf/10-slirp.json) 47 | using [cnitool](https://github.com/containernetworking/cni/tree/master/cnitool). 48 | Please note that the [slirp4netns](https://github.com/rootless-containers/slirp4netns) 49 | binary must be in the `PATH` or specified in the `SLIRP4NETNS` environment variable. 50 | 51 | Terminal 1: Create user, network and mount namespaces: 52 | ``` 53 | $ unshare --user --map-root-user --net --mount 54 | unshared$ echo $$ > /tmp/pid 55 | ``` 56 | 57 | Terminal 2: Add network interface: 58 | ``` 59 | $ export PATH="$(pwd)/build/bin:$PATH" \ 60 | CNI_PATH="$(pwd):$CNI_PATH" \ 61 | NETCONFPATH=$(pwd)/example-conf 62 | $ cnitool add slirp "/proc/$(cat /tmp/pid)/ns/net" 63 | ``` 64 | 65 | Terminal 1: test connectivity: 66 | ``` 67 | unshared$ ip a 68 | 1: lo: mtu 65536 qdisc noop state DOWN group default qlen 1 69 | link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 70 | 2: eth0: mtu 1500 qdisc pfifo_fast state UNKNOWN group default qlen 1000 71 | link/ether 12:72:1b:c0:e0:0e brd ff:ff:ff:ff:ff:ff 72 | inet 10.0.2.100/24 brd 10.0.2.255 scope global tap0 73 | valid_lft forever preferred_lft forever 74 | inet6 fe80::1072:1bff:fec0:e00e/64 scope link 75 | valid_lft forever preferred_lft forever 76 | unshared$ curl http://example.org 77 | 78 | ... 79 | ``` 80 | 81 | Terminal 2: remove slirp network from the namespace after you're done: 82 | ``` 83 | $ cnitool del slirp "/proc/$(cat /tmp/pid)/ns/net" 84 | ``` -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net" 7 | "regexp" 8 | "strconv" 9 | 10 | "github.com/containernetworking/cni/pkg/skel" 11 | "github.com/containernetworking/cni/pkg/types" 12 | "github.com/containernetworking/cni/pkg/types/current" 13 | "github.com/containernetworking/cni/pkg/version" 14 | ) 15 | 16 | type netConf struct { 17 | types.NetConf 18 | MTU int 19 | } 20 | 21 | func cmdAdd(args *skel.CmdArgs) (err error) { 22 | defer func() { 23 | if err != nil { 24 | err = fmt.Errorf("add slirp %s: %v", args.IfName, err) 25 | } 26 | }() 27 | 28 | conf := netConf{} 29 | if err := json.Unmarshal(args.StdinData, &conf); err != nil { 30 | return fmt.Errorf("failed to load netconf: %v", err) 31 | } 32 | 33 | if conf.IPAM.Type != "" { 34 | return fmt.Errorf("ipam plugin not supported within slirp") 35 | } 36 | 37 | if conf.MTU <= 0 { 38 | conf.MTU = 1500 // default 39 | } 40 | if conf.MTU > 65521 || conf.MTU < 1500 { 41 | return fmt.Errorf("invalid MTU value %d configured. 1500 <= MTU <= 65521", conf.MTU) 42 | } 43 | 44 | containerPID, err := pidFromNetns(args.Netns) 45 | if err != nil { 46 | return 47 | } 48 | 49 | pidFile := slirpPIDFile(args.ContainerID, args.IfName) 50 | slirpPID, err := getSlirpPID(pidFile) 51 | if err != nil { 52 | return 53 | } 54 | if slirpPID > 0 { 55 | return fmt.Errorf("a slirp4netns process (%d) is already running for netns (containerID: %s, PID: %d)", slirpPID, args.ContainerID, containerPID) 56 | } 57 | 58 | slirpPID, err = startSlirp(containerPID, args.IfName, conf.MTU) 59 | if err != nil { 60 | return 61 | } 62 | if err = writeSlirpPIDFile(pidFile, slirpPID); err != nil { 63 | stopSlirp(slirpPID) 64 | return 65 | } 66 | 67 | // See https://github.com/rootless-containers/slirp4netns/blob/master/slirp4netns.1.md#description 68 | r := current.Result{ 69 | CNIVersion: current.ImplementedSpecVersion, 70 | Interfaces: []*current.Interface{{ 71 | Name: args.IfName, 72 | Sandbox: args.Netns, 73 | }}, 74 | IPs: []*current.IPConfig{{ 75 | Version: "4", 76 | Interface: current.Int(0), 77 | Address: net.IPNet{ 78 | IP: net.ParseIP("10.0.2.100"), 79 | Mask: net.IPv4Mask(255, 255, 255, 0), 80 | }, 81 | Gateway: net.ParseIP("10.0.2.2"), 82 | }}, 83 | Routes: []*types.Route{{ 84 | Dst: net.IPNet{ 85 | IP: net.ParseIP("0.0.0.0"), 86 | Mask: net.IPv4Mask(0, 0, 0, 0), 87 | }, 88 | GW: net.ParseIP("10.0.2.2"), 89 | }}, 90 | DNS: conf.DNS, 91 | } 92 | r.DNS.Nameservers = append(r.DNS.Nameservers, "10.0.2.3") 93 | 94 | if err = types.PrintResult(&r, conf.CNIVersion); err != nil { 95 | stopSlirp(slirpPID) 96 | return fmt.Errorf("error printing slirp result") 97 | } 98 | return 99 | } 100 | 101 | func cmdDel(args *skel.CmdArgs) (err error) { 102 | defer func() { 103 | if err != nil { 104 | err = fmt.Errorf("del slirp %s: %v", args.IfName, err) 105 | } 106 | }() 107 | 108 | conf := netConf{} 109 | if err = json.Unmarshal(args.StdinData, &conf); err != nil { 110 | return fmt.Errorf("failed to load netconf: %v", err) 111 | } 112 | pidFile := slirpPIDFile(args.ContainerID, args.IfName) 113 | slirpPID, err := getSlirpPID(pidFile) 114 | if err != nil || slirpPID == 0 { 115 | return 116 | } 117 | if err = stopSlirp(slirpPID); err != nil { 118 | return 119 | } 120 | return deleteSlirpPIDFile(pidFile) 121 | } 122 | 123 | func pidFromNetns(netnsPath string) (pid int, err error) { 124 | regex := regexp.MustCompile("^/proc/([0-9]+)/ns/net$") 125 | matches := regex.FindStringSubmatch(netnsPath) 126 | if len(matches) != 2 { 127 | return 0, fmt.Errorf("get pid: unsupported netns path provided: %q", netnsPath) 128 | } 129 | return strconv.Atoi(matches[1]) 130 | } 131 | 132 | func main() { 133 | skel.PluginMain(cmdAdd, cmdDel, version.All) 134 | } 135 | -------------------------------------------------------------------------------- /slirp.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "os/exec" 8 | "strconv" 9 | "syscall" 10 | "time" 11 | ) 12 | 13 | func startSlirp(containerPID int, ifName string, mtu int) (slirpPID int, err error) { 14 | slirp4netns, err := findSlirp4netnsBinary() 15 | if err != nil { 16 | return 17 | } 18 | 19 | readyPipeR, readyPipeW, err := os.Pipe() 20 | if err != nil { 21 | return 0, fmt.Errorf("failed to create pipe for ready message: %v", err) 22 | } 23 | defer func() { 24 | readyPipeR.Close() 25 | readyPipeW.Close() 26 | }() 27 | 28 | cmd := &exec.Cmd{ 29 | Path: slirp4netns, 30 | Args: append([]string{"slirp4netns", "-c", "-m", strconv.Itoa(mtu), "-r", "3", strconv.Itoa(containerPID), ifName}), 31 | ExtraFiles: []*os.File{readyPipeW}, 32 | } 33 | if err = redirectOutput(cmd); err != nil { 34 | return 35 | } 36 | if err = cmd.Start(); err != nil { 37 | return 0, fmt.Errorf("failed to start slirp4netns: %v", err) 38 | } 39 | slirpPID = cmd.Process.Pid 40 | if err = awaitSlirp4netnsReady(readyPipeR); err != nil { 41 | stopSlirp(slirpPID) 42 | return 0, err 43 | } 44 | if err = cmd.Process.Release(); err != nil { 45 | return 0, fmt.Errorf("failed to release slirp4netns: %v", err) 46 | } 47 | return 48 | } 49 | 50 | func stopSlirp(slirpPID int) (err error) { 51 | if err = signalExist(slirpPID, syscall.SIGTERM); err != nil { 52 | return fmt.Errorf("failed to terminate slirp4netns: %v", err) 53 | } 54 | if err = awaitSlirp4netnsTermination(slirpPID, time.Duration(5*time.Second)); err != nil { 55 | if err = signalExist(slirpPID, syscall.SIGKILL); err != nil { 56 | return fmt.Errorf("failed to kill slirp4netns: %v", err) 57 | } 58 | } 59 | return awaitSlirp4netnsTermination(slirpPID, time.Duration(1*time.Second)) 60 | } 61 | 62 | func signalExist(pid int, s syscall.Signal) (err error) { 63 | if err = syscall.Kill(pid, s); err == syscall.ESRCH { 64 | err = nil // process does not exist (anymore) 65 | } 66 | return 67 | } 68 | 69 | func awaitSlirp4netnsReady(readyPipeR *os.File) (err error) { 70 | if err = readyPipeR.SetReadDeadline(time.Now().Add(time.Duration(5 * time.Second))); err != nil { 71 | return fmt.Errorf("setting slirp4netns ready pipe read timeout: %v", err) 72 | } 73 | read := make([]byte, 1) 74 | if _, err = readyPipeR.Read(read); err != nil { 75 | return fmt.Errorf("await ready message from slirp4netns: %v", err) 76 | } 77 | if string(read) != "1" { 78 | return fmt.Errorf("unexpected message from slirp4netns: %q, expected 1", string(read)) 79 | } 80 | return 81 | } 82 | 83 | func redirectOutput(cmd *exec.Cmd) error { 84 | stdout, err := cmd.StdoutPipe() 85 | if err != nil { 86 | return fmt.Errorf("failed to create slirp4netns stdout pipe: %v", err) 87 | } 88 | stderr, err := cmd.StderrPipe() 89 | if err != nil { 90 | return fmt.Errorf("failed to create slirp4netns stderr pipe: %v", err) 91 | } 92 | go func() { 93 | io.Copy(os.Stderr, stderr) 94 | }() 95 | go func() { 96 | io.Copy(os.Stderr, stdout) 97 | }() 98 | return nil 99 | } 100 | 101 | func findSlirp4netnsBinary() (binary string, err error) { 102 | binary, ok := os.LookupEnv("SLIRP4NETNS") 103 | if ok { 104 | return 105 | } 106 | if binary, err = exec.LookPath("slirp4netns"); err != nil { 107 | err = fmt.Errorf("cannot find slirp4netns in path (hint: specify SLIRP4NETNS env var or add slirp4netns to PATH)") 108 | } 109 | return 110 | } 111 | 112 | func awaitSlirp4netnsTermination(pid int, timeout time.Duration) error { 113 | if !pidExists(pid) { 114 | return nil 115 | } 116 | checkInterval := time.Duration(30 * time.Millisecond) 117 | for i := 0; i < int(timeout/checkInterval); i++ { 118 | time.Sleep(checkInterval) 119 | if !pidExists(pid) { 120 | return nil 121 | } 122 | } 123 | return fmt.Errorf("slirp4netns did not terminate (timeout)") 124 | } 125 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SLIRP4NETNS_VERSION?=e5c7f16a3f866b2def1cc4d20587553342f6f6eb 2 | CNI_VERSION?=0.6.0 3 | BUILDTAGS?=linux static_build 4 | LDFLAGS?=-extldflags '-static' 5 | CGO_ENABLED?=0 6 | GOOS?=linux 7 | 8 | BUILDIMAGE=local/slirp-cni-plugin-build:latest 9 | LITEIDEIMAGE=local/slirp-cni-plugin-build:liteide 10 | DOCKER=docker 11 | USER=$(shell [ '${DOCKER}' = docker ] && id -u || echo 0) 12 | DOCKERRUN=${DOCKER} run --name slirp-cni-plugin-build --rm -e CGO_ENABLED=${CGO_ENABLED} -e GOOS=${GOOS} -e GOPATH=/work/build -v "${REPODIR}:/work" -w /work 13 | 14 | REPODIR=$(shell pwd) 15 | GOPATH=${REPODIR}/build 16 | CNIGOPATH=${GOPATH}/cni 17 | LITEIDE_WORKSPACE=${GOPATH}/liteide-workspace 18 | PKGNAME=github.com/mgoltzsche/slirp-cni-plugin 19 | PKGRELATIVEROOT=$(shell echo /src/${PKGNAME} | sed -E 's/\/+[^\/]*/..\//g') 20 | 21 | all: slirp 22 | 23 | slirp: .buildimage 24 | ${DOCKERRUN} -u ${USER}:${USER} ${BUILDIMAGE} make .slirp 25 | 26 | .slirp: vendor 27 | # Building slirp plugin: 28 | export GOPATH="${GOPATH}" && \ 29 | go build -o slirp -a -ldflags "${LDFLAGS}" -tags "${BUILDTAGS}" "${PKGNAME}" 30 | 31 | test: vendor 32 | # Run unit tests: 33 | export GOPATH="${GOPATH}" && \ 34 | go test ${PKGNAME} 35 | 36 | test-integration: cnitool slirp4netns 37 | # Run integration test: 38 | ${DOCKERRUN} --privileged -u ${USER}:${USER} ${BUILDIMAGE} ./integration-test/test.sh 39 | 40 | lint: .workspace 41 | export GOPATH="${GOPATH}"; \ 42 | go get golang.org/x/lint/golint && \ 43 | "${GOPATH}/bin/golint" ${PKGNAME} 44 | 45 | fmt: 46 | # Format the go code 47 | (find . -mindepth 1 -maxdepth 1 -type d; ls *.go) | grep -Ev '^(./vendor|./build|./.git)(/.*)?$$' | xargs -n1 gofmt -w 48 | 49 | validate: 50 | export GOPATH="${GOPATH}"; \ 51 | go get github.com/vbatts/git-validation 52 | "${GOPATH}/bin/git-validation" -run DCO,short-subject 53 | 54 | check: .buildimage 55 | ${DOCKERRUN} -u ${USER}:${USER} ${BUILDIMAGE} make validate lint test 56 | make slirp test-integration 57 | # Test slirp binary 58 | ./slirp || ([ $$? -eq 1 ] && echo slirp binary exists and is runnable) 59 | 60 | slirp4netns: 61 | docker image build --force-rm -f Dockerfile --build-arg SLIRP4NETNS_VERSION=${SLIRP4NETNS_VERSION} --target slirp4netns --tag local/slirp4netns . 62 | id=$$(docker create local/slirp4netns) && \ 63 | docker cp $$id:/slirp4netns/slirp4netns build/bin/slirp4netns && \ 64 | docker rm -v $$id 65 | 66 | cnitool: vendor 67 | docker image build --force-rm -f Dockerfile --build-arg SLIRP4NETNS_VERSION=${SLIRP4NETNS_VERSION} --target cnitool --tag local/cnitool . 68 | id=$$(docker create local/cnitool) && \ 69 | docker cp $$id:/cnitool build/bin/cnitool && \ 70 | docker rm -v $$id 71 | 72 | .buildimage: 73 | # Building build image: 74 | ${DOCKER} build --force-rm -f Dockerfile --target build-base -t ${BUILDIMAGE} . 75 | 76 | build-sh: .buildimage 77 | # Running dockerized interactive build shell 78 | ${DOCKERRUN} -ti ${BUILDIMAGE} /bin/sh 79 | 80 | vendor: .workspace 81 | ifeq ($(shell [ ! -d vendor -o "${UPDATE_VENDOR}" = TRUE ] && echo 0),0) 82 | # Fetching dependencies: 83 | GOPATH="${GOPATH}" go get github.com/LK4D4/vndr 84 | rm -rf "${GOPATH}/vndrtmp" 85 | mkdir "${GOPATH}/vndrtmp" 86 | ln -sf "${REPODIR}/vendor.conf" "${GOPATH}/vndrtmp/vendor.conf" 87 | (cd build/vndrtmp && "${GOPATH}/bin/vndr" -whitelist='.*') 88 | rm -rf vendor 89 | mv "${GOPATH}/vndrtmp/vendor" vendor 90 | else 91 | # Skipping vendor update 92 | endif 93 | 94 | vendor-update: 95 | # Update vendor directory 96 | @make vendor UPDATE_VENDOR=TRUE 97 | # In case LiteIDE is running it must be restarted to apply the changes 98 | 99 | .workspace: 100 | # Preparing build directory: 101 | [ -d "${GOPATH}/src/${PKGNAME}" ] || \ 102 | (mkdir -p vendor "$(shell dirname "${GOPATH}/src/${PKGNAME}")" \ 103 | && ln -sf "${PKGRELATIVEROOT}" "${GOPATH}/src/${PKGNAME}") 104 | 105 | liteide: vendor 106 | rm -rf "${LITEIDE_WORKSPACE}" 107 | mkdir "${LITEIDE_WORKSPACE}" 108 | cp -r vendor "${LITEIDE_WORKSPACE}/src" 109 | mkdir -p "${LITEIDE_WORKSPACE}/src/${PKGNAME}" 110 | ln -sr "${REPODIR}"/* "${LITEIDE_WORKSPACE}/src/${PKGNAME}" 111 | (cd "${LITEIDE_WORKSPACE}/src/${PKGNAME}" && rm build vendor dist) 112 | GOPATH="${LITEIDE_WORKSPACE}" \ 113 | BUILDFLAGS="-tags \"${BUILDTAGS}\"" \ 114 | liteide "${LITEIDE_WORKSPACE}/src/${PKGNAME}" & 115 | ################################################################ 116 | # Setup LiteIDE project using the main package's context menu: # 117 | # - 'Build Path Configuration': # 118 | # - Make sure 'Inherit System GOPATH' is checked! # 119 | # - Configure BUILDFLAGS variable printed above # 120 | # - 'Lock Build Path' to the top-level directory # 121 | # # 122 | # CREATE NEW TOP LEVEL PACKAGES IN THE REPOSITORY DIRECTORY # 123 | # EXTERNALLY AND RESTART LiteIDE WITH THIS COMMAND! # 124 | ################################################################ 125 | 126 | ide: .liteideimage 127 | # Make sure to lock the build path to the top-level directory 128 | ctnr bundle create -b slirp-liteide --update=true -w /work \ 129 | --mount "src=${REPODIR},dst=/work/src/github.com/mgoltzsche/slirp-cni-plugin" \ 130 | --mount src=/etc/machine-id,dst=/etc/machine-id,opt=ro \ 131 | --mount src=/tmp/.X11-unix,dst=/tmp/.X11-unix \ 132 | --env DISPLAY=$$DISPLAY \ 133 | --env GOPATH=/work \ 134 | ${LITEIDEIMAGE} \ 135 | liteide /work/src/github.com/mgoltzsche/slirp-cni-plugin 136 | ctnr bundle run --verbose slirp-liteide & 137 | 138 | .liteideimage: 139 | ctnr image build --dockerfile=Dockerfile --target=liteide --tag=${LITEIDEIMAGE} 140 | 141 | clean: 142 | rm -rf ./build ./slirp ./slirp-cni-plugin 143 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------