Files
rkt/networking/networking.go
Eugene Yakubovich 2440b4a54d Merge pull request #960 from steveeJ/private-net-selection2
networking: allow network selection with private-net (2nd impl.)
2015-05-29 17:42:47 -07:00

293 lines
6.6 KiB
Go

// Copyright 2015 The rkt Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package networking
import (
"fmt"
"log"
"net"
"os"
"syscall"
"github.com/coreos/rkt/Godeps/_workspace/src/github.com/appc/cni/pkg/ns"
"github.com/coreos/rkt/Godeps/_workspace/src/github.com/appc/spec/schema/types"
"github.com/coreos/rkt/Godeps/_workspace/src/github.com/vishvananda/netlink"
"github.com/coreos/rkt/common"
"github.com/coreos/rkt/networking/netinfo"
)
const (
ifnamePattern = "eth%d"
selfNetNS = "/proc/self/ns/net"
)
// ForwardedPort describes a port that will be
// forwarded (mapped) from the host to the pod
type ForwardedPort struct {
Protocol string
HostPort uint
PodPort uint
}
// Networking describes the networking details of a pod.
type Networking struct {
podEnv
hostNS *os.File
nets []activeNet
}
// Setup creates a new networking namespace and executes network plugins to
// setup private networking. It returns in the new pod namespace
func Setup(podRoot string, podID types.UUID, fps []ForwardedPort, privateNetList common.PrivateNetList) (*Networking, error) {
// TODO(jonboulle): currently podRoot is _always_ ".", and behaviour in other
// circumstances is untested. This should be cleaned up.
n := Networking{
podEnv: podEnv{
podRoot: podRoot,
podID: podID,
netsLoadList: privateNetList,
},
}
hostNS, podNS, err := basicNetNS()
if err != nil {
return nil, err
}
// we're in podNS!
n.hostNS = hostNS
nspath := n.podNSPath()
if err = bindMountFile(selfNetNS, nspath); err != nil {
return nil, err
}
defer func() {
if err != nil {
if err := syscall.Unmount(nspath, 0); err != nil {
log.Printf("Error unmounting %q: %v", nspath, err)
}
}
}()
n.nets, err = n.loadNets()
if err != nil {
return nil, fmt.Errorf("error loading network definitions: %v", err)
}
err = withNetNS(podNS, hostNS, func() error {
if err := n.setupNets(n.nets); err != nil {
return err
}
return n.forwardPorts(fps, n.GetDefaultIP())
})
if err != nil {
return nil, err
}
return &n, nil
}
// Load creates the Networking object from saved state.
// Assumes the current netns is that of the host.
func Load(podRoot string, podID *types.UUID) (*Networking, error) {
// the current directory is pod root
pdirfd, err := syscall.Open(podRoot, syscall.O_RDONLY|syscall.O_DIRECTORY, 0)
if err != nil {
return nil, fmt.Errorf("Failed to open pod root directory (%v): %v", podRoot, err)
}
defer syscall.Close(pdirfd)
nis, err := netinfo.LoadAt(pdirfd)
if err != nil {
return nil, err
}
hostNS, err := os.Open(selfNetNS)
if err != nil {
return nil, err
}
nets := []activeNet{}
for _, ni := range nis {
n, err := loadNet(ni.ConfPath)
if err != nil {
if !os.IsNotExist(err) {
log.Printf("Error loading %q: %v; ignoring", ni.ConfPath, err)
}
continue
}
// make a copy of ni to make it a unique object as it's saved via ptr
rti := ni
n.runtime = &rti
nets = append(nets, *n)
}
return &Networking{
podEnv: podEnv{
podRoot: podRoot,
podID: *podID,
},
hostNS: hostNS,
nets: nets,
}, nil
}
func (n *Networking) GetDefaultIP() net.IP {
if len(n.nets) == 0 {
return nil
}
return n.nets[len(n.nets)-1].runtime.IP
}
func (n *Networking) GetDefaultHostIP() (net.IP, error) {
if len(n.nets) == 0 {
return nil, fmt.Errorf("no networks found")
}
return n.nets[len(n.nets)-1].runtime.HostIP, nil
}
// Teardown cleans up a produced Networking object.
func (n *Networking) Teardown() {
// Teardown everything in reverse order of setup.
// This should be idempotent -- be tolerant of missing stuff
if err := n.enterHostNS(); err != nil {
log.Printf("Error switching to host netns: %v", err)
return
}
if err := n.unforwardPorts(); err != nil {
log.Printf("Error removing forwarded ports: %v", err)
}
n.teardownNets(n.nets)
if err := syscall.Unmount(n.podNSPath(), 0); err != nil {
// if already unmounted, umount(2) returns EINVAL
if !os.IsNotExist(err) && err != syscall.EINVAL {
log.Printf("Error unmounting %q: %v", n.podNSPath(), err)
}
}
}
// sets up new netns with just lo
func basicNetNS() (hostNS, podNS *os.File, err error) {
hostNS, podNS, err = newNetNS()
if err != nil {
err = fmt.Errorf("failed to create new netns: %v", err)
return
}
// we're in podNS!!
if err = loUp(); err != nil {
hostNS.Close()
podNS.Close()
return nil, nil, err
}
return
}
// enterHostNS moves into the host's network namespace.
func (n *Networking) enterHostNS() error {
return ns.SetNS(n.hostNS, syscall.CLONE_NEWNET)
}
// Save writes out the info about active nets
// for "rkt list" and friends to display
func (e *Networking) Save() error {
nis := []netinfo.NetInfo{}
for _, n := range e.nets {
nis = append(nis, *n.runtime)
}
return netinfo.Save(e.podRoot, nis)
}
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 {
ns.SetNS(hostNS, syscall.CLONE_NEWNET)
return
}
return
}
// execute f() in tgtNS
func withNetNS(curNS, tgtNS *os.File, f func() error) error {
if err := ns.SetNS(tgtNS, syscall.CLONE_NEWNET); err != nil {
return err
}
if err := f(); err != nil {
// Attempt to revert the net ns in a known state
if err := ns.SetNS(curNS, syscall.CLONE_NEWNET); err != nil {
log.Printf("Cannot revert the net namespace: %v", err)
}
return err
}
return ns.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, dst string) error {
// 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, "")
}