mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Merge pull request #257 from mrjana/overlay
Refactor sandbox code to use interfaces and add bridge support to sandbox
This commit is contained in:
commit
d861b7ec70
13 changed files with 767 additions and 731 deletions
|
@ -5,7 +5,6 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
|
@ -15,7 +14,6 @@ import (
|
||||||
"github.com/docker/libnetwork/netutils"
|
"github.com/docker/libnetwork/netutils"
|
||||||
"github.com/docker/libnetwork/options"
|
"github.com/docker/libnetwork/options"
|
||||||
"github.com/docker/libnetwork/portmapper"
|
"github.com/docker/libnetwork/portmapper"
|
||||||
"github.com/docker/libnetwork/sandbox"
|
|
||||||
"github.com/docker/libnetwork/types"
|
"github.com/docker/libnetwork/types"
|
||||||
"github.com/vishvananda/netlink"
|
"github.com/vishvananda/netlink"
|
||||||
)
|
)
|
||||||
|
@ -71,7 +69,9 @@ type containerConfiguration struct {
|
||||||
|
|
||||||
type bridgeEndpoint struct {
|
type bridgeEndpoint struct {
|
||||||
id types.UUID
|
id types.UUID
|
||||||
intf *sandbox.Interface
|
srcName string
|
||||||
|
addr *net.IPNet
|
||||||
|
addrv6 *net.IPNet
|
||||||
macAddress net.HardwareAddr
|
macAddress net.HardwareAddr
|
||||||
config *endpointConfiguration // User specified parameters
|
config *endpointConfiguration // User specified parameters
|
||||||
containerConfig *containerConfiguration
|
containerConfig *containerConfiguration
|
||||||
|
@ -701,13 +701,13 @@ func (d *driver) CreateEndpoint(nid, eid types.UUID, epInfo driverapi.EndpointIn
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Generate a name for what will be the host side pipe interface
|
// Generate a name for what will be the host side pipe interface
|
||||||
name1, err := generateIfaceName()
|
name1, err := netutils.GenerateIfaceName(vethPrefix, vethLen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a name for what will be the sandbox side pipe interface
|
// Generate a name for what will be the sandbox side pipe interface
|
||||||
name2, err := generateIfaceName()
|
name2, err := netutils.GenerateIfaceName(vethPrefix, vethLen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -803,25 +803,20 @@ func (d *driver) CreateEndpoint(nid, eid types.UUID, epInfo driverapi.EndpointIn
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the sandbox side pipe interface
|
// Create the sandbox side pipe interface
|
||||||
intf := &sandbox.Interface{}
|
endpoint.srcName = name2
|
||||||
intf.SrcName = name2
|
endpoint.addr = ipv4Addr
|
||||||
intf.DstName = containerVethPrefix
|
|
||||||
intf.Address = ipv4Addr
|
|
||||||
|
|
||||||
if config.EnableIPv6 {
|
if config.EnableIPv6 {
|
||||||
intf.AddressIPv6 = ipv6Addr
|
endpoint.addrv6 = ipv6Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the interface in endpoint, this is needed for cleanup on DeleteEndpoint()
|
|
||||||
endpoint.intf = intf
|
|
||||||
|
|
||||||
err = epInfo.AddInterface(ifaceID, endpoint.macAddress, *ipv4Addr, *ipv6Addr)
|
err = epInfo.AddInterface(ifaceID, endpoint.macAddress, *ipv4Addr, *ipv6Addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Program any required port mapping and store them in the endpoint
|
// Program any required port mapping and store them in the endpoint
|
||||||
endpoint.portMapping, err = n.allocatePorts(epConfig, intf, config.DefaultBindingIP, config.EnableUserlandProxy)
|
endpoint.portMapping, err = n.allocatePorts(epConfig, endpoint, config.DefaultBindingIP, config.EnableUserlandProxy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -883,14 +878,14 @@ func (d *driver) DeleteEndpoint(nid, eid types.UUID) error {
|
||||||
n.releasePorts(ep)
|
n.releasePorts(ep)
|
||||||
|
|
||||||
// Release the v4 address allocated to this endpoint's sandbox interface
|
// Release the v4 address allocated to this endpoint's sandbox interface
|
||||||
err = ipAllocator.ReleaseIP(n.bridge.bridgeIPv4, ep.intf.Address.IP)
|
err = ipAllocator.ReleaseIP(n.bridge.bridgeIPv4, ep.addr.IP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release the v6 address allocated to this endpoint's sandbox interface
|
// Release the v6 address allocated to this endpoint's sandbox interface
|
||||||
if config.EnableIPv6 {
|
if config.EnableIPv6 {
|
||||||
err := ipAllocator.ReleaseIP(n.bridge.bridgeIPv6, ep.intf.AddressIPv6.IP)
|
err := ipAllocator.ReleaseIP(n.bridge.bridgeIPv6, ep.addrv6.IP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -898,7 +893,7 @@ func (d *driver) DeleteEndpoint(nid, eid types.UUID) error {
|
||||||
|
|
||||||
// Try removal of link. Discard error: link pair might have
|
// Try removal of link. Discard error: link pair might have
|
||||||
// already been deleted by sandbox delete.
|
// already been deleted by sandbox delete.
|
||||||
link, err := netlink.LinkByName(ep.intf.SrcName)
|
link, err := netlink.LinkByName(ep.srcName)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
netlink.LinkDel(link)
|
netlink.LinkDel(link)
|
||||||
}
|
}
|
||||||
|
@ -982,7 +977,7 @@ func (d *driver) Join(nid, eid types.UUID, sboxKey string, jinfo driverapi.JoinI
|
||||||
for _, iNames := range jinfo.InterfaceNames() {
|
for _, iNames := range jinfo.InterfaceNames() {
|
||||||
// Make sure to set names on the correct interface ID.
|
// Make sure to set names on the correct interface ID.
|
||||||
if iNames.ID() == ifaceID {
|
if iNames.ID() == ifaceID {
|
||||||
err = iNames.SetNames(endpoint.intf.SrcName, endpoint.intf.DstName)
|
err = iNames.SetNames(endpoint.srcName, containerVethPrefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -1060,8 +1055,8 @@ func (d *driver) link(network *bridgeNetwork, endpoint *bridgeEndpoint, options
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
l := newLink(parentEndpoint.intf.Address.IP.String(),
|
l := newLink(parentEndpoint.addr.IP.String(),
|
||||||
endpoint.intf.Address.IP.String(),
|
endpoint.addr.IP.String(),
|
||||||
endpoint.config.ExposedPorts, network.config.BridgeName)
|
endpoint.config.ExposedPorts, network.config.BridgeName)
|
||||||
if enable {
|
if enable {
|
||||||
err = l.Enable()
|
err = l.Enable()
|
||||||
|
@ -1093,8 +1088,8 @@ func (d *driver) link(network *bridgeNetwork, endpoint *bridgeEndpoint, options
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
l := newLink(endpoint.intf.Address.IP.String(),
|
l := newLink(endpoint.addr.IP.String(),
|
||||||
childEndpoint.intf.Address.IP.String(),
|
childEndpoint.addr.IP.String(),
|
||||||
childEndpoint.config.ExposedPorts, network.config.BridgeName)
|
childEndpoint.config.ExposedPorts, network.config.BridgeName)
|
||||||
if enable {
|
if enable {
|
||||||
err = l.Enable()
|
err = l.Enable()
|
||||||
|
@ -1184,22 +1179,3 @@ func electMacAddress(epConfig *endpointConfiguration) net.HardwareAddr {
|
||||||
}
|
}
|
||||||
return netutils.GenerateRandomMAC()
|
return netutils.GenerateRandomMAC()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generates a name to be used for a virtual ethernet
|
|
||||||
// interface. The name is constructed by 'veth' appended
|
|
||||||
// by a randomly generated hex value. (example: veth0f60e2c)
|
|
||||||
func generateIfaceName() (string, error) {
|
|
||||||
for i := 0; i < 3; i++ {
|
|
||||||
name, err := netutils.GenerateRandomName(vethPrefix, vethLen)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, err := net.InterfaceByName(name); err != nil {
|
|
||||||
if strings.Contains(err.Error(), "no such") {
|
|
||||||
return name, nil
|
|
||||||
}
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", &ErrIfaceName{}
|
|
||||||
}
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/libnetwork/sandbox"
|
|
||||||
"github.com/docker/libnetwork/types"
|
"github.com/docker/libnetwork/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -15,7 +14,7 @@ var (
|
||||||
defaultBindingIP = net.IPv4(0, 0, 0, 0)
|
defaultBindingIP = net.IPv4(0, 0, 0, 0)
|
||||||
)
|
)
|
||||||
|
|
||||||
func (n *bridgeNetwork) allocatePorts(epConfig *endpointConfiguration, intf *sandbox.Interface, reqDefBindIP net.IP, ulPxyEnabled bool) ([]types.PortBinding, error) {
|
func (n *bridgeNetwork) allocatePorts(epConfig *endpointConfiguration, ep *bridgeEndpoint, reqDefBindIP net.IP, ulPxyEnabled bool) ([]types.PortBinding, error) {
|
||||||
if epConfig == nil || epConfig.PortBindings == nil {
|
if epConfig == nil || epConfig.PortBindings == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
@ -25,7 +24,7 @@ func (n *bridgeNetwork) allocatePorts(epConfig *endpointConfiguration, intf *san
|
||||||
defHostIP = reqDefBindIP
|
defHostIP = reqDefBindIP
|
||||||
}
|
}
|
||||||
|
|
||||||
return n.allocatePortsInternal(epConfig.PortBindings, intf.Address.IP, defHostIP, ulPxyEnabled)
|
return n.allocatePortsInternal(epConfig.PortBindings, ep.addr.IP, defHostIP, ulPxyEnabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *bridgeNetwork) allocatePortsInternal(bindings []types.PortBinding, containerIP, defHostIP net.IP, ulPxyEnabled bool) ([]types.PortBinding, error) {
|
func (n *bridgeNetwork) allocatePortsInternal(bindings []types.PortBinding, containerIP, defHostIP net.IP, ulPxyEnabled bool) ([]types.PortBinding, error) {
|
||||||
|
|
|
@ -9,7 +9,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/libnetwork/types"
|
||||||
"github.com/vishvananda/netlink"
|
"github.com/vishvananda/netlink"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -147,3 +149,22 @@ func GenerateRandomName(prefix string, size int) (string, error) {
|
||||||
}
|
}
|
||||||
return prefix + hex.EncodeToString(id)[:size], nil
|
return prefix + hex.EncodeToString(id)[:size], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GenerateIfaceName returns an interface name using the passed in
|
||||||
|
// prefix and the length of random bytes. The api ensures that the
|
||||||
|
// there are is no interface which exists with that name.
|
||||||
|
func GenerateIfaceName(prefix string, len int) (string, error) {
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
name, err := GenerateRandomName(prefix, len)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, err := net.InterfaceByName(name); err != nil {
|
||||||
|
if strings.Contains(err.Error(), "no such") {
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", types.InternalErrorf("could not generate interface name")
|
||||||
|
}
|
||||||
|
|
|
@ -1,176 +0,0 @@
|
||||||
package sandbox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/vishvananda/netlink"
|
|
||||||
"github.com/vishvananda/netns"
|
|
||||||
)
|
|
||||||
|
|
||||||
func configureInterface(iface netlink.Link, settings *Interface) error {
|
|
||||||
ifaceName := iface.Attrs().Name
|
|
||||||
ifaceConfigurators := []struct {
|
|
||||||
Fn func(netlink.Link, *Interface) error
|
|
||||||
ErrMessage string
|
|
||||||
}{
|
|
||||||
{setInterfaceName, fmt.Sprintf("error renaming interface %q to %q", ifaceName, settings.DstName)},
|
|
||||||
{setInterfaceIP, fmt.Sprintf("error setting interface %q IP to %q", ifaceName, settings.Address)},
|
|
||||||
{setInterfaceIPv6, fmt.Sprintf("error setting interface %q IPv6 to %q", ifaceName, settings.AddressIPv6)},
|
|
||||||
{setInterfaceRoutes, fmt.Sprintf("error setting interface %q routes to %q", ifaceName, settings.Routes)},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, config := range ifaceConfigurators {
|
|
||||||
if err := config.Fn(iface, settings); err != nil {
|
|
||||||
return fmt.Errorf("%s: %v", config.ErrMessage, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func programGateway(path string, gw net.IP, isAdd bool) error {
|
|
||||||
runtime.LockOSThread()
|
|
||||||
defer runtime.UnlockOSThread()
|
|
||||||
|
|
||||||
origns, err := netns.Get()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer origns.Close()
|
|
||||||
|
|
||||||
f, err := os.OpenFile(path, os.O_RDONLY, 0)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed get network namespace %q: %v", path, err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
nsFD := f.Fd()
|
|
||||||
if err = netns.Set(netns.NsHandle(nsFD)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer netns.Set(origns)
|
|
||||||
|
|
||||||
gwRoutes, err := netlink.RouteGet(gw)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("route for the gateway could not be found: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if isAdd {
|
|
||||||
return netlink.RouteAdd(&netlink.Route{
|
|
||||||
Scope: netlink.SCOPE_UNIVERSE,
|
|
||||||
LinkIndex: gwRoutes[0].LinkIndex,
|
|
||||||
Gw: gw,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return netlink.RouteDel(&netlink.Route{
|
|
||||||
Scope: netlink.SCOPE_UNIVERSE,
|
|
||||||
LinkIndex: gwRoutes[0].LinkIndex,
|
|
||||||
Gw: gw,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Program a route in to the namespace routing table.
|
|
||||||
func programRoute(path string, dest *net.IPNet, nh net.IP) error {
|
|
||||||
runtime.LockOSThread()
|
|
||||||
defer runtime.UnlockOSThread()
|
|
||||||
|
|
||||||
origns, err := netns.Get()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer origns.Close()
|
|
||||||
|
|
||||||
f, err := os.OpenFile(path, os.O_RDONLY, 0)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed get network namespace %q: %v", path, err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
nsFD := f.Fd()
|
|
||||||
if err = netns.Set(netns.NsHandle(nsFD)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer netns.Set(origns)
|
|
||||||
|
|
||||||
gwRoutes, err := netlink.RouteGet(nh)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("route for the next hop could not be found: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return netlink.RouteAdd(&netlink.Route{
|
|
||||||
Scope: netlink.SCOPE_UNIVERSE,
|
|
||||||
LinkIndex: gwRoutes[0].LinkIndex,
|
|
||||||
Gw: gwRoutes[0].Gw,
|
|
||||||
Dst: dest,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete a route from the namespace routing table.
|
|
||||||
func removeRoute(path string, dest *net.IPNet, nh net.IP) error {
|
|
||||||
runtime.LockOSThread()
|
|
||||||
defer runtime.UnlockOSThread()
|
|
||||||
|
|
||||||
origns, err := netns.Get()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer origns.Close()
|
|
||||||
|
|
||||||
f, err := os.OpenFile(path, os.O_RDONLY, 0)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed get network namespace %q: %v", path, err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
nsFD := f.Fd()
|
|
||||||
if err = netns.Set(netns.NsHandle(nsFD)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer netns.Set(origns)
|
|
||||||
|
|
||||||
gwRoutes, err := netlink.RouteGet(nh)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("route for the next hop could not be found: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return netlink.RouteDel(&netlink.Route{
|
|
||||||
Scope: netlink.SCOPE_UNIVERSE,
|
|
||||||
LinkIndex: gwRoutes[0].LinkIndex,
|
|
||||||
Gw: gwRoutes[0].Gw,
|
|
||||||
Dst: dest,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func setInterfaceIP(iface netlink.Link, settings *Interface) error {
|
|
||||||
ipAddr := &netlink.Addr{IPNet: settings.Address, Label: ""}
|
|
||||||
return netlink.AddrAdd(iface, ipAddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setInterfaceIPv6(iface netlink.Link, settings *Interface) error {
|
|
||||||
if settings.AddressIPv6 == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
ipAddr := &netlink.Addr{IPNet: settings.AddressIPv6, Label: ""}
|
|
||||||
return netlink.AddrAdd(iface, ipAddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setInterfaceName(iface netlink.Link, settings *Interface) error {
|
|
||||||
return netlink.LinkSetName(iface, settings.DstName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setInterfaceRoutes(iface netlink.Link, settings *Interface) error {
|
|
||||||
for _, route := range settings.Routes {
|
|
||||||
err := netlink.RouteAdd(&netlink.Route{
|
|
||||||
Scope: netlink.SCOPE_LINK,
|
|
||||||
LinkIndex: iface.Attrs().Index,
|
|
||||||
Dst: route,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
313
libnetwork/sandbox/interface_linux.go
Normal file
313
libnetwork/sandbox/interface_linux.go
Normal file
|
@ -0,0 +1,313 @@
|
||||||
|
package sandbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/docker/libnetwork/types"
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IfaceOption is a function option type to set interface options
|
||||||
|
type IfaceOption func(i *nwIface)
|
||||||
|
|
||||||
|
type nwIface struct {
|
||||||
|
srcName string
|
||||||
|
dstName string
|
||||||
|
master string
|
||||||
|
dstMaster string
|
||||||
|
address *net.IPNet
|
||||||
|
addressIPv6 *net.IPNet
|
||||||
|
routes []*net.IPNet
|
||||||
|
bridge bool
|
||||||
|
ns *networkNamespace
|
||||||
|
sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *nwIface) SrcName() string {
|
||||||
|
i.Lock()
|
||||||
|
defer i.Unlock()
|
||||||
|
|
||||||
|
return i.srcName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *nwIface) DstName() string {
|
||||||
|
i.Lock()
|
||||||
|
defer i.Unlock()
|
||||||
|
|
||||||
|
return i.dstName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *nwIface) DstMaster() string {
|
||||||
|
i.Lock()
|
||||||
|
defer i.Unlock()
|
||||||
|
|
||||||
|
return i.dstMaster
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *nwIface) Bridge() bool {
|
||||||
|
i.Lock()
|
||||||
|
defer i.Unlock()
|
||||||
|
|
||||||
|
return i.bridge
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *nwIface) Master() string {
|
||||||
|
i.Lock()
|
||||||
|
defer i.Unlock()
|
||||||
|
|
||||||
|
return i.master
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *nwIface) Address() *net.IPNet {
|
||||||
|
i.Lock()
|
||||||
|
defer i.Unlock()
|
||||||
|
|
||||||
|
return types.GetIPNetCopy(i.address)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *nwIface) AddressIPv6() *net.IPNet {
|
||||||
|
i.Lock()
|
||||||
|
defer i.Unlock()
|
||||||
|
|
||||||
|
return types.GetIPNetCopy(i.addressIPv6)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *nwIface) Routes() []*net.IPNet {
|
||||||
|
i.Lock()
|
||||||
|
defer i.Unlock()
|
||||||
|
|
||||||
|
routes := make([]*net.IPNet, len(i.routes))
|
||||||
|
for index, route := range i.routes {
|
||||||
|
r := types.GetIPNetCopy(route)
|
||||||
|
routes[index] = r
|
||||||
|
}
|
||||||
|
|
||||||
|
return routes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *networkNamespace) Interfaces() []Interface {
|
||||||
|
n.Lock()
|
||||||
|
defer n.Unlock()
|
||||||
|
|
||||||
|
ifaces := make([]Interface, len(n.iFaces))
|
||||||
|
|
||||||
|
for i, iface := range n.iFaces {
|
||||||
|
ifaces[i] = iface
|
||||||
|
}
|
||||||
|
|
||||||
|
return ifaces
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *nwIface) Remove() error {
|
||||||
|
i.Lock()
|
||||||
|
n := i.ns
|
||||||
|
i.Unlock()
|
||||||
|
|
||||||
|
n.Lock()
|
||||||
|
path := n.path
|
||||||
|
n.Unlock()
|
||||||
|
|
||||||
|
return nsInvoke(path, func(nsFD int) error { return nil }, func(callerFD int) error {
|
||||||
|
// Find the network inteerface identified by the DstName attribute.
|
||||||
|
iface, err := netlink.LinkByName(i.DstName())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Down the interface before configuring
|
||||||
|
if err := netlink.LinkSetDown(iface); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = netlink.LinkSetName(iface, i.SrcName())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("LinkSetName failed: ", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// if it is a bridge just delete it.
|
||||||
|
if i.Bridge() {
|
||||||
|
if err := netlink.LinkDel(iface); err != nil {
|
||||||
|
return fmt.Errorf("failed deleting bridge %q: %v", i.SrcName(), err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Move the network interface to caller namespace.
|
||||||
|
if err := netlink.LinkSetNsFd(iface, callerFD); err != nil {
|
||||||
|
fmt.Println("LinkSetNsPid failed: ", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
n.Lock()
|
||||||
|
for index, intf := range n.iFaces {
|
||||||
|
if intf == i {
|
||||||
|
n.iFaces = append(n.iFaces[:index], n.iFaces[index+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *networkNamespace) findDstMaster(srcName string) string {
|
||||||
|
n.Lock()
|
||||||
|
defer n.Unlock()
|
||||||
|
|
||||||
|
for _, i := range n.iFaces {
|
||||||
|
// The master should match the srcname of the interface and the
|
||||||
|
// master interface should be of type bridge.
|
||||||
|
if i.SrcName() == srcName && i.Bridge() {
|
||||||
|
return i.DstName()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *networkNamespace) AddInterface(srcName, dstPrefix string, options ...IfaceOption) error {
|
||||||
|
i := &nwIface{srcName: srcName, dstName: dstPrefix, ns: n}
|
||||||
|
i.processInterfaceOptions(options...)
|
||||||
|
|
||||||
|
if i.master != "" {
|
||||||
|
i.dstMaster = n.findDstMaster(i.master)
|
||||||
|
if i.dstMaster == "" {
|
||||||
|
return fmt.Errorf("could not find an appropriate master %q for %q",
|
||||||
|
i.master, i.srcName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
n.Lock()
|
||||||
|
i.dstName = fmt.Sprintf("%s%d", i.dstName, n.nextIfIndex)
|
||||||
|
n.nextIfIndex++
|
||||||
|
path := n.path
|
||||||
|
n.Unlock()
|
||||||
|
|
||||||
|
return nsInvoke(path, func(nsFD int) error {
|
||||||
|
// If it is a bridge interface we have to create the bridge inside
|
||||||
|
// the namespace so don't try to lookup the interface using srcName
|
||||||
|
if i.bridge {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the network interface identified by the SrcName attribute.
|
||||||
|
iface, err := netlink.LinkByName(i.srcName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get link by name %q: %v", i.srcName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the network interface to the destination namespace.
|
||||||
|
if err := netlink.LinkSetNsFd(iface, nsFD); err != nil {
|
||||||
|
return fmt.Errorf("failed to set namespace on link %q: %v", i.srcName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}, func(callerFD int) error {
|
||||||
|
if i.bridge {
|
||||||
|
link := &netlink.Bridge{
|
||||||
|
LinkAttrs: netlink.LinkAttrs{
|
||||||
|
Name: i.srcName,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.LinkAdd(link); err != nil {
|
||||||
|
return fmt.Errorf("failed to create bridge %q: %v", i.srcName, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the network interface identified by the SrcName attribute.
|
||||||
|
iface, err := netlink.LinkByName(i.srcName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get link by name %q: %v", i.srcName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Down the interface before configuring
|
||||||
|
if err := netlink.LinkSetDown(iface); err != nil {
|
||||||
|
return fmt.Errorf("failed to set link down: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure the interface now this is moved in the proper namespace.
|
||||||
|
if err := configureInterface(iface, i); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Up the interface.
|
||||||
|
if err := netlink.LinkSetUp(iface); err != nil {
|
||||||
|
return fmt.Errorf("failed to set link up: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
n.Lock()
|
||||||
|
n.iFaces = append(n.iFaces, i)
|
||||||
|
n.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func configureInterface(iface netlink.Link, i *nwIface) error {
|
||||||
|
ifaceName := iface.Attrs().Name
|
||||||
|
ifaceConfigurators := []struct {
|
||||||
|
Fn func(netlink.Link, *nwIface) error
|
||||||
|
ErrMessage string
|
||||||
|
}{
|
||||||
|
{setInterfaceName, fmt.Sprintf("error renaming interface %q to %q", ifaceName, i.DstName())},
|
||||||
|
{setInterfaceIP, fmt.Sprintf("error setting interface %q IP to %q", ifaceName, i.Address())},
|
||||||
|
{setInterfaceIPv6, fmt.Sprintf("error setting interface %q IPv6 to %q", ifaceName, i.AddressIPv6())},
|
||||||
|
{setInterfaceRoutes, fmt.Sprintf("error setting interface %q routes to %q", ifaceName, i.Routes())},
|
||||||
|
{setInterfaceMaster, fmt.Sprintf("error setting interface %q master to %q", ifaceName, i.DstMaster())},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, config := range ifaceConfigurators {
|
||||||
|
if err := config.Fn(iface, i); err != nil {
|
||||||
|
return fmt.Errorf("%s: %v", config.ErrMessage, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setInterfaceMaster(iface netlink.Link, i *nwIface) error {
|
||||||
|
if i.DstMaster() == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return netlink.LinkSetMaster(iface, &netlink.Bridge{
|
||||||
|
LinkAttrs: netlink.LinkAttrs{Name: i.DstMaster()}})
|
||||||
|
}
|
||||||
|
|
||||||
|
func setInterfaceIP(iface netlink.Link, i *nwIface) error {
|
||||||
|
if i.Address() == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ipAddr := &netlink.Addr{IPNet: i.Address(), Label: ""}
|
||||||
|
return netlink.AddrAdd(iface, ipAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setInterfaceIPv6(iface netlink.Link, i *nwIface) error {
|
||||||
|
if i.AddressIPv6() == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ipAddr := &netlink.Addr{IPNet: i.AddressIPv6(), Label: ""}
|
||||||
|
return netlink.AddrAdd(iface, ipAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setInterfaceName(iface netlink.Link, i *nwIface) error {
|
||||||
|
return netlink.LinkSetName(iface, i.DstName())
|
||||||
|
}
|
||||||
|
|
||||||
|
func setInterfaceRoutes(iface netlink.Link, i *nwIface) error {
|
||||||
|
for _, route := range i.Routes() {
|
||||||
|
err := netlink.RouteAdd(&netlink.Route{
|
||||||
|
Scope: netlink.SCOPE_LINK,
|
||||||
|
LinkIndex: iface.Attrs().Index,
|
||||||
|
Dst: route,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -32,9 +32,12 @@ var (
|
||||||
// interface. It represents a linux network namespace, and moves an interface
|
// interface. It represents a linux network namespace, and moves an interface
|
||||||
// into it when called on method AddInterface or sets the gateway etc.
|
// into it when called on method AddInterface or sets the gateway etc.
|
||||||
type networkNamespace struct {
|
type networkNamespace struct {
|
||||||
path string
|
path string
|
||||||
sinfo *Info
|
iFaces []*nwIface
|
||||||
nextIfIndex int
|
gw net.IP
|
||||||
|
gwv6 net.IP
|
||||||
|
staticRoutes []*types.StaticRoute
|
||||||
|
nextIfIndex int
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,12 +130,16 @@ func GenerateKey(containerID string) string {
|
||||||
// NewSandbox provides a new sandbox instance created in an os specific way
|
// NewSandbox provides a new sandbox instance created in an os specific way
|
||||||
// provided a key which uniquely identifies the sandbox
|
// provided a key which uniquely identifies the sandbox
|
||||||
func NewSandbox(key string, osCreate bool) (Sandbox, error) {
|
func NewSandbox(key string, osCreate bool) (Sandbox, error) {
|
||||||
info, err := createNetworkNamespace(key, osCreate)
|
err := createNetworkNamespace(key, osCreate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &networkNamespace{path: key, sinfo: info}, nil
|
return &networkNamespace{path: key}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *networkNamespace) InterfaceOptions() IfaceOptionSetter {
|
||||||
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
func reexecCreateNamespace() {
|
func reexecCreateNamespace() {
|
||||||
|
@ -149,18 +156,18 @@ func reexecCreateNamespace() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createNetworkNamespace(path string, osCreate bool) (*Info, error) {
|
func createNetworkNamespace(path string, osCreate bool) error {
|
||||||
runtime.LockOSThread()
|
runtime.LockOSThread()
|
||||||
defer runtime.UnlockOSThread()
|
defer runtime.UnlockOSThread()
|
||||||
|
|
||||||
origns, err := netns.Get()
|
origns, err := netns.Get()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
defer origns.Close()
|
defer origns.Close()
|
||||||
|
|
||||||
if err := createNamespaceFile(path); err != nil {
|
if err := createNamespaceFile(path); err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := &exec.Cmd{
|
cmd := &exec.Cmd{
|
||||||
|
@ -174,12 +181,10 @@ func createNetworkNamespace(path string, osCreate bool) (*Info, error) {
|
||||||
cmd.SysProcAttr.Cloneflags = syscall.CLONE_NEWNET
|
cmd.SysProcAttr.Cloneflags = syscall.CLONE_NEWNET
|
||||||
}
|
}
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
return nil, fmt.Errorf("namespace creation reexec command failed: %v", err)
|
return fmt.Errorf("namespace creation reexec command failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
interfaces := []*Interface{}
|
return nil
|
||||||
info := &Info{Interfaces: interfaces}
|
|
||||||
return info, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func unmountNamespaceFile(path string) {
|
func unmountNamespaceFile(path string) {
|
||||||
|
@ -217,7 +222,7 @@ func loopbackUp() error {
|
||||||
return netlink.LinkSetUp(iface)
|
return netlink.LinkSetUp(iface)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *networkNamespace) RemoveInterface(i *Interface) error {
|
func nsInvoke(path string, prefunc func(nsFD int) error, postfunc func(callerFD int) error) error {
|
||||||
runtime.LockOSThread()
|
runtime.LockOSThread()
|
||||||
defer runtime.UnlockOSThread()
|
defer runtime.UnlockOSThread()
|
||||||
|
|
||||||
|
@ -227,84 +232,18 @@ func (n *networkNamespace) RemoveInterface(i *Interface) error {
|
||||||
}
|
}
|
||||||
defer origns.Close()
|
defer origns.Close()
|
||||||
|
|
||||||
f, err := os.OpenFile(n.path, os.O_RDONLY, 0)
|
f, err := os.OpenFile(path, os.O_RDONLY, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed get network namespace %q: %v", n.path, err)
|
return fmt.Errorf("failed get network namespace %q: %v", path, err)
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
nsFD := f.Fd()
|
nsFD := f.Fd()
|
||||||
if err = netns.Set(netns.NsHandle(nsFD)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer netns.Set(origns)
|
|
||||||
|
|
||||||
// Find the network inteerface identified by the DstName attribute.
|
// Invoked before the namespace switch happens but after the namespace file
|
||||||
iface, err := netlink.LinkByName(i.DstName)
|
// handle is obtained.
|
||||||
if err != nil {
|
if err := prefunc(int(nsFD)); err != nil {
|
||||||
return err
|
return fmt.Errorf("failed in prefunc: %v", err)
|
||||||
}
|
|
||||||
|
|
||||||
// Down the interface before configuring
|
|
||||||
if err := netlink.LinkSetDown(iface); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = netlink.LinkSetName(iface, i.SrcName)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("LinkSetName failed: ", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move the network interface to caller namespace.
|
|
||||||
if err := netlink.LinkSetNsFd(iface, int(origns)); err != nil {
|
|
||||||
fmt.Println("LinkSetNsPid failed: ", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
n.Lock()
|
|
||||||
for index, intf := range n.sinfo.Interfaces {
|
|
||||||
if intf == i {
|
|
||||||
n.sinfo.Interfaces = append(n.sinfo.Interfaces[:index], n.sinfo.Interfaces[index+1:]...)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
n.Unlock()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *networkNamespace) AddInterface(i *Interface) error {
|
|
||||||
n.Lock()
|
|
||||||
i.DstName = fmt.Sprintf("%s%d", i.DstName, n.nextIfIndex)
|
|
||||||
n.nextIfIndex++
|
|
||||||
n.Unlock()
|
|
||||||
|
|
||||||
runtime.LockOSThread()
|
|
||||||
defer runtime.UnlockOSThread()
|
|
||||||
|
|
||||||
origns, err := netns.Get()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer origns.Close()
|
|
||||||
|
|
||||||
f, err := os.OpenFile(n.path, os.O_RDONLY, 0)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed get network namespace %q: %v", n.path, err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
// Find the network interface identified by the SrcName attribute.
|
|
||||||
iface, err := netlink.LinkByName(i.SrcName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move the network interface to the destination namespace.
|
|
||||||
nsFD := f.Fd()
|
|
||||||
if err := netlink.LinkSetNsFd(iface, int(nsFD)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = netns.Set(netns.NsHandle(nsFD)); err != nil {
|
if err = netns.Set(netns.NsHandle(nsFD)); err != nil {
|
||||||
|
@ -312,133 +251,19 @@ func (n *networkNamespace) AddInterface(i *Interface) error {
|
||||||
}
|
}
|
||||||
defer netns.Set(origns)
|
defer netns.Set(origns)
|
||||||
|
|
||||||
// Down the interface before configuring
|
// Invoked after the namespace switch.
|
||||||
if err := netlink.LinkSetDown(iface); err != nil {
|
return postfunc(int(origns))
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure the interface now this is moved in the proper namespace.
|
|
||||||
if err := configureInterface(iface, i); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Up the interface.
|
|
||||||
if err := netlink.LinkSetUp(iface); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
n.Lock()
|
|
||||||
n.sinfo.Interfaces = append(n.sinfo.Interfaces, i)
|
|
||||||
n.Unlock()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *networkNamespace) SetGateway(gw net.IP) error {
|
func (n *networkNamespace) nsPath() string {
|
||||||
// Silently return if the gateway is empty
|
|
||||||
if len(gw) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err := programGateway(n.path, gw, true)
|
|
||||||
if err == nil {
|
|
||||||
n.Lock()
|
|
||||||
n.sinfo.Gateway = gw
|
|
||||||
n.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *networkNamespace) UnsetGateway() error {
|
|
||||||
n.Lock()
|
|
||||||
gw := n.sinfo.Gateway
|
|
||||||
n.Unlock()
|
|
||||||
|
|
||||||
// Silently return if the gateway is empty
|
|
||||||
if len(gw) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err := programGateway(n.path, gw, false)
|
|
||||||
if err == nil {
|
|
||||||
n.Lock()
|
|
||||||
n.sinfo.Gateway = net.IP{}
|
|
||||||
n.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *networkNamespace) SetGatewayIPv6(gw net.IP) error {
|
|
||||||
// Silently return if the gateway is empty
|
|
||||||
if len(gw) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err := programGateway(n.path, gw, true)
|
|
||||||
if err == nil {
|
|
||||||
n.Lock()
|
|
||||||
n.sinfo.GatewayIPv6 = gw
|
|
||||||
n.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *networkNamespace) UnsetGatewayIPv6() error {
|
|
||||||
n.Lock()
|
|
||||||
gw := n.sinfo.GatewayIPv6
|
|
||||||
n.Unlock()
|
|
||||||
|
|
||||||
// Silently return if the gateway is empty
|
|
||||||
if len(gw) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err := programGateway(n.path, gw, false)
|
|
||||||
if err == nil {
|
|
||||||
n.Lock()
|
|
||||||
n.sinfo.GatewayIPv6 = net.IP{}
|
|
||||||
n.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *networkNamespace) AddStaticRoute(r *types.StaticRoute) error {
|
|
||||||
err := programRoute(n.path, r.Destination, r.NextHop)
|
|
||||||
if err == nil {
|
|
||||||
n.Lock()
|
|
||||||
n.sinfo.StaticRoutes = append(n.sinfo.StaticRoutes, r)
|
|
||||||
n.Unlock()
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *networkNamespace) RemoveStaticRoute(r *types.StaticRoute) error {
|
|
||||||
err := removeRoute(n.path, r.Destination, r.NextHop)
|
|
||||||
if err == nil {
|
|
||||||
n.Lock()
|
|
||||||
lastIndex := len(n.sinfo.StaticRoutes) - 1
|
|
||||||
for i, v := range n.sinfo.StaticRoutes {
|
|
||||||
if v == r {
|
|
||||||
// Overwrite the route we're removing with the last element
|
|
||||||
n.sinfo.StaticRoutes[i] = n.sinfo.StaticRoutes[lastIndex]
|
|
||||||
// Shorten the slice to trim the extra element
|
|
||||||
n.sinfo.StaticRoutes = n.sinfo.StaticRoutes[:lastIndex]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
n.Unlock()
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *networkNamespace) Interfaces() []*Interface {
|
|
||||||
n.Lock()
|
n.Lock()
|
||||||
defer n.Unlock()
|
defer n.Unlock()
|
||||||
return n.sinfo.Interfaces
|
|
||||||
|
return n.path
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *networkNamespace) Info() Info {
|
||||||
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *networkNamespace) Key() string {
|
func (n *networkNamespace) Key() string {
|
||||||
|
|
41
libnetwork/sandbox/options_linux.go
Normal file
41
libnetwork/sandbox/options_linux.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package sandbox
|
||||||
|
|
||||||
|
import "net"
|
||||||
|
|
||||||
|
func (i *nwIface) processInterfaceOptions(options ...IfaceOption) {
|
||||||
|
for _, opt := range options {
|
||||||
|
if opt != nil {
|
||||||
|
opt(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *networkNamespace) Bridge(isBridge bool) IfaceOption {
|
||||||
|
return func(i *nwIface) {
|
||||||
|
i.bridge = isBridge
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *networkNamespace) Master(name string) IfaceOption {
|
||||||
|
return func(i *nwIface) {
|
||||||
|
i.master = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *networkNamespace) Address(addr *net.IPNet) IfaceOption {
|
||||||
|
return func(i *nwIface) {
|
||||||
|
i.address = addr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *networkNamespace) AddressIPv6(addr *net.IPNet) IfaceOption {
|
||||||
|
return func(i *nwIface) {
|
||||||
|
i.addressIPv6 = addr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *networkNamespace) Routes(routes []*net.IPNet) IfaceOption {
|
||||||
|
return func(i *nwIface) {
|
||||||
|
i.routes = routes
|
||||||
|
}
|
||||||
|
}
|
198
libnetwork/sandbox/route_linux.go
Normal file
198
libnetwork/sandbox/route_linux.go
Normal file
|
@ -0,0 +1,198 @@
|
||||||
|
package sandbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/docker/libnetwork/types"
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (n *networkNamespace) Gateway() net.IP {
|
||||||
|
n.Lock()
|
||||||
|
defer n.Unlock()
|
||||||
|
|
||||||
|
return n.gw
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *networkNamespace) GatewayIPv6() net.IP {
|
||||||
|
n.Lock()
|
||||||
|
defer n.Unlock()
|
||||||
|
|
||||||
|
return n.gwv6
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *networkNamespace) StaticRoutes() []*types.StaticRoute {
|
||||||
|
n.Lock()
|
||||||
|
defer n.Unlock()
|
||||||
|
|
||||||
|
routes := make([]*types.StaticRoute, len(n.staticRoutes))
|
||||||
|
for i, route := range n.staticRoutes {
|
||||||
|
r := route.GetCopy()
|
||||||
|
routes[i] = r
|
||||||
|
}
|
||||||
|
|
||||||
|
return routes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *networkNamespace) setGateway(gw net.IP) {
|
||||||
|
n.Lock()
|
||||||
|
n.gw = gw
|
||||||
|
n.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *networkNamespace) setGatewayIPv6(gwv6 net.IP) {
|
||||||
|
n.Lock()
|
||||||
|
n.gwv6 = gwv6
|
||||||
|
n.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *networkNamespace) SetGateway(gw net.IP) error {
|
||||||
|
// Silently return if the gateway is empty
|
||||||
|
if len(gw) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := programGateway(n.nsPath(), gw, true)
|
||||||
|
if err == nil {
|
||||||
|
n.setGateway(gw)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *networkNamespace) UnsetGateway() error {
|
||||||
|
gw := n.Gateway()
|
||||||
|
|
||||||
|
// Silently return if the gateway is empty
|
||||||
|
if len(gw) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := programGateway(n.nsPath(), gw, false)
|
||||||
|
if err == nil {
|
||||||
|
n.setGateway(net.IP{})
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func programGateway(path string, gw net.IP, isAdd bool) error {
|
||||||
|
return nsInvoke(path, func(nsFD int) error { return nil }, func(callerFD int) error {
|
||||||
|
gwRoutes, err := netlink.RouteGet(gw)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("route for the gateway could not be found: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isAdd {
|
||||||
|
return netlink.RouteAdd(&netlink.Route{
|
||||||
|
Scope: netlink.SCOPE_UNIVERSE,
|
||||||
|
LinkIndex: gwRoutes[0].LinkIndex,
|
||||||
|
Gw: gw,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return netlink.RouteDel(&netlink.Route{
|
||||||
|
Scope: netlink.SCOPE_UNIVERSE,
|
||||||
|
LinkIndex: gwRoutes[0].LinkIndex,
|
||||||
|
Gw: gw,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Program a route in to the namespace routing table.
|
||||||
|
func programRoute(path string, dest *net.IPNet, nh net.IP) error {
|
||||||
|
return nsInvoke(path, func(nsFD int) error { return nil }, func(callerFD int) error {
|
||||||
|
gwRoutes, err := netlink.RouteGet(nh)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("route for the next hop could not be found: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return netlink.RouteAdd(&netlink.Route{
|
||||||
|
Scope: netlink.SCOPE_UNIVERSE,
|
||||||
|
LinkIndex: gwRoutes[0].LinkIndex,
|
||||||
|
Gw: gwRoutes[0].Gw,
|
||||||
|
Dst: dest,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete a route from the namespace routing table.
|
||||||
|
func removeRoute(path string, dest *net.IPNet, nh net.IP) error {
|
||||||
|
return nsInvoke(path, func(nsFD int) error { return nil }, func(callerFD int) error {
|
||||||
|
gwRoutes, err := netlink.RouteGet(nh)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("route for the next hop could not be found: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return netlink.RouteDel(&netlink.Route{
|
||||||
|
Scope: netlink.SCOPE_UNIVERSE,
|
||||||
|
LinkIndex: gwRoutes[0].LinkIndex,
|
||||||
|
Gw: gwRoutes[0].Gw,
|
||||||
|
Dst: dest,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *networkNamespace) SetGatewayIPv6(gwv6 net.IP) error {
|
||||||
|
// Silently return if the gateway is empty
|
||||||
|
if len(gwv6) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := programGateway(n.nsPath(), gwv6, true)
|
||||||
|
if err == nil {
|
||||||
|
n.SetGatewayIPv6(gwv6)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *networkNamespace) UnsetGatewayIPv6() error {
|
||||||
|
gwv6 := n.GatewayIPv6()
|
||||||
|
|
||||||
|
// Silently return if the gateway is empty
|
||||||
|
if len(gwv6) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := programGateway(n.nsPath(), gwv6, false)
|
||||||
|
if err == nil {
|
||||||
|
n.Lock()
|
||||||
|
n.gwv6 = net.IP{}
|
||||||
|
n.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *networkNamespace) AddStaticRoute(r *types.StaticRoute) error {
|
||||||
|
err := programRoute(n.nsPath(), r.Destination, r.NextHop)
|
||||||
|
if err == nil {
|
||||||
|
n.Lock()
|
||||||
|
n.staticRoutes = append(n.staticRoutes, r)
|
||||||
|
n.Unlock()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *networkNamespace) RemoveStaticRoute(r *types.StaticRoute) error {
|
||||||
|
n.Lock()
|
||||||
|
|
||||||
|
err := removeRoute(n.nsPath(), r.Destination, r.NextHop)
|
||||||
|
if err == nil {
|
||||||
|
n.Lock()
|
||||||
|
lastIndex := len(n.staticRoutes) - 1
|
||||||
|
for i, v := range n.staticRoutes {
|
||||||
|
if v == r {
|
||||||
|
// Overwrite the route we're removing with the last element
|
||||||
|
n.staticRoutes[i] = n.staticRoutes[lastIndex]
|
||||||
|
// Shorten the slice to trim the extra element
|
||||||
|
n.staticRoutes = n.staticRoutes[:lastIndex]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n.Unlock()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
|
@ -12,22 +12,12 @@ type Sandbox interface {
|
||||||
// The path where the network namespace is mounted.
|
// The path where the network namespace is mounted.
|
||||||
Key() string
|
Key() string
|
||||||
|
|
||||||
// The collection of Interface previously added with the AddInterface
|
|
||||||
// method. Note that this doesn't incude network interfaces added in any
|
|
||||||
// other way (such as the default loopback interface which are automatically
|
|
||||||
// created on creation of a sandbox).
|
|
||||||
Interfaces() []*Interface
|
|
||||||
|
|
||||||
// Add an existing Interface to this sandbox. The operation will rename
|
// Add an existing Interface to this sandbox. The operation will rename
|
||||||
// from the Interface SrcName to DstName as it moves, and reconfigure the
|
// from the Interface SrcName to DstName as it moves, and reconfigure the
|
||||||
// interface according to the specified settings. The caller is expected
|
// interface according to the specified settings. The caller is expected
|
||||||
// to only provide a prefix for DstName. The AddInterface api will auto-generate
|
// to only provide a prefix for DstName. The AddInterface api will auto-generate
|
||||||
// an appropriate suffix for the DstName to disambiguate.
|
// an appropriate suffix for the DstName to disambiguate.
|
||||||
AddInterface(*Interface) error
|
AddInterface(SrcName string, DstPrefix string, options ...IfaceOption) error
|
||||||
|
|
||||||
// Remove an interface from the sandbox by renaming to original name
|
|
||||||
// and moving it out of the sandbox.
|
|
||||||
RemoveInterface(*Interface) error
|
|
||||||
|
|
||||||
// Set default IPv4 gateway for the sandbox
|
// Set default IPv4 gateway for the sandbox
|
||||||
SetGateway(gw net.IP) error
|
SetGateway(gw net.IP) error
|
||||||
|
@ -47,25 +37,55 @@ type Sandbox interface {
|
||||||
// Remove a static route from the sandbox.
|
// Remove a static route from the sandbox.
|
||||||
RemoveStaticRoute(*types.StaticRoute) error
|
RemoveStaticRoute(*types.StaticRoute) error
|
||||||
|
|
||||||
|
// Returns an interface with methods to set interface options.
|
||||||
|
InterfaceOptions() IfaceOptionSetter
|
||||||
|
|
||||||
|
// Returns an interface with methods to get sandbox state.
|
||||||
|
Info() Info
|
||||||
|
|
||||||
// Destroy the sandbox
|
// Destroy the sandbox
|
||||||
Destroy() error
|
Destroy() error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IfaceOptionSetter interface defines the option setter methods for interface options.
|
||||||
|
type IfaceOptionSetter interface {
|
||||||
|
// Bridge returns an option setter to set if the interface is a bridge.
|
||||||
|
Bridge(bool) IfaceOption
|
||||||
|
|
||||||
|
// Address returns an option setter to set IPv4 address.
|
||||||
|
Address(*net.IPNet) IfaceOption
|
||||||
|
|
||||||
|
// Address returns an option setter to set IPv6 address.
|
||||||
|
AddressIPv6(*net.IPNet) IfaceOption
|
||||||
|
|
||||||
|
// Master returns an option setter to set the master interface if any for this
|
||||||
|
// interface. The master interface name should refer to the srcname of a
|
||||||
|
// previously added interface of type bridge.
|
||||||
|
Master(string) IfaceOption
|
||||||
|
|
||||||
|
// Address returns an option setter to set interface routes.
|
||||||
|
Routes([]*net.IPNet) IfaceOption
|
||||||
|
}
|
||||||
|
|
||||||
// Info represents all possible information that
|
// Info represents all possible information that
|
||||||
// the driver wants to place in the sandbox which includes
|
// the driver wants to place in the sandbox which includes
|
||||||
// interfaces, routes and gateway
|
// interfaces, routes and gateway
|
||||||
type Info struct {
|
type Info interface {
|
||||||
Interfaces []*Interface
|
// The collection of Interface previously added with the AddInterface
|
||||||
|
// method. Note that this doesn't incude network interfaces added in any
|
||||||
|
// other way (such as the default loopback interface which are automatically
|
||||||
|
// created on creation of a sandbox).
|
||||||
|
Interfaces() []Interface
|
||||||
|
|
||||||
// IPv4 gateway for the sandbox.
|
// IPv4 gateway for the sandbox.
|
||||||
Gateway net.IP
|
Gateway() net.IP
|
||||||
|
|
||||||
// IPv6 gateway for the sandbox.
|
// IPv6 gateway for the sandbox.
|
||||||
GatewayIPv6 net.IP
|
GatewayIPv6() net.IP
|
||||||
|
|
||||||
// Additional static routes for the sandbox. (Note that directly
|
// Additional static routes for the sandbox. (Note that directly
|
||||||
// connected routes are stored on the particular interface they refer to.)
|
// connected routes are stored on the particular interface they refer to.)
|
||||||
StaticRoutes []*types.StaticRoute
|
StaticRoutes() []*types.StaticRoute
|
||||||
|
|
||||||
// TODO: Add ip tables etc.
|
// TODO: Add ip tables etc.
|
||||||
}
|
}
|
||||||
|
@ -75,140 +95,32 @@ type Info struct {
|
||||||
// caller to use this information when moving interface SrcName from host
|
// caller to use this information when moving interface SrcName from host
|
||||||
// namespace to DstName in a different net namespace with the appropriate
|
// namespace to DstName in a different net namespace with the appropriate
|
||||||
// network settings.
|
// network settings.
|
||||||
type Interface struct {
|
type Interface interface {
|
||||||
// The name of the interface in the origin network namespace.
|
// The name of the interface in the origin network namespace.
|
||||||
SrcName string
|
SrcName() string
|
||||||
|
|
||||||
// The name that will be assigned to the interface once moves inside a
|
// The name that will be assigned to the interface once moves inside a
|
||||||
// network namespace. When the caller passes in a DstName, it is only
|
// network namespace. When the caller passes in a DstName, it is only
|
||||||
// expected to pass a prefix. The name will modified with an appropriately
|
// expected to pass a prefix. The name will modified with an appropriately
|
||||||
// auto-generated suffix.
|
// auto-generated suffix.
|
||||||
DstName string
|
DstName() string
|
||||||
|
|
||||||
// IPv4 address for the interface.
|
// IPv4 address for the interface.
|
||||||
Address *net.IPNet
|
Address() *net.IPNet
|
||||||
|
|
||||||
// IPv6 address for the interface.
|
// IPv6 address for the interface.
|
||||||
AddressIPv6 *net.IPNet
|
AddressIPv6() *net.IPNet
|
||||||
|
|
||||||
// IP routes for the interface.
|
// IP routes for the interface.
|
||||||
Routes []*net.IPNet
|
Routes() []*net.IPNet
|
||||||
}
|
|
||||||
|
// Bridge returns true if the interface is a bridge
|
||||||
// GetCopy returns a copy of this Interface structure
|
Bridge() bool
|
||||||
func (i *Interface) GetCopy() *Interface {
|
|
||||||
copiedRoutes := make([]*net.IPNet, len(i.Routes))
|
// Master returns the srcname of the master interface for this interface.
|
||||||
|
Master() string
|
||||||
for index := range i.Routes {
|
|
||||||
copiedRoutes[index] = types.GetIPNetCopy(i.Routes[index])
|
// Remove an interface from the sandbox by renaming to original name
|
||||||
}
|
// and moving it out of the sandbox.
|
||||||
|
Remove() error
|
||||||
return &Interface{
|
|
||||||
SrcName: i.SrcName,
|
|
||||||
DstName: i.DstName,
|
|
||||||
Address: types.GetIPNetCopy(i.Address),
|
|
||||||
AddressIPv6: types.GetIPNetCopy(i.AddressIPv6),
|
|
||||||
Routes: copiedRoutes,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equal checks if this instance of Interface is equal to the passed one
|
|
||||||
func (i *Interface) Equal(o *Interface) bool {
|
|
||||||
if i == o {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if o == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if i.SrcName != o.SrcName || i.DstName != o.DstName {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !types.CompareIPNet(i.Address, o.Address) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !types.CompareIPNet(i.AddressIPv6, o.AddressIPv6) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(i.Routes) != len(o.Routes) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for index := range i.Routes {
|
|
||||||
if !types.CompareIPNet(i.Routes[index], o.Routes[index]) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCopy returns a copy of this SandboxInfo structure
|
|
||||||
func (s *Info) GetCopy() *Info {
|
|
||||||
list := make([]*Interface, len(s.Interfaces))
|
|
||||||
for i, iface := range s.Interfaces {
|
|
||||||
list[i] = iface.GetCopy()
|
|
||||||
}
|
|
||||||
gw := types.GetIPCopy(s.Gateway)
|
|
||||||
gw6 := types.GetIPCopy(s.GatewayIPv6)
|
|
||||||
|
|
||||||
routes := make([]*types.StaticRoute, len(s.StaticRoutes))
|
|
||||||
for i, r := range s.StaticRoutes {
|
|
||||||
routes[i] = r.GetCopy()
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Info{Interfaces: list,
|
|
||||||
Gateway: gw,
|
|
||||||
GatewayIPv6: gw6,
|
|
||||||
StaticRoutes: routes}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equal checks if this instance of SandboxInfo is equal to the passed one
|
|
||||||
func (s *Info) Equal(o *Info) bool {
|
|
||||||
if s == o {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if o == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !s.Gateway.Equal(o.Gateway) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !s.GatewayIPv6.Equal(o.GatewayIPv6) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (s.Interfaces == nil && o.Interfaces != nil) ||
|
|
||||||
(s.Interfaces != nil && o.Interfaces == nil) ||
|
|
||||||
(len(s.Interfaces) != len(o.Interfaces)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: At the moment, the two lists must be in the same order
|
|
||||||
for i := 0; i < len(s.Interfaces); i++ {
|
|
||||||
if !s.Interfaces[i].Equal(o.Interfaces[i]) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for index := range s.StaticRoutes {
|
|
||||||
ss := s.StaticRoutes[index]
|
|
||||||
oo := o.StaticRoutes[index]
|
|
||||||
if !types.CompareIPNet(ss.Destination, oo.Destination) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !ss.NextHop.Equal(oo.NextHop) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@ func newKey(t *testing.T) (string, error) {
|
||||||
return name, nil
|
return name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newInfo(t *testing.T) (*Info, error) {
|
func newInfo(t *testing.T) (Sandbox, error) {
|
||||||
veth := &netlink.Veth{
|
veth := &netlink.Veth{
|
||||||
LinkAttrs: netlink.LinkAttrs{Name: vethName1, TxQLen: 0},
|
LinkAttrs: netlink.LinkAttrs{Name: vethName1, TxQLen: 0},
|
||||||
PeerName: vethName2}
|
PeerName: vethName2}
|
||||||
|
@ -50,31 +50,36 @@ func newInfo(t *testing.T) (*Info, error) {
|
||||||
|
|
||||||
// Store the sandbox side pipe interface
|
// Store the sandbox side pipe interface
|
||||||
// This is needed for cleanup on DeleteEndpoint()
|
// This is needed for cleanup on DeleteEndpoint()
|
||||||
intf1 := &Interface{}
|
intf1 := &nwIface{}
|
||||||
intf1.SrcName = vethName2
|
intf1.srcName = vethName2
|
||||||
intf1.DstName = sboxIfaceName
|
intf1.dstName = sboxIfaceName
|
||||||
|
|
||||||
ip4, addr, err := net.ParseCIDR("192.168.1.100/24")
|
ip4, addr, err := net.ParseCIDR("192.168.1.100/24")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
intf1.Address = addr
|
intf1.address = addr
|
||||||
intf1.Address.IP = ip4
|
intf1.address.IP = ip4
|
||||||
|
|
||||||
// ip6, addrv6, err := net.ParseCIDR("2001:DB8::ABCD/48")
|
// ip6, addrv6, err := net.ParseCIDR("2001:DB8::ABCD/48")
|
||||||
ip6, addrv6, err := net.ParseCIDR("fe80::2/64")
|
ip6, addrv6, err := net.ParseCIDR("fe80::2/64")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
intf1.AddressIPv6 = addrv6
|
intf1.addressIPv6 = addrv6
|
||||||
intf1.AddressIPv6.IP = ip6
|
intf1.addressIPv6.IP = ip6
|
||||||
|
|
||||||
_, route, err := net.ParseCIDR("192.168.2.1/32")
|
_, route, err := net.ParseCIDR("192.168.2.1/32")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
intf1.Routes = []*net.IPNet{route}
|
intf1.routes = []*net.IPNet{route}
|
||||||
|
|
||||||
|
intf2 := &nwIface{}
|
||||||
|
intf2.srcName = "testbridge"
|
||||||
|
intf2.dstName = sboxIfaceName
|
||||||
|
intf2.bridge = true
|
||||||
|
|
||||||
veth = &netlink.Veth{
|
veth = &netlink.Veth{
|
||||||
LinkAttrs: netlink.LinkAttrs{Name: vethName3, TxQLen: 0},
|
LinkAttrs: netlink.LinkAttrs{Name: vethName3, TxQLen: 0},
|
||||||
|
@ -84,36 +89,21 @@ func newInfo(t *testing.T) (*Info, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
intf2 := &Interface{}
|
intf3 := &nwIface{}
|
||||||
intf2.SrcName = vethName4
|
intf3.srcName = vethName4
|
||||||
intf2.DstName = sboxIfaceName
|
intf3.dstName = sboxIfaceName
|
||||||
|
intf3.master = "testbridge"
|
||||||
|
|
||||||
ip4, addr, err = net.ParseCIDR("192.168.2.100/24")
|
info := &networkNamespace{iFaces: []*nwIface{intf1, intf2, intf3}}
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
intf2.Address = addr
|
|
||||||
intf2.Address.IP = ip4
|
|
||||||
|
|
||||||
// ip6, addrv6, err := net.ParseCIDR("2001:DB8::ABCD/48")
|
info.gw = net.ParseIP("192.168.1.1")
|
||||||
ip6, addrv6, err = net.ParseCIDR("fe80::3/64")
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
intf2.AddressIPv6 = addrv6
|
|
||||||
intf2.AddressIPv6.IP = ip6
|
|
||||||
|
|
||||||
sinfo := &Info{Interfaces: []*Interface{intf1, intf2}}
|
|
||||||
|
|
||||||
sinfo.Gateway = net.ParseIP("192.168.1.1")
|
|
||||||
// sinfo.GatewayIPv6 = net.ParseIP("2001:DB8::1")
|
// sinfo.GatewayIPv6 = net.ParseIP("2001:DB8::1")
|
||||||
sinfo.GatewayIPv6 = net.ParseIP("fe80::1")
|
info.gwv6 = net.ParseIP("fe80::1")
|
||||||
|
|
||||||
return sinfo, nil
|
return info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func verifySandbox(t *testing.T, s Sandbox) {
|
func verifySandbox(t *testing.T, s Sandbox, ifaceSuffixes []string) {
|
||||||
_, ok := s.(*networkNamespace)
|
_, ok := s.(*networkNamespace)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("The sandox interface returned is not of type networkNamespace")
|
t.Fatalf("The sandox interface returned is not of type networkNamespace")
|
||||||
|
@ -140,16 +130,12 @@ func verifySandbox(t *testing.T, s Sandbox) {
|
||||||
}
|
}
|
||||||
defer netns.Set(origns)
|
defer netns.Set(origns)
|
||||||
|
|
||||||
_, err = netlink.LinkByName(sboxIfaceName + "0")
|
for _, suffix := range ifaceSuffixes {
|
||||||
if err != nil {
|
_, err = netlink.LinkByName(sboxIfaceName + suffix)
|
||||||
t.Fatalf("Could not find the interface %s inside the sandbox: %v", sboxIfaceName+"0",
|
if err != nil {
|
||||||
err)
|
t.Fatalf("Could not find the interface %s inside the sandbox: %v",
|
||||||
}
|
sboxIfaceName+suffix, err)
|
||||||
|
}
|
||||||
_, err = netlink.LinkByName(sboxIfaceName + "1")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Could not find the interface %s inside the sandbox: %v", sboxIfaceName+"1",
|
|
||||||
err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
package sandbox
|
package sandbox
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/reexec"
|
"github.com/docker/docker/pkg/reexec"
|
||||||
|
"github.com/docker/libnetwork/netutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
|
@ -16,6 +17,8 @@ func TestMain(m *testing.M) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSandboxCreate(t *testing.T) {
|
func TestSandboxCreate(t *testing.T) {
|
||||||
|
defer netutils.SetupTestNetNS(t)()
|
||||||
|
|
||||||
key, err := newKey(t)
|
key, err := newKey(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to obtain a key: %v", err)
|
t.Fatalf("Failed to obtain a key: %v", err)
|
||||||
|
@ -25,39 +28,50 @@ func TestSandboxCreate(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create a new sandbox: %v", err)
|
t.Fatalf("Failed to create a new sandbox: %v", err)
|
||||||
}
|
}
|
||||||
|
runtime.LockOSThread()
|
||||||
|
|
||||||
if s.Key() != key {
|
if s.Key() != key {
|
||||||
t.Fatalf("s.Key() returned %s. Expected %s", s.Key(), key)
|
t.Fatalf("s.Key() returned %s. Expected %s", s.Key(), key)
|
||||||
}
|
}
|
||||||
|
|
||||||
info, err := newInfo(t)
|
tbox, err := newInfo(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to generate new sandbox info: %v", err)
|
t.Fatalf("Failed to generate new sandbox info: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, i := range info.Interfaces {
|
for _, i := range tbox.Info().Interfaces() {
|
||||||
err = s.AddInterface(i)
|
err = s.AddInterface(i.SrcName(), i.DstName(),
|
||||||
|
tbox.InterfaceOptions().Bridge(i.Bridge()),
|
||||||
|
tbox.InterfaceOptions().Address(i.Address()),
|
||||||
|
tbox.InterfaceOptions().AddressIPv6(i.AddressIPv6()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to add interfaces to sandbox: %v", err)
|
t.Fatalf("Failed to add interfaces to sandbox: %v", err)
|
||||||
}
|
}
|
||||||
|
runtime.LockOSThread()
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.SetGateway(info.Gateway)
|
err = s.SetGateway(tbox.Info().Gateway())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to set gateway to sandbox: %v", err)
|
t.Fatalf("Failed to set gateway to sandbox: %v", err)
|
||||||
}
|
}
|
||||||
|
runtime.LockOSThread()
|
||||||
|
|
||||||
err = s.SetGatewayIPv6(info.GatewayIPv6)
|
err = s.SetGatewayIPv6(tbox.Info().GatewayIPv6())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to set ipv6 gateway to sandbox: %v", err)
|
t.Fatalf("Failed to set ipv6 gateway to sandbox: %v", err)
|
||||||
}
|
}
|
||||||
|
runtime.LockOSThread()
|
||||||
|
|
||||||
|
verifySandbox(t, s, []string{"0", "1", "2"})
|
||||||
|
runtime.LockOSThread()
|
||||||
|
|
||||||
verifySandbox(t, s)
|
|
||||||
s.Destroy()
|
s.Destroy()
|
||||||
verifyCleanup(t, s, true)
|
verifyCleanup(t, s, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSandboxCreateTwice(t *testing.T) {
|
func TestSandboxCreateTwice(t *testing.T) {
|
||||||
|
defer netutils.SetupTestNetNS(t)()
|
||||||
|
|
||||||
key, err := newKey(t)
|
key, err := newKey(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to obtain a key: %v", err)
|
t.Fatalf("Failed to obtain a key: %v", err)
|
||||||
|
@ -67,6 +81,7 @@ func TestSandboxCreateTwice(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create a new sandbox: %v", err)
|
t.Fatalf("Failed to create a new sandbox: %v", err)
|
||||||
}
|
}
|
||||||
|
runtime.LockOSThread()
|
||||||
|
|
||||||
// Create another sandbox with the same key to see if we handle it
|
// Create another sandbox with the same key to see if we handle it
|
||||||
// gracefully.
|
// gracefully.
|
||||||
|
@ -74,6 +89,7 @@ func TestSandboxCreateTwice(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create a new sandbox: %v", err)
|
t.Fatalf("Failed to create a new sandbox: %v", err)
|
||||||
}
|
}
|
||||||
|
runtime.LockOSThread()
|
||||||
s.Destroy()
|
s.Destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,6 +111,8 @@ func TestSandboxGC(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddRemoveInterface(t *testing.T) {
|
func TestAddRemoveInterface(t *testing.T) {
|
||||||
|
defer netutils.SetupTestNetNS(t)()
|
||||||
|
|
||||||
key, err := newKey(t)
|
key, err := newKey(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to obtain a key: %v", err)
|
t.Fatalf("Failed to obtain a key: %v", err)
|
||||||
|
@ -104,128 +122,51 @@ func TestAddRemoveInterface(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create a new sandbox: %v", err)
|
t.Fatalf("Failed to create a new sandbox: %v", err)
|
||||||
}
|
}
|
||||||
|
runtime.LockOSThread()
|
||||||
|
|
||||||
if s.Key() != key {
|
if s.Key() != key {
|
||||||
t.Fatalf("s.Key() returned %s. Expected %s", s.Key(), key)
|
t.Fatalf("s.Key() returned %s. Expected %s", s.Key(), key)
|
||||||
}
|
}
|
||||||
|
|
||||||
info, err := newInfo(t)
|
tbox, err := newInfo(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to generate new sandbox info: %v", err)
|
t.Fatalf("Failed to generate new sandbox info: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, i := range info.Interfaces {
|
for _, i := range tbox.Info().Interfaces() {
|
||||||
err = s.AddInterface(i)
|
err = s.AddInterface(i.SrcName(), i.DstName(),
|
||||||
|
tbox.InterfaceOptions().Bridge(i.Bridge()),
|
||||||
|
tbox.InterfaceOptions().Address(i.Address()),
|
||||||
|
tbox.InterfaceOptions().AddressIPv6(i.AddressIPv6()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to add interfaces to sandbox: %v", err)
|
t.Fatalf("Failed to add interfaces to sandbox: %v", err)
|
||||||
}
|
}
|
||||||
|
runtime.LockOSThread()
|
||||||
}
|
}
|
||||||
|
|
||||||
interfaces := s.Interfaces()
|
verifySandbox(t, s, []string{"0", "1", "2"})
|
||||||
if !(interfaces[0].Equal(info.Interfaces[0]) && interfaces[1].Equal(info.Interfaces[1])) {
|
runtime.LockOSThread()
|
||||||
t.Fatalf("Failed to update Sandbox.sinfo.Interfaces in AddInterfaces")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.RemoveInterface(info.Interfaces[0]); err != nil {
|
interfaces := s.Info().Interfaces()
|
||||||
|
if err := interfaces[0].Remove(); err != nil {
|
||||||
t.Fatalf("Failed to remove interfaces from sandbox: %v", err)
|
t.Fatalf("Failed to remove interfaces from sandbox: %v", err)
|
||||||
}
|
}
|
||||||
|
runtime.LockOSThread()
|
||||||
|
|
||||||
if !s.Interfaces()[0].Equal(info.Interfaces[1]) {
|
verifySandbox(t, s, []string{"1", "2"})
|
||||||
t.Fatalf("Failed to update the sanbox.sinfo.Interfaces in RemoveInterferce")
|
runtime.LockOSThread()
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.AddInterface(info.Interfaces[0]); err != nil {
|
i := tbox.Info().Interfaces()[0]
|
||||||
|
if err := s.AddInterface(i.SrcName(), i.DstName(),
|
||||||
|
tbox.InterfaceOptions().Bridge(i.Bridge()),
|
||||||
|
tbox.InterfaceOptions().Address(i.Address()),
|
||||||
|
tbox.InterfaceOptions().AddressIPv6(i.AddressIPv6())); err != nil {
|
||||||
t.Fatalf("Failed to add interfaces to sandbox: %v", err)
|
t.Fatalf("Failed to add interfaces to sandbox: %v", err)
|
||||||
}
|
}
|
||||||
|
runtime.LockOSThread()
|
||||||
|
|
||||||
interfaces = s.Interfaces()
|
verifySandbox(t, s, []string{"1", "2", "3"})
|
||||||
if !(interfaces[0].Equal(info.Interfaces[1]) && interfaces[1].Equal(info.Interfaces[0])) {
|
runtime.LockOSThread()
|
||||||
t.Fatalf("Failed to update Sandbox.sinfo.Interfaces in AddInterfaces")
|
|
||||||
}
|
|
||||||
|
|
||||||
s.Destroy()
|
s.Destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInterfaceEqual(t *testing.T) {
|
|
||||||
list := getInterfaceList()
|
|
||||||
|
|
||||||
if !list[0].Equal(list[0]) {
|
|
||||||
t.Fatalf("Interface.Equal() returned false negative")
|
|
||||||
}
|
|
||||||
|
|
||||||
if list[0].Equal(list[1]) {
|
|
||||||
t.Fatalf("Interface.Equal() returned false positive")
|
|
||||||
}
|
|
||||||
|
|
||||||
if list[0].Equal(list[1]) != list[1].Equal(list[0]) {
|
|
||||||
t.Fatalf("Interface.Equal() failed commutative check")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSandboxInfoEqual(t *testing.T) {
|
|
||||||
si1 := &Info{Interfaces: getInterfaceList(), Gateway: net.ParseIP("192.168.1.254"), GatewayIPv6: net.ParseIP("2001:2345::abcd:8889")}
|
|
||||||
si2 := &Info{Interfaces: getInterfaceList(), Gateway: net.ParseIP("172.18.255.254"), GatewayIPv6: net.ParseIP("2001:2345::abcd:8888")}
|
|
||||||
|
|
||||||
if !si1.Equal(si1) {
|
|
||||||
t.Fatalf("Info.Equal() returned false negative")
|
|
||||||
}
|
|
||||||
|
|
||||||
if si1.Equal(si2) {
|
|
||||||
t.Fatalf("Info.Equal() returned false positive")
|
|
||||||
}
|
|
||||||
|
|
||||||
if si1.Equal(si2) != si2.Equal(si1) {
|
|
||||||
t.Fatalf("Info.Equal() failed commutative check")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInterfaceCopy(t *testing.T) {
|
|
||||||
for _, iface := range getInterfaceList() {
|
|
||||||
cp := iface.GetCopy()
|
|
||||||
|
|
||||||
if !iface.Equal(cp) {
|
|
||||||
t.Fatalf("Failed to return a copy of Interface")
|
|
||||||
}
|
|
||||||
|
|
||||||
if iface == cp {
|
|
||||||
t.Fatalf("Failed to return a true copy of Interface")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSandboxInfoCopy(t *testing.T) {
|
|
||||||
si := Info{Interfaces: getInterfaceList(), Gateway: net.ParseIP("192.168.1.254"), GatewayIPv6: net.ParseIP("2001:2345::abcd:8889")}
|
|
||||||
cp := si.GetCopy()
|
|
||||||
|
|
||||||
if !si.Equal(cp) {
|
|
||||||
t.Fatalf("Failed to return a copy of Info")
|
|
||||||
}
|
|
||||||
|
|
||||||
if &si == cp {
|
|
||||||
t.Fatalf("Failed to return a true copy of Info")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getInterfaceList() []*Interface {
|
|
||||||
_, netv4a, _ := net.ParseCIDR("192.168.30.1/24")
|
|
||||||
_, netv4b, _ := net.ParseCIDR("172.18.255.2/23")
|
|
||||||
_, netv6a, _ := net.ParseCIDR("2001:2345::abcd:8888/80")
|
|
||||||
_, netv6b, _ := net.ParseCIDR("2001:2345::abcd:8889/80")
|
|
||||||
|
|
||||||
return []*Interface{
|
|
||||||
&Interface{
|
|
||||||
SrcName: "veth1234567",
|
|
||||||
DstName: "eth0",
|
|
||||||
Address: netv4a,
|
|
||||||
AddressIPv6: netv6a,
|
|
||||||
Routes: []*net.IPNet{netv4a, netv6a},
|
|
||||||
},
|
|
||||||
&Interface{
|
|
||||||
SrcName: "veth7654321",
|
|
||||||
DstName: "eth1",
|
|
||||||
Address: netv4b,
|
|
||||||
AddressIPv6: netv6b,
|
|
||||||
Routes: []*net.IPNet{netv4b, netv6b},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -83,17 +83,16 @@ func (s *sandboxData) addEndpoint(ep *endpoint) error {
|
||||||
|
|
||||||
sb := s.sandbox()
|
sb := s.sandbox()
|
||||||
for _, i := range ifaces {
|
for _, i := range ifaces {
|
||||||
iface := &sandbox.Interface{
|
var ifaceOptions []sandbox.IfaceOption
|
||||||
SrcName: i.srcName,
|
|
||||||
DstName: i.dstPrefix,
|
ifaceOptions = append(ifaceOptions, sb.InterfaceOptions().Address(&i.addr),
|
||||||
Address: &i.addr,
|
sb.InterfaceOptions().Routes(i.routes))
|
||||||
Routes: i.routes,
|
|
||||||
}
|
|
||||||
if i.addrv6.IP.To16() != nil {
|
if i.addrv6.IP.To16() != nil {
|
||||||
iface.AddressIPv6 = &i.addrv6
|
ifaceOptions = append(ifaceOptions,
|
||||||
|
sb.InterfaceOptions().AddressIPv6(&i.addrv6))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := sb.AddInterface(iface); err != nil {
|
if err := sb.AddInterface(i.srcName, i.dstPrefix, ifaceOptions...); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,10 +130,10 @@ func (s *sandboxData) rmEndpoint(ep *endpoint) int {
|
||||||
ep.Unlock()
|
ep.Unlock()
|
||||||
|
|
||||||
sb := s.sandbox()
|
sb := s.sandbox()
|
||||||
for _, i := range sb.Interfaces() {
|
for _, i := range sb.Info().Interfaces() {
|
||||||
// Only remove the interfaces owned by this endpoint from the sandbox.
|
// Only remove the interfaces owned by this endpoint from the sandbox.
|
||||||
if ep.hasInterface(i.SrcName) {
|
if ep.hasInterface(i.SrcName()) {
|
||||||
if err := sb.RemoveInterface(i); err != nil {
|
if err := i.Remove(); err != nil {
|
||||||
logrus.Debugf("Remove interface failed: %v", err)
|
logrus.Debugf("Remove interface failed: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"flag"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
_ "github.com/docker/libnetwork/netutils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var runningInContainer = flag.Bool("incontainer", false, "Indicates if the test is running in a container")
|
||||||
|
|
||||||
func TestErrorConstructors(t *testing.T) {
|
func TestErrorConstructors(t *testing.T) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue