mirror of
https://github.com/clearlinux/rkt.git
synced 2026-09-01 11:26:05 +00:00
added proposed network plumbing
This commit is contained in:
@@ -35,3 +35,10 @@ go build -o $GOBIN/rkt ${REPO_PATH}/rkt
|
||||
|
||||
echo "Building metadatasvc..."
|
||||
go build -o $GOBIN/metadatasvc ${REPO_PATH}/metadatasvc
|
||||
|
||||
echo "Building network plugins"
|
||||
for d in network/plugins/*; do
|
||||
plugin=$(basename $d)
|
||||
echo " " $plugin
|
||||
go build -o $GOBIN/$plugin ${REPO_PATH}/$d
|
||||
done
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
package ipam
|
||||
|
||||
// ATTN: This is mostly throw away code. It'll be replaced
|
||||
// by proper ip mgt plugins.
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/rocket/network/util"
|
||||
)
|
||||
|
||||
type options struct {
|
||||
ipRange *net.IPNet
|
||||
ip net.IP
|
||||
}
|
||||
|
||||
func ipAdd(ip net.IP, val uint) net.IP {
|
||||
n := binary.BigEndian.Uint32(ip.To4())
|
||||
n += uint32(val)
|
||||
|
||||
nip := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(nip, n)
|
||||
return net.IP(nip)
|
||||
}
|
||||
|
||||
func allocIP(ipn *net.IPNet) (*net.IPNet, error) {
|
||||
ones, bits := ipn.Mask.Size()
|
||||
zeros := bits - ones
|
||||
rng := (1 << uint(zeros)) - 2 // (reduce for gw, bcast)
|
||||
|
||||
n, err := rand.Int(rand.Reader, big.NewInt(int64(rng)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
offset := uint(n.Uint64() + 1)
|
||||
|
||||
return &net.IPNet{
|
||||
IP: ipAdd(ipn.IP, offset),
|
||||
Mask: ipn.Mask,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func deallocIP(ip net.IP) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func splitArg(arg string) (k, v string) {
|
||||
parts := strings.SplitN(arg, "=", 2)
|
||||
|
||||
switch len(parts) {
|
||||
case 1:
|
||||
k = parts[0]
|
||||
case 2:
|
||||
k, v = parts[0], parts[1]
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func parseArgs(args string) (*options, error) {
|
||||
argv := strings.Split(args, ",")
|
||||
|
||||
var err error
|
||||
opts := &options{}
|
||||
|
||||
for _, arg := range argv {
|
||||
k, v := splitArg(arg)
|
||||
switch k {
|
||||
case "iprange":
|
||||
opts.ipRange, err = util.ParseCIDR(v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse iprange arg (%q): %v", v, err)
|
||||
}
|
||||
|
||||
case "ip":
|
||||
opts.ip = net.ParseIP(v)
|
||||
if opts.ip == nil {
|
||||
return nil, fmt.Errorf("failed to parse ip arg (%q)", v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
func AllocIP(contID, netConf, ifName, args string) (*net.IPNet, net.IP, error) {
|
||||
opts, err := parseArgs(args)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if opts.ipRange != nil {
|
||||
ipn, err := allocIP(opts.ipRange)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error allocating IP in %v: %v", ipn, err)
|
||||
}
|
||||
return ipn, nil, nil
|
||||
}
|
||||
|
||||
n := util.Net{}
|
||||
if err := util.LoadNet(netConf, &n); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
switch n.IPAlloc.Type {
|
||||
case "static":
|
||||
_, ipn, err := net.ParseCIDR(n.IPAlloc.Subnet)
|
||||
if err != nil {
|
||||
// TODO: cleanup
|
||||
return nil, nil, fmt.Errorf("error parsing %q conf: ipAlloc.Subnet: %v")
|
||||
}
|
||||
|
||||
ipn, err = allocIP(ipn)
|
||||
if err != nil {
|
||||
// TODO: cleanup
|
||||
return nil, nil, fmt.Errorf("error allocating IP in %v: %v", ipn, err)
|
||||
}
|
||||
|
||||
return ipn, nil, nil
|
||||
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("unsupported IP allocation type")
|
||||
}
|
||||
}
|
||||
|
||||
func DeallocIP(contID, netConf, ifName string, ipn *net.IPNet) error {
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/rocket/network/util"
|
||||
)
|
||||
|
||||
type NetPlugin struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Endpoint string `json:"endpoint,omitempty"`
|
||||
Command struct {
|
||||
Add []string `json:"add,omitempty"`
|
||||
Del []string `json:"del,omitempty"`
|
||||
}
|
||||
}
|
||||
|
||||
const RktNetPluginsPath = "/etc/rkt-net-plugins.conf.d"
|
||||
|
||||
func LoadNetPlugin(path string) (*NetPlugin, error) {
|
||||
c, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
np := &NetPlugin{}
|
||||
if err = json.Unmarshal(c, np); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return np, nil
|
||||
}
|
||||
|
||||
func LoadNetPlugins() (map[string]*NetPlugin, error) {
|
||||
plugins := make(map[string]*NetPlugin)
|
||||
|
||||
dirents, err := ioutil.ReadDir(RktNetPluginsPath)
|
||||
switch {
|
||||
case err == nil:
|
||||
case os.IsNotExist(err):
|
||||
return plugins, nil
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, dent := range dirents {
|
||||
if dent.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
npPath := filepath.Join(RktNetPluginsPath, dent.Name())
|
||||
np, err := LoadNetPlugin(npPath)
|
||||
if err != nil {
|
||||
log.Printf("Loading %v: %v", npPath, err)
|
||||
continue
|
||||
}
|
||||
|
||||
plugins[np.Name] = np
|
||||
}
|
||||
|
||||
return plugins, nil
|
||||
}
|
||||
|
||||
func (np *NetPlugin) Add(n *Net, contID, netns, args, ifName string) (*net.IPNet, error) {
|
||||
switch {
|
||||
case np.Endpoint != "":
|
||||
return nil, execHTTP(np.Endpoint, "add", n.Name, contID, netns, n.Filename, args, ifName)
|
||||
|
||||
default:
|
||||
if len(np.Command.Add) == 0 {
|
||||
return nil, fmt.Errorf("plugin does not define command.add")
|
||||
}
|
||||
|
||||
output, err := execCmd(np.Command.Add, n.Name, contID, netns, n.Filename, args, ifName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fmt.Printf("plugin's output %q\n", output)
|
||||
|
||||
return util.ParseCIDR(output)
|
||||
}
|
||||
}
|
||||
|
||||
func (np *NetPlugin) Del(n *Net, contID, netns, args, ifName string) error {
|
||||
switch {
|
||||
case np.Endpoint != "":
|
||||
return execHTTP(np.Endpoint, "del", n.Name, contID, netns, n.Filename, args, ifName)
|
||||
|
||||
default:
|
||||
if len(np.Command.Del) == 0 {
|
||||
return fmt.Errorf("plugin does not define command.del")
|
||||
}
|
||||
|
||||
_, err := execCmd(np.Command.Del, n.Name, contID, netns, n.Filename, args, ifName)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func execHTTP(ep, cmd, netName, contID, netns, confFile, args, ifName string) error {
|
||||
return fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func replaceAll(xs []string, what, with string) {
|
||||
for i, x := range xs {
|
||||
xs[i] = strings.Replace(x, what, with, -1)
|
||||
}
|
||||
}
|
||||
|
||||
func execCmd(cmd []string, netName, contID, netns, confFile, args, ifName string) (string, error) {
|
||||
replaceAll(cmd, "{net-name}", netName)
|
||||
replaceAll(cmd, "{cont-id}", contID)
|
||||
replaceAll(cmd, "{netns}", netns)
|
||||
replaceAll(cmd, "{conf-file}", confFile)
|
||||
replaceAll(cmd, "{args}", args)
|
||||
replaceAll(cmd, "{if-name}", ifName)
|
||||
|
||||
stdout := &bytes.Buffer{}
|
||||
|
||||
c := exec.Command(cmd[0], cmd[1:]...)
|
||||
c.Stdout = stdout
|
||||
c.Stderr = os.Stderr
|
||||
if err := c.Run(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return stdout.String(), nil
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/coreos/rocket/network/util"
|
||||
)
|
||||
|
||||
const RktNetPath = "/etc/rkt-net.conf.d"
|
||||
const DefaultIPNet = "172.16.28.0/24"
|
||||
|
||||
type Net struct {
|
||||
util.Net
|
||||
args string
|
||||
}
|
||||
|
||||
var defaultNet Net
|
||||
|
||||
func init() {
|
||||
defaultNet = Net{
|
||||
Net: util.Net{
|
||||
Name: "default",
|
||||
Type: "veth",
|
||||
},
|
||||
args: fmt.Sprintf("default,iprange=%v", DefaultIPNet),
|
||||
}
|
||||
}
|
||||
|
||||
func LoadNets() ([]Net, error) {
|
||||
dirents, err := ioutil.ReadDir(RktNetPath)
|
||||
switch {
|
||||
case err == nil:
|
||||
case os.IsNotExist(err):
|
||||
return nil, nil
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var nets []Net
|
||||
|
||||
for _, dent := range dirents {
|
||||
if dent.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
nf := path.Join(RktNetPath, dent.Name())
|
||||
n := Net{}
|
||||
if err := util.LoadNet(nf, &n); err != nil {
|
||||
log.Printf("Error loading %v: %v", nf, err)
|
||||
continue
|
||||
}
|
||||
|
||||
nets = append(nets, n)
|
||||
}
|
||||
|
||||
nets = append(nets, defaultNet)
|
||||
|
||||
return nets, nil
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/appc/spec/schema/types"
|
||||
"github.com/coreos/rocket/Godeps/_workspace/src/github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/coreos/rocket/network/util"
|
||||
)
|
||||
|
||||
const (
|
||||
ifnamePattern = "eth%d"
|
||||
selfNetNS = "/proc/self/ns/net"
|
||||
)
|
||||
|
||||
type activeNet struct {
|
||||
Net
|
||||
ifName string
|
||||
ipn *net.IPNet
|
||||
}
|
||||
|
||||
type Network struct {
|
||||
MetadataIP net.IP
|
||||
|
||||
contID types.UUID
|
||||
hostNS *os.File
|
||||
contNS *os.File
|
||||
contNSPath string
|
||||
nets []activeNet
|
||||
plugins map[string]*NetPlugin
|
||||
}
|
||||
|
||||
func Setup(contID types.UUID) (*Network, error) {
|
||||
var err error
|
||||
n := Network{contID: contID}
|
||||
|
||||
defer func() {
|
||||
// cleanup on error
|
||||
if err != nil {
|
||||
n.Teardown()
|
||||
}
|
||||
}()
|
||||
|
||||
if n.hostNS, n.contNS, err = basicNetNS(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// we're in contNS!
|
||||
|
||||
contNSPath := filepath.Join("/var/lib/rkt/containers", contID.String(), "ns")
|
||||
if err = bindMountFile(selfNetNS, contNSPath, "net"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
n.contNSPath = filepath.Join(contNSPath, "net")
|
||||
|
||||
n.plugins, err = LoadNetPlugins()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error loading plugin definitions: %v", err)
|
||||
}
|
||||
|
||||
nets, err := LoadNets()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error loading network definitions: %v", err)
|
||||
}
|
||||
|
||||
err = withNetNS(n.contNS, n.hostNS, func() error {
|
||||
n.nets, err = setupNets(contID, n.contNSPath, n.plugins, nets)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// last net is the default
|
||||
n.MetadataIP = n.nets[len(n.nets)-1].ipn.IP
|
||||
|
||||
return &n, nil
|
||||
}
|
||||
|
||||
func (n *Network) Teardown() {
|
||||
// teardown everything in reverse order of setup.
|
||||
// this is called during error case as well so not
|
||||
// everything maybe setup.
|
||||
// N.B. better to keep going in case of errors to get as much
|
||||
// cleaned up as possible
|
||||
|
||||
if n.contNS == nil || n.hostNS == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := n.EnterHostNS(); err != nil {
|
||||
log.Print(err)
|
||||
return
|
||||
}
|
||||
|
||||
teardownNets(n.contID, n.contNSPath, n.plugins, n.nets)
|
||||
|
||||
if n.contNSPath == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if err := syscall.Unmount(n.contNSPath, 0); err != nil {
|
||||
log.Print("Error unmounting %q: %v", n.contNSPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
// sets up new netns with just lo
|
||||
func basicNetNS() (hostNS, contNS *os.File, err error) {
|
||||
hostNS, contNS, err = newNetNS()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to create new netns: %v", err)
|
||||
return
|
||||
}
|
||||
// we're in contNS!!
|
||||
|
||||
if err = loUp(); err != nil {
|
||||
hostNS.Close()
|
||||
contNS.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
func (n *Network) EnterHostNS() error {
|
||||
return util.SetNS(n.hostNS, syscall.CLONE_NEWNET)
|
||||
}
|
||||
|
||||
func (n *Network) EnterContNS() error {
|
||||
return util.SetNS(n.contNS, syscall.CLONE_NEWNET)
|
||||
}
|
||||
|
||||
func setupNets(contID types.UUID, netns string, plugins map[string]*NetPlugin, nets []Net) ([]activeNet, error) {
|
||||
var err error
|
||||
|
||||
active := []activeNet{}
|
||||
|
||||
for i, nt := range nets {
|
||||
plugin, ok := plugins[nt.Type]
|
||||
if !ok {
|
||||
err = fmt.Errorf("could not find network plugin %q\n", nt.Type)
|
||||
break
|
||||
}
|
||||
|
||||
an := activeNet{
|
||||
Net: nt,
|
||||
ifName: fmt.Sprintf(ifnamePattern, i),
|
||||
}
|
||||
|
||||
log.Printf("Executing net-plugin %v", nt.Type)
|
||||
|
||||
an.ipn, err = plugin.Add(&nt, contID.String(), netns, nt.args, an.ifName)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error adding network %q: %v\n", nt.Name, err)
|
||||
break
|
||||
}
|
||||
|
||||
active = append(active, an)
|
||||
}
|
||||
|
||||
log.Print("Done executing net plugins")
|
||||
|
||||
if err != nil {
|
||||
teardownNets(contID, netns, plugins, active)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return active, nil
|
||||
}
|
||||
|
||||
func teardownNets(contID types.UUID, netns string, plugins map[string]*NetPlugin, nets []activeNet) {
|
||||
for i := len(nets) - 1; i >= 0; i-- {
|
||||
nt := nets[i]
|
||||
plugin := plugins[nt.Type]
|
||||
|
||||
err := plugin.Del(&nt.Net, contID.String(), netns, nt.args, nt.ifName)
|
||||
if err != nil {
|
||||
log.Printf("Error deleting %q: %v", nt.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newNetNS() (hostNS, childNS *os.File, err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if hostNS != nil {
|
||||
hostNS.Close()
|
||||
}
|
||||
if childNS != nil {
|
||||
childNS.Close()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
hostNS, err = os.Open(selfNetNS)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = syscall.Unshare(syscall.CLONE_NEWNET); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
childNS, err = os.Open(selfNetNS)
|
||||
if err != nil {
|
||||
util.SetNS(hostNS, syscall.CLONE_NEWNET)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// execute f() in tgtNS
|
||||
func withNetNS(curNS, tgtNS *os.File, f func() error) error {
|
||||
if err := util.SetNS(tgtNS, syscall.CLONE_NEWNET); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := f(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return util.SetNS(curNS, syscall.CLONE_NEWNET)
|
||||
}
|
||||
|
||||
func loUp() error {
|
||||
lo, err := netlink.LinkByName("lo")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to lookup lo: %v", err)
|
||||
}
|
||||
|
||||
if err := netlink.LinkSetUp(lo); err != nil {
|
||||
return fmt.Errorf("failed to set lo up: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func bindMountFile(src, dstDir, dstFile string) error {
|
||||
if err := os.MkdirAll(dstDir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dst := filepath.Join(dstDir, dstFile)
|
||||
|
||||
// mount point has to be an existing file
|
||||
f, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.Close()
|
||||
|
||||
return syscall.Mount(src, dst, "none", syscall.MS_BIND, "")
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/coreos/rocket/Godeps/_workspace/src/github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/coreos/rocket/network/ipam"
|
||||
"github.com/coreos/rocket/network/util"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// this ensures that main runs only on main thread (thread group leader).
|
||||
// since namespace ops (unshare, setns) are done for a single thread, we
|
||||
// must ensure that the goroutine does not jump from OS thread to thread
|
||||
runtime.LockOSThread()
|
||||
}
|
||||
|
||||
func argsHasDefault(args string) bool {
|
||||
argv := strings.Split(args, ",")
|
||||
for _, a := range argv {
|
||||
if a == "default" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func cmdAdd(contID, netns, netConf, ifName, args string) error {
|
||||
var hostVethName string
|
||||
|
||||
ipn, gw, err := ipam.AllocIP(contID, netConf, ifName, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = util.WithNetNSPath(netns, func(hostNS *os.File) error {
|
||||
entropy := contID + ifName
|
||||
|
||||
hostVeth, contVeth, err := util.SetupVeth(entropy, ifName, ipn, hostNS)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if argsHasDefault(args) {
|
||||
if err = util.AddDefaultRoute(gw, contVeth); err != nil {
|
||||
return fmt.Errorf("failed to add default route: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
hostVethName = hostVeth.Attrs().Name
|
||||
return err
|
||||
})
|
||||
|
||||
// hostVeth moved namespaces and will have a new ifindex
|
||||
hostVeth, err := netlink.LinkByName(hostVethName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to lookup %q: %v", hostVeth.Attrs().Name, err)
|
||||
}
|
||||
|
||||
|
||||
// On the host we route traffic for the allocated IP to the container
|
||||
ipn.Mask = net.CIDRMask(32, 32)
|
||||
|
||||
if err = util.AddRoute(ipn, nil, hostVeth); err != nil {
|
||||
return fmt.Errorf("failed to add route on host: %v", err)
|
||||
}
|
||||
|
||||
os.Stdout.Write([]byte(ipn.String()))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func cmdDel(contID, netns, netConf, ifName, args string) error {
|
||||
// switch to the container namespace
|
||||
contNS, err := os.Open(netns)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to open %v: %v", netns, err)
|
||||
}
|
||||
|
||||
if err = util.SetNS(contNS, syscall.CLONE_NEWNET); err != nil {
|
||||
return fmt.Errorf("Error switching to ns %v: %v", netns, err)
|
||||
}
|
||||
|
||||
iface, err := netlink.LinkByName(ifName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to lookup %q: %v", ifName, err)
|
||||
}
|
||||
|
||||
if err = netlink.LinkDel(iface); err != nil {
|
||||
return fmt.Errorf("Failed to delete %q: %v", ifName, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func usage() int {
|
||||
fmt.Fprintln(os.Stderr, "USAGE: add|del CONTAINER-ID NETNS NET-CONF IF-NAME ARGS")
|
||||
return 1
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) != 7 {
|
||||
os.Exit(usage())
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
switch os.Args[1] {
|
||||
case "add":
|
||||
err = cmdAdd(os.Args[2], os.Args[3], os.Args[4], os.Args[5], os.Args[6])
|
||||
|
||||
case "del":
|
||||
err = cmdDel(os.Args[2], os.Args[3], os.Args[4], os.Args[5], os.Args[6])
|
||||
|
||||
default:
|
||||
os.Exit(usage())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
func ParseCIDR(s string) (*net.IPNet, error) {
|
||||
ip, ipn, err := net.ParseCIDR(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ipn.IP = ip
|
||||
return ipn, nil
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type Net struct {
|
||||
Filename string
|
||||
Name string `json:"name,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
IPAlloc struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
Subnet string `json:"subnet,omitempty"`
|
||||
}
|
||||
}
|
||||
|
||||
func LoadNet(path string, n interface{}) error {
|
||||
c, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(c, n); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// populate n.Filename if exists
|
||||
v := reflect.ValueOf(n)
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
if v.Kind() == reflect.Struct {
|
||||
if fn := v.FieldByName("Filename"); fn.IsValid() {
|
||||
if fn.Type().Kind() == reflect.String && fn.CanSet() {
|
||||
fn.SetString(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func saveToTemp(v interface{}) (string, error) {
|
||||
f, err := ioutil.TempFile("", "net")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return f.Name(), json.NewEncoder(f).Encode(v)
|
||||
}
|
||||
|
||||
func TestNet(t *testing.T) {
|
||||
expected := Net{
|
||||
Name: "mynet",
|
||||
Type: "veth",
|
||||
}
|
||||
expected.IPAlloc.Type = "static"
|
||||
expected.IPAlloc.Subnet = "10.1.2.0/24"
|
||||
|
||||
fn, err := saveToTemp(expected)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected.Filename = fn
|
||||
|
||||
actual := Net{}
|
||||
if err = LoadNet(fn, &actual); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if expected.Filename != actual.Filename {
|
||||
t.Errorf("Filename mismatch: expected=%q; actual=%q", expected.Filename, actual.Filename)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Errorf("Mismatch: expected=%#v; actual=%#v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
type MyNet struct {
|
||||
Net
|
||||
}
|
||||
|
||||
func TestNetEmbedded(t *testing.T) {
|
||||
expected := MyNet{
|
||||
Net: Net{
|
||||
Name: "mynet",
|
||||
Type: "veth",
|
||||
},
|
||||
}
|
||||
expected.IPAlloc.Type = "static"
|
||||
expected.IPAlloc.Subnet = "10.1.2.0/24"
|
||||
|
||||
fn, err := saveToTemp(expected)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected.Filename = fn
|
||||
|
||||
actual := MyNet{}
|
||||
if err = LoadNet(fn, &actual); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if expected.Filename != actual.Filename {
|
||||
t.Errorf("Filename mismatch: expected=%q; actual=%q", expected.Filename, actual.Filename)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Errorf("Mismatch: expected=%#v; actual=%#v", expected, actual)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/coreos/rocket/Godeps/_workspace/src/github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
func AddDefaultRoute(gw net.IP, dev netlink.Link) error {
|
||||
_, defNet, _ := net.ParseCIDR("0.0.0.0/0")
|
||||
return AddRoute(defNet, gw, dev)
|
||||
}
|
||||
|
||||
func AddRoute(ipn *net.IPNet, gw net.IP, dev netlink.Link) error {
|
||||
return netlink.RouteAdd(&netlink.Route{
|
||||
LinkIndex: dev.Attrs().Index,
|
||||
Scope: netlink.SCOPE_UNIVERSE,
|
||||
Dst: ipn,
|
||||
Gw: gw,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var setNsMap = map[string]uintptr{
|
||||
"386": 346,
|
||||
"amd64": 308,
|
||||
"arm": 374,
|
||||
}
|
||||
|
||||
func SetNS(f *os.File, flags uintptr) error {
|
||||
if runtime.GOOS != "linux" {
|
||||
return fmt.Errorf("unsupported OS: %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
trap, ok := setNsMap[runtime.GOARCH]
|
||||
if !ok {
|
||||
return fmt.Errorf("unsupported arch: %s", runtime.GOARCH)
|
||||
}
|
||||
_, _, err := syscall.RawSyscall(trap, f.Fd(), flags, 0)
|
||||
if err != 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func WithNetNSPath(nspath string, f func(*os.File) error) error {
|
||||
// save a handle to current (host) network namespace
|
||||
thisNS, err := os.Open("/proc/self/ns/net")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to open /proc/self/ns/net: %v", err)
|
||||
}
|
||||
|
||||
// switch to the container namespace
|
||||
ns, err := os.Open(nspath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to open %v: %v", nspath, err)
|
||||
}
|
||||
|
||||
if err = SetNS(ns, syscall.CLONE_NEWNET); err != nil {
|
||||
return fmt.Errorf("Error switching to ns %v: %v", nspath, err)
|
||||
}
|
||||
|
||||
if err = f(thisNS); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// switch back
|
||||
if err = SetNS(thisNS, syscall.CLONE_NEWNET); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/coreos/rocket/Godeps/_workspace/src/github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
func makeVeth(name, peer string) (netlink.Link, error) {
|
||||
veth := &netlink.Veth{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: name,
|
||||
Flags: net.FlagUp,
|
||||
},
|
||||
PeerName: peer,
|
||||
}
|
||||
if err := netlink.LinkAdd(veth); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return veth, nil
|
||||
}
|
||||
|
||||
func hash(s string) string {
|
||||
h := sha512.New()
|
||||
h.Write([]byte(s))
|
||||
return fmt.Sprintf("%x", h.Sum(nil))
|
||||
}
|
||||
|
||||
// Should be in container netns
|
||||
func SetupVeth(entropy, contVethName string, ipn *net.IPNet, hostNS *os.File) (hostVeth, contVeth netlink.Link, err error) {
|
||||
hostVethName := "rk" + hash(entropy)[:6]
|
||||
hostVeth, err = makeVeth(hostVethName, contVethName)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to make veth pair: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = netlink.LinkSetNsFd(hostVeth, int(hostNS.Fd())); err != nil {
|
||||
err = fmt.Errorf("failed to move veth to root netns: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
contVeth, err = netlink.LinkByName(contVethName)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to lookup %q: %v", contVethName, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = netlink.LinkSetUp(contVeth); err != nil {
|
||||
err = fmt.Errorf("failed to set eth0 up: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if ipn != nil {
|
||||
addr := &netlink.Addr{ipn, ""}
|
||||
if err = netlink.AddrAdd(contVeth, addr); err != nil {
|
||||
err = fmt.Errorf("failed to add IP addr to veth: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user