VMware vSphere Integrated Containers provider (#206)

* Add Virtual Kubelet provider for VIC

Initial virtual kubelet provider for VMware VIC.  This provider currently
handles creating and starting of a pod VM via the VIC portlayer and persona
server.  Image store handling via the VIC persona server.  This provider
currently requires the feature/wolfpack branch of VIC.

* Added pod stop and delete.  Also added node capacity.

Added the ability to stop and delete pod VMs via VIC.  Also retrieve
node capacity information from the VCH.

* Cleanup and readme file

Some file clean up and added a Readme.md markdown file for the VIC
provider.

* Cleaned up errors, added function comments, moved operation code

1. Cleaned up error handling.  Set standard for creating errors.
2. Added method prototype comments for all interface functions.
3. Moved PodCreator, PodStarter, PodStopper, and PodDeleter to a new folder.

* Add mocking code and unit tests for podcache, podcreator, and podstarter

Used the unit test framework used in VIC to handle assertions in the provider's
unit test.  Mocking code generated using OSS project mockery, which is compatible
with the testify assertion framework.

* Vendored packages for the VIC provider

Requires feature/wolfpack branch of VIC and a few specific commit sha of
projects used within VIC.

* Implementation of POD Stopper and Deleter unit tests (#4)

* Updated files for initial PR
This commit is contained in:
Loc Nguyen
2018-06-04 15:41:32 -07:00
committed by Ria Bhatia
parent 98a111e8b7
commit 513cebe7b7
6296 changed files with 1123685 additions and 8 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,211 @@
package bridge
import (
"encoding/json"
"fmt"
"net"
"github.com/Sirupsen/logrus"
"github.com/docker/libkv/store"
"github.com/docker/libkv/store/boltdb"
"github.com/docker/libnetwork/datastore"
"github.com/docker/libnetwork/netlabel"
"github.com/docker/libnetwork/types"
)
const bridgePrefix = "bridge"
func (d *driver) initStore(option map[string]interface{}) error {
var err error
provider, provOk := option[netlabel.LocalKVProvider]
provURL, urlOk := option[netlabel.LocalKVProviderURL]
if provOk && urlOk {
cfg := &datastore.ScopeCfg{
Client: datastore.ScopeClientCfg{
Provider: provider.(string),
Address: provURL.(string),
},
}
provConfig, confOk := option[netlabel.LocalKVProviderConfig]
if confOk {
cfg.Client.Config = provConfig.(*store.Config)
}
d.store, err = datastore.NewDataStore(datastore.LocalScope, cfg)
if err != nil {
return fmt.Errorf("bridge driver failed to initialize data store: %v", err)
}
return d.populateNetworks()
}
return nil
}
func (d *driver) populateNetworks() error {
kvol, err := d.store.List(datastore.Key(bridgePrefix), &networkConfiguration{})
if err != nil && err != datastore.ErrKeyNotFound && err != boltdb.ErrBoltBucketNotFound {
return fmt.Errorf("failed to get bridge network configurations from store: %v", err)
}
// It's normal for network configuration state to be empty. Just return.
if err == datastore.ErrKeyNotFound {
return nil
}
for _, kvo := range kvol {
ncfg := kvo.(*networkConfiguration)
if err = d.createNetwork(ncfg); err != nil {
logrus.Warnf("could not create bridge network for id %s bridge name %s while booting up from persistent state", ncfg.ID, ncfg.BridgeName)
}
}
return nil
}
func (d *driver) storeUpdate(kvObject datastore.KVObject) error {
if d.store == nil {
logrus.Warnf("bridge store not initialized. kv object %s is not added to the store", datastore.Key(kvObject.Key()...))
return nil
}
if err := d.store.PutObjectAtomic(kvObject); err != nil {
return fmt.Errorf("failed to update bridge store for object type %T: %v", kvObject, err)
}
return nil
}
func (d *driver) storeDelete(kvObject datastore.KVObject) error {
if d.store == nil {
logrus.Debugf("bridge store not initialized. kv object %s is not deleted from store", datastore.Key(kvObject.Key()...))
return nil
}
retry:
if err := d.store.DeleteObjectAtomic(kvObject); err != nil {
if err == datastore.ErrKeyModified {
if err := d.store.GetObject(datastore.Key(kvObject.Key()...), kvObject); err != nil {
return fmt.Errorf("could not update the kvobject to latest when trying to delete: %v", err)
}
goto retry
}
return err
}
return nil
}
func (ncfg *networkConfiguration) MarshalJSON() ([]byte, error) {
nMap := make(map[string]interface{})
nMap["ID"] = ncfg.ID
nMap["BridgeName"] = ncfg.BridgeName
nMap["EnableIPv6"] = ncfg.EnableIPv6
nMap["EnableIPMasquerade"] = ncfg.EnableIPMasquerade
nMap["EnableICC"] = ncfg.EnableICC
nMap["Mtu"] = ncfg.Mtu
nMap["DefaultBridge"] = ncfg.DefaultBridge
nMap["DefaultBindingIP"] = ncfg.DefaultBindingIP.String()
nMap["DefaultGatewayIPv4"] = ncfg.DefaultGatewayIPv4.String()
nMap["DefaultGatewayIPv6"] = ncfg.DefaultGatewayIPv6.String()
if ncfg.AddressIPv4 != nil {
nMap["AddressIPv4"] = ncfg.AddressIPv4.String()
}
if ncfg.AddressIPv6 != nil {
nMap["AddressIPv6"] = ncfg.AddressIPv6.String()
}
return json.Marshal(nMap)
}
func (ncfg *networkConfiguration) UnmarshalJSON(b []byte) error {
var (
err error
nMap map[string]interface{}
)
if err = json.Unmarshal(b, &nMap); err != nil {
return err
}
if v, ok := nMap["AddressIPv4"]; ok {
if ncfg.AddressIPv4, err = types.ParseCIDR(v.(string)); err != nil {
return types.InternalErrorf("failed to decode bridge network address IPv4 after json unmarshal: %s", v.(string))
}
}
if v, ok := nMap["AddressIPv6"]; ok {
if ncfg.AddressIPv6, err = types.ParseCIDR(v.(string)); err != nil {
return types.InternalErrorf("failed to decode bridge network address IPv6 after json unmarshal: %s", v.(string))
}
}
ncfg.DefaultBridge = nMap["DefaultBridge"].(bool)
ncfg.DefaultBindingIP = net.ParseIP(nMap["DefaultBindingIP"].(string))
ncfg.DefaultGatewayIPv4 = net.ParseIP(nMap["DefaultGatewayIPv4"].(string))
ncfg.DefaultGatewayIPv6 = net.ParseIP(nMap["DefaultGatewayIPv6"].(string))
ncfg.ID = nMap["ID"].(string)
ncfg.BridgeName = nMap["BridgeName"].(string)
ncfg.EnableIPv6 = nMap["EnableIPv6"].(bool)
ncfg.EnableIPMasquerade = nMap["EnableIPMasquerade"].(bool)
ncfg.EnableICC = nMap["EnableICC"].(bool)
ncfg.Mtu = int(nMap["Mtu"].(float64))
return nil
}
func (ncfg *networkConfiguration) Key() []string {
return []string{bridgePrefix, ncfg.ID}
}
func (ncfg *networkConfiguration) KeyPrefix() []string {
return []string{bridgePrefix}
}
func (ncfg *networkConfiguration) Value() []byte {
b, err := json.Marshal(ncfg)
if err != nil {
return nil
}
return b
}
func (ncfg *networkConfiguration) SetValue(value []byte) error {
return json.Unmarshal(value, ncfg)
}
func (ncfg *networkConfiguration) Index() uint64 {
return ncfg.dbIndex
}
func (ncfg *networkConfiguration) SetIndex(index uint64) {
ncfg.dbIndex = index
ncfg.dbExists = true
}
func (ncfg *networkConfiguration) Exists() bool {
return ncfg.dbExists
}
func (ncfg *networkConfiguration) Skip() bool {
return ncfg.DefaultBridge
}
func (ncfg *networkConfiguration) New() datastore.KVObject {
return &networkConfiguration{}
}
func (ncfg *networkConfiguration) CopyTo(o datastore.KVObject) error {
dstNcfg := o.(*networkConfiguration)
*dstNcfg = *ncfg
return nil
}
func (ncfg *networkConfiguration) DataScope() string {
return datastore.LocalScope
}

View File

@@ -0,0 +1,841 @@
package bridge
import (
"bytes"
"fmt"
"net"
"regexp"
"testing"
"github.com/docker/libnetwork/driverapi"
"github.com/docker/libnetwork/ipamutils"
"github.com/docker/libnetwork/iptables"
"github.com/docker/libnetwork/netlabel"
"github.com/docker/libnetwork/testutils"
"github.com/docker/libnetwork/types"
"github.com/vishvananda/netlink"
)
func getIPv4Data(t *testing.T) []driverapi.IPAMData {
ipd := driverapi.IPAMData{AddressSpace: "full"}
nw, _, err := ipamutils.ElectInterfaceAddresses("")
if err != nil {
t.Fatal(err)
}
ipd.Pool = nw
// Set network gateway to X.X.X.1
ipd.Gateway = types.GetIPNetCopy(nw)
ipd.Gateway.IP[len(ipd.Gateway.IP)-1] = 1
return []driverapi.IPAMData{ipd}
}
func TestCreateFullOptions(t *testing.T) {
defer testutils.SetupTestOSContext(t)()
d := newDriver()
config := &configuration{
EnableIPForwarding: true,
EnableIPTables: true,
}
// Test this scenario: Default gw address does not belong to
// container network and it's greater than bridge address
cnw, _ := types.ParseCIDR("172.16.122.0/24")
bnw, _ := types.ParseCIDR("172.16.0.0/24")
br, _ := types.ParseCIDR("172.16.0.1/16")
defgw, _ := types.ParseCIDR("172.16.0.100/16")
netConfig := &networkConfiguration{
BridgeName: DefaultBridgeName,
EnableIPv6: true,
}
genericOption := make(map[string]interface{})
genericOption[netlabel.GenericData] = config
if err := d.configure(genericOption); err != nil {
t.Fatalf("Failed to setup driver config: %v", err)
}
netOption := make(map[string]interface{})
netOption[netlabel.GenericData] = netConfig
ipdList := []driverapi.IPAMData{
driverapi.IPAMData{
Pool: bnw,
Gateway: br,
AuxAddresses: map[string]*net.IPNet{DefaultGatewayV4AuxKey: defgw},
},
}
err := d.CreateNetwork("dummy", netOption, ipdList, nil)
if err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
// Verify the IP address allocated for the endpoint belongs to the container network
epOptions := make(map[string]interface{})
te := newTestEndpoint(cnw, 10)
err = d.CreateEndpoint("dummy", "ep1", te.Interface(), epOptions)
if err != nil {
t.Fatalf("Failed to create an endpoint : %s", err.Error())
}
if !cnw.Contains(te.Interface().Address().IP) {
t.Fatalf("endpoint got assigned address outside of container network(%s): %s", cnw.String(), te.Interface().Address())
}
}
func TestCreateNoConfig(t *testing.T) {
defer testutils.SetupTestOSContext(t)()
d := newDriver()
netconfig := &networkConfiguration{BridgeName: DefaultBridgeName}
genericOption := make(map[string]interface{})
genericOption[netlabel.GenericData] = netconfig
if err := d.CreateNetwork("dummy", genericOption, getIPv4Data(t), nil); err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
}
func TestCreateFullOptionsLabels(t *testing.T) {
defer testutils.SetupTestOSContext(t)()
d := newDriver()
config := &configuration{
EnableIPForwarding: true,
}
genericOption := make(map[string]interface{})
genericOption[netlabel.GenericData] = config
if err := d.configure(genericOption); err != nil {
t.Fatalf("Failed to setup driver config: %v", err)
}
bndIPs := "127.0.0.1"
nwV6s := "2100:2400:2600:2700:2800::/80"
gwV6s := "2100:2400:2600:2700:2800::25/80"
nwV6, _ := types.ParseCIDR(nwV6s)
gwV6, _ := types.ParseCIDR(gwV6s)
labels := map[string]string{
BridgeName: DefaultBridgeName,
DefaultBridge: "true",
netlabel.EnableIPv6: "true",
EnableICC: "true",
EnableIPMasquerade: "true",
DefaultBindingIP: bndIPs,
}
netOption := make(map[string]interface{})
netOption[netlabel.GenericData] = labels
ipdList := getIPv4Data(t)
ipd6List := []driverapi.IPAMData{
driverapi.IPAMData{
Pool: nwV6,
AuxAddresses: map[string]*net.IPNet{
DefaultGatewayV6AuxKey: gwV6,
},
},
}
err := d.CreateNetwork("dummy", netOption, ipdList, ipd6List)
if err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
nw, ok := d.networks["dummy"]
if !ok {
t.Fatalf("Cannot find dummy network in bridge driver")
}
if nw.config.BridgeName != DefaultBridgeName {
t.Fatalf("incongruent name in bridge network")
}
if !nw.config.EnableIPv6 {
t.Fatalf("incongruent EnableIPv6 in bridge network")
}
if !nw.config.EnableICC {
t.Fatalf("incongruent EnableICC in bridge network")
}
if !nw.config.EnableIPMasquerade {
t.Fatalf("incongruent EnableIPMasquerade in bridge network")
}
bndIP := net.ParseIP(bndIPs)
if !bndIP.Equal(nw.config.DefaultBindingIP) {
t.Fatalf("Unexpected: %v", nw.config.DefaultBindingIP)
}
if !types.CompareIPNet(nw.config.AddressIPv6, nwV6) {
t.Fatalf("Unexpected: %v", nw.config.AddressIPv6)
}
if !gwV6.IP.Equal(nw.config.DefaultGatewayIPv6) {
t.Fatalf("Unexpected: %v", nw.config.DefaultGatewayIPv6)
}
// In short here we are testing --fixed-cidr-v6 daemon option
// plus --mac-address run option
mac, _ := net.ParseMAC("aa:bb:cc:dd:ee:ff")
epOptions := map[string]interface{}{netlabel.MacAddress: mac}
te := newTestEndpoint(ipdList[0].Pool, 20)
err = d.CreateEndpoint("dummy", "ep1", te.Interface(), epOptions)
if err != nil {
t.Fatal(err)
}
if !nwV6.Contains(te.Interface().AddressIPv6().IP) {
t.Fatalf("endpoint got assigned address outside of container network(%s): %s", nwV6.String(), te.Interface().AddressIPv6())
}
if te.Interface().AddressIPv6().IP.String() != "2100:2400:2600:2700:2800:aabb:ccdd:eeff" {
t.Fatalf("Unexpected endpoint IPv6 address: %v", te.Interface().AddressIPv6().IP)
}
}
func TestCreate(t *testing.T) {
defer testutils.SetupTestOSContext(t)()
d := newDriver()
if err := d.configure(nil); err != nil {
t.Fatalf("Failed to setup driver config: %v", err)
}
netconfig := &networkConfiguration{BridgeName: DefaultBridgeName}
genericOption := make(map[string]interface{})
genericOption[netlabel.GenericData] = netconfig
if err := d.CreateNetwork("dummy", genericOption, getIPv4Data(t), nil); err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
err := d.CreateNetwork("dummy", genericOption, getIPv4Data(t), nil)
if err == nil {
t.Fatalf("Expected bridge driver to refuse creation of second network with default name")
}
if _, ok := err.(types.ForbiddenError); !ok {
t.Fatalf("Creation of second network with default name failed with unexpected error type")
}
}
func TestCreateFail(t *testing.T) {
defer testutils.SetupTestOSContext(t)()
d := newDriver()
if err := d.configure(nil); err != nil {
t.Fatalf("Failed to setup driver config: %v", err)
}
netconfig := &networkConfiguration{BridgeName: "dummy0", DefaultBridge: true}
genericOption := make(map[string]interface{})
genericOption[netlabel.GenericData] = netconfig
if err := d.CreateNetwork("dummy", genericOption, getIPv4Data(t), nil); err == nil {
t.Fatal("Bridge creation was expected to fail")
}
}
func TestCreateMultipleNetworks(t *testing.T) {
defer testutils.SetupTestOSContext(t)()
d := newDriver()
config := &configuration{
EnableIPTables: true,
}
genericOption := make(map[string]interface{})
genericOption[netlabel.GenericData] = config
if err := d.configure(genericOption); err != nil {
t.Fatalf("Failed to setup driver config: %v", err)
}
config1 := &networkConfiguration{BridgeName: "net_test_1"}
genericOption = make(map[string]interface{})
genericOption[netlabel.GenericData] = config1
if err := d.CreateNetwork("1", genericOption, getIPv4Data(t), nil); err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
config2 := &networkConfiguration{BridgeName: "net_test_2"}
genericOption[netlabel.GenericData] = config2
if err := d.CreateNetwork("2", genericOption, getIPv4Data(t), nil); err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
// Verify the network isolation rules are installed, each network subnet should appear 2 times
verifyV4INCEntries(d.networks, 2, t)
config3 := &networkConfiguration{BridgeName: "net_test_3"}
genericOption[netlabel.GenericData] = config3
if err := d.CreateNetwork("3", genericOption, getIPv4Data(t), nil); err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
// Verify the network isolation rules are installed, each network subnet should appear 4 times
verifyV4INCEntries(d.networks, 6, t)
config4 := &networkConfiguration{BridgeName: "net_test_4"}
genericOption[netlabel.GenericData] = config4
if err := d.CreateNetwork("4", genericOption, getIPv4Data(t), nil); err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
// Now 6 times
verifyV4INCEntries(d.networks, 12, t)
d.DeleteNetwork("1")
verifyV4INCEntries(d.networks, 6, t)
d.DeleteNetwork("2")
verifyV4INCEntries(d.networks, 2, t)
d.DeleteNetwork("3")
verifyV4INCEntries(d.networks, 0, t)
d.DeleteNetwork("4")
verifyV4INCEntries(d.networks, 0, t)
}
func verifyV4INCEntries(networks map[string]*bridgeNetwork, numEntries int, t *testing.T) {
out, err := iptables.Raw("-nvL", IsolationChain)
if err != nil {
t.Fatal(err)
}
found := 0
for _, x := range networks {
for _, y := range networks {
if x == y {
continue
}
re := regexp.MustCompile(fmt.Sprintf("%s %s", x.config.BridgeName, y.config.BridgeName))
matches := re.FindAllString(string(out[:]), -1)
if len(matches) != 1 {
t.Fatalf("Cannot find expected inter-network isolation rules in IP Tables:\n%s", string(out[:]))
}
found++
}
}
if found != numEntries {
t.Fatalf("Cannot find expected number (%d) of inter-network isolation rules in IP Tables:\n%s\nFound %d", numEntries, string(out[:]), found)
}
}
type testInterface struct {
mac net.HardwareAddr
addr *net.IPNet
addrv6 *net.IPNet
srcName string
dstName string
}
type testEndpoint struct {
iface *testInterface
gw net.IP
gw6 net.IP
hostsPath string
resolvConfPath string
routes []types.StaticRoute
}
func newTestEndpoint(nw *net.IPNet, ordinal byte) *testEndpoint {
addr := types.GetIPNetCopy(nw)
addr.IP[len(addr.IP)-1] = ordinal
return &testEndpoint{iface: &testInterface{addr: addr}}
}
func (te *testEndpoint) Interface() driverapi.InterfaceInfo {
if te.iface != nil {
return te.iface
}
return nil
}
func (i *testInterface) MacAddress() net.HardwareAddr {
return i.mac
}
func (i *testInterface) Address() *net.IPNet {
return i.addr
}
func (i *testInterface) AddressIPv6() *net.IPNet {
return i.addrv6
}
func (i *testInterface) SetMacAddress(mac net.HardwareAddr) error {
if i.mac != nil {
return types.ForbiddenErrorf("endpoint interface MAC address present (%s). Cannot be modified with %s.", i.mac, mac)
}
if mac == nil {
return types.BadRequestErrorf("tried to set nil MAC address to endpoint interface")
}
i.mac = types.GetMacCopy(mac)
return nil
}
func (i *testInterface) SetIPAddress(address *net.IPNet) error {
if address.IP == nil {
return types.BadRequestErrorf("tried to set nil IP address to endpoint interface")
}
if address.IP.To4() == nil {
return setAddress(&i.addrv6, address)
}
return setAddress(&i.addr, address)
}
func setAddress(ifaceAddr **net.IPNet, address *net.IPNet) error {
if *ifaceAddr != nil {
return types.ForbiddenErrorf("endpoint interface IP present (%s). Cannot be modified with (%s).", *ifaceAddr, address)
}
*ifaceAddr = types.GetIPNetCopy(address)
return nil
}
func (i *testInterface) SetNames(srcName string, dstName string) error {
i.srcName = srcName
i.dstName = dstName
return nil
}
func (te *testEndpoint) InterfaceName() driverapi.InterfaceNameInfo {
if te.iface != nil {
return te.iface
}
return nil
}
func (te *testEndpoint) SetGateway(gw net.IP) error {
te.gw = gw
return nil
}
func (te *testEndpoint) SetGatewayIPv6(gw6 net.IP) error {
te.gw6 = gw6
return nil
}
func (te *testEndpoint) AddStaticRoute(destination *net.IPNet, routeType int, nextHop net.IP) error {
te.routes = append(te.routes, types.StaticRoute{Destination: destination, RouteType: routeType, NextHop: nextHop})
return nil
}
func (te *testEndpoint) DisableGatewayService() {}
func TestQueryEndpointInfo(t *testing.T) {
testQueryEndpointInfo(t, true)
}
func TestQueryEndpointInfoHairpin(t *testing.T) {
testQueryEndpointInfo(t, false)
}
func testQueryEndpointInfo(t *testing.T, ulPxyEnabled bool) {
defer testutils.SetupTestOSContext(t)()
d := newDriver()
config := &configuration{
EnableIPTables: true,
EnableUserlandProxy: ulPxyEnabled,
}
genericOption := make(map[string]interface{})
genericOption[netlabel.GenericData] = config
if err := d.configure(genericOption); err != nil {
t.Fatalf("Failed to setup driver config: %v", err)
}
netconfig := &networkConfiguration{
BridgeName: DefaultBridgeName,
EnableICC: false,
}
genericOption = make(map[string]interface{})
genericOption[netlabel.GenericData] = netconfig
ipdList := getIPv4Data(t)
err := d.CreateNetwork("net1", genericOption, ipdList, nil)
if err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
portMappings := getPortMapping()
epOptions := make(map[string]interface{})
epOptions[netlabel.PortMap] = portMappings
te := newTestEndpoint(ipdList[0].Pool, 11)
err = d.CreateEndpoint("net1", "ep1", te.Interface(), epOptions)
if err != nil {
t.Fatalf("Failed to create an endpoint : %s", err.Error())
}
network, ok := d.networks["net1"]
if !ok {
t.Fatalf("Cannot find network %s inside driver", "net1")
}
ep, _ := network.endpoints["ep1"]
data, err := d.EndpointOperInfo(network.id, ep.id)
if err != nil {
t.Fatalf("Failed to ask for endpoint operational data: %v", err)
}
pmd, ok := data[netlabel.PortMap]
if !ok {
t.Fatalf("Endpoint operational data does not contain port mapping data")
}
pm, ok := pmd.([]types.PortBinding)
if !ok {
t.Fatalf("Unexpected format for port mapping in endpoint operational data")
}
if len(ep.portMapping) != len(pm) {
t.Fatalf("Incomplete data for port mapping in endpoint operational data")
}
for i, pb := range ep.portMapping {
if !pb.Equal(&pm[i]) {
t.Fatalf("Unexpected data for port mapping in endpoint operational data")
}
}
// Cleanup as host ports are there
err = network.releasePorts(ep)
if err != nil {
t.Fatalf("Failed to release mapped ports: %v", err)
}
}
func TestCreateLinkWithOptions(t *testing.T) {
defer testutils.SetupTestOSContext(t)()
d := newDriver()
if err := d.configure(nil); err != nil {
t.Fatalf("Failed to setup driver config: %v", err)
}
netconfig := &networkConfiguration{BridgeName: DefaultBridgeName}
netOptions := make(map[string]interface{})
netOptions[netlabel.GenericData] = netconfig
ipdList := getIPv4Data(t)
err := d.CreateNetwork("net1", netOptions, ipdList, nil)
if err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
mac := net.HardwareAddr([]byte{0x1e, 0x67, 0x66, 0x44, 0x55, 0x66})
epOptions := make(map[string]interface{})
epOptions[netlabel.MacAddress] = mac
te := newTestEndpoint(ipdList[0].Pool, 11)
err = d.CreateEndpoint("net1", "ep", te.Interface(), epOptions)
if err != nil {
t.Fatalf("Failed to create an endpoint: %s", err.Error())
}
err = d.Join("net1", "ep", "sbox", te, nil)
if err != nil {
t.Fatalf("Failed to join the endpoint: %v", err)
}
ifaceName := te.iface.srcName
veth, err := netlink.LinkByName(ifaceName)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(mac, veth.Attrs().HardwareAddr) {
t.Fatalf("Failed to parse and program endpoint configuration")
}
}
func getExposedPorts() []types.TransportPort {
return []types.TransportPort{
types.TransportPort{Proto: types.TCP, Port: uint16(5000)},
types.TransportPort{Proto: types.UDP, Port: uint16(400)},
types.TransportPort{Proto: types.TCP, Port: uint16(600)},
}
}
func getPortMapping() []types.PortBinding {
return []types.PortBinding{
types.PortBinding{Proto: types.TCP, Port: uint16(230), HostPort: uint16(23000)},
types.PortBinding{Proto: types.UDP, Port: uint16(200), HostPort: uint16(22000)},
types.PortBinding{Proto: types.TCP, Port: uint16(120), HostPort: uint16(12000)},
}
}
func TestLinkContainers(t *testing.T) {
defer testutils.SetupTestOSContext(t)()
d := newDriver()
config := &configuration{
EnableIPTables: true,
}
genericOption := make(map[string]interface{})
genericOption[netlabel.GenericData] = config
if err := d.configure(genericOption); err != nil {
t.Fatalf("Failed to setup driver config: %v", err)
}
netconfig := &networkConfiguration{
BridgeName: DefaultBridgeName,
EnableICC: false,
}
genericOption = make(map[string]interface{})
genericOption[netlabel.GenericData] = netconfig
ipdList := getIPv4Data(t)
err := d.CreateNetwork("net1", genericOption, ipdList, nil)
if err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
exposedPorts := getExposedPorts()
epOptions := make(map[string]interface{})
epOptions[netlabel.ExposedPorts] = exposedPorts
te1 := newTestEndpoint(ipdList[0].Pool, 11)
err = d.CreateEndpoint("net1", "ep1", te1.Interface(), epOptions)
if err != nil {
t.Fatalf("Failed to create an endpoint : %s", err.Error())
}
addr1 := te1.iface.addr
if addr1.IP.To4() == nil {
t.Fatalf("No Ipv4 address assigned to the endpoint: ep1")
}
te2 := newTestEndpoint(ipdList[0].Pool, 22)
err = d.CreateEndpoint("net1", "ep2", te2.Interface(), nil)
if err != nil {
t.Fatalf("Failed to create an endpoint : %s", err.Error())
}
addr2 := te2.iface.addr
if addr2.IP.To4() == nil {
t.Fatalf("No Ipv4 address assigned to the endpoint: ep2")
}
ce := []string{"ep1"}
cConfig := &containerConfiguration{ChildEndpoints: ce}
genericOption = make(map[string]interface{})
genericOption[netlabel.GenericData] = cConfig
err = d.Join("net1", "ep2", "", te2, genericOption)
if err != nil {
t.Fatalf("Failed to link ep1 and ep2")
}
out, err := iptables.Raw("-L", DockerChain)
for _, pm := range exposedPorts {
regex := fmt.Sprintf("%s dpt:%d", pm.Proto.String(), pm.Port)
re := regexp.MustCompile(regex)
matches := re.FindAllString(string(out[:]), -1)
if len(matches) != 1 {
t.Fatalf("IP Tables programming failed %s", string(out[:]))
}
regex = fmt.Sprintf("%s spt:%d", pm.Proto.String(), pm.Port)
matched, _ := regexp.MatchString(regex, string(out[:]))
if !matched {
t.Fatalf("IP Tables programming failed %s", string(out[:]))
}
}
err = d.Leave("net1", "ep2")
if err != nil {
t.Fatalf("Failed to unlink ep1 and ep2")
}
out, err = iptables.Raw("-L", DockerChain)
for _, pm := range exposedPorts {
regex := fmt.Sprintf("%s dpt:%d", pm.Proto.String(), pm.Port)
re := regexp.MustCompile(regex)
matches := re.FindAllString(string(out[:]), -1)
if len(matches) != 0 {
t.Fatalf("Leave should have deleted relevant IPTables rules %s", string(out[:]))
}
regex = fmt.Sprintf("%s spt:%d", pm.Proto.String(), pm.Port)
matched, _ := regexp.MatchString(regex, string(out[:]))
if matched {
t.Fatalf("Leave should have deleted relevant IPTables rules %s", string(out[:]))
}
}
// Error condition test with an invalid endpoint-id "ep4"
ce = []string{"ep1", "ep4"}
cConfig = &containerConfiguration{ChildEndpoints: ce}
genericOption = make(map[string]interface{})
genericOption[netlabel.GenericData] = cConfig
err = d.Join("net1", "ep2", "", te2, genericOption)
if err != nil {
out, err = iptables.Raw("-L", DockerChain)
for _, pm := range exposedPorts {
regex := fmt.Sprintf("%s dpt:%d", pm.Proto.String(), pm.Port)
re := regexp.MustCompile(regex)
matches := re.FindAllString(string(out[:]), -1)
if len(matches) != 0 {
t.Fatalf("Error handling should rollback relevant IPTables rules %s", string(out[:]))
}
regex = fmt.Sprintf("%s spt:%d", pm.Proto.String(), pm.Port)
matched, _ := regexp.MatchString(regex, string(out[:]))
if matched {
t.Fatalf("Error handling should rollback relevant IPTables rules %s", string(out[:]))
}
}
} else {
t.Fatalf("Expected Join to fail given link conditions are not satisfied")
}
}
func TestValidateConfig(t *testing.T) {
// Test mtu
c := networkConfiguration{Mtu: -2}
err := c.Validate()
if err == nil {
t.Fatalf("Failed to detect invalid MTU number")
}
c.Mtu = 9000
err = c.Validate()
if err != nil {
t.Fatalf("unexpected validation error on MTU number")
}
// Bridge network
_, network, _ := net.ParseCIDR("172.28.0.0/16")
c = networkConfiguration{
AddressIPv4: network,
}
err = c.Validate()
if err != nil {
t.Fatal(err)
}
// Test v4 gw
c.DefaultGatewayIPv4 = net.ParseIP("172.27.30.234")
err = c.Validate()
if err == nil {
t.Fatalf("Failed to detect invalid default gateway")
}
c.DefaultGatewayIPv4 = net.ParseIP("172.28.30.234")
err = c.Validate()
if err != nil {
t.Fatalf("Unexpected validation error on default gateway")
}
// Test v6 gw
_, v6nw, _ := net.ParseCIDR("2001:1234:ae:b004::/64")
c = networkConfiguration{
EnableIPv6: true,
AddressIPv6: v6nw,
DefaultGatewayIPv6: net.ParseIP("2001:1234:ac:b004::bad:a55"),
}
err = c.Validate()
if err == nil {
t.Fatalf("Failed to detect invalid v6 default gateway")
}
c.DefaultGatewayIPv6 = net.ParseIP("2001:1234:ae:b004::bad:a55")
err = c.Validate()
if err != nil {
t.Fatalf("Unexpected validation error on v6 default gateway")
}
c.AddressIPv6 = nil
err = c.Validate()
if err == nil {
t.Fatalf("Failed to detect invalid v6 default gateway")
}
c.AddressIPv6 = nil
err = c.Validate()
if err == nil {
t.Fatalf("Failed to detect invalid v6 default gateway")
}
}
func TestSetDefaultGw(t *testing.T) {
defer testutils.SetupTestOSContext(t)()
d := newDriver()
if err := d.configure(nil); err != nil {
t.Fatalf("Failed to setup driver config: %v", err)
}
_, subnetv6, _ := net.ParseCIDR("2001:db8:ea9:9abc:b0c4::/80")
ipdList := getIPv4Data(t)
gw4 := types.GetIPCopy(ipdList[0].Pool.IP).To4()
gw4[3] = 254
gw6 := net.ParseIP("2001:db8:ea9:9abc:b0c4::254")
config := &networkConfiguration{
BridgeName: DefaultBridgeName,
EnableIPv6: true,
AddressIPv6: subnetv6,
DefaultGatewayIPv4: gw4,
DefaultGatewayIPv6: gw6,
}
genericOption := make(map[string]interface{})
genericOption[netlabel.GenericData] = config
err := d.CreateNetwork("dummy", genericOption, ipdList, nil)
if err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
te := newTestEndpoint(ipdList[0].Pool, 10)
err = d.CreateEndpoint("dummy", "ep", te.Interface(), nil)
if err != nil {
t.Fatalf("Failed to create endpoint: %v", err)
}
err = d.Join("dummy", "ep", "sbox", te, nil)
if err != nil {
t.Fatalf("Failed to join endpoint: %v", err)
}
if !gw4.Equal(te.gw) {
t.Fatalf("Failed to configure default gateway. Expected %v. Found %v", gw4, te.gw)
}
if !gw6.Equal(te.gw6) {
t.Fatalf("Failed to configure default gateway. Expected %v. Found %v", gw6, te.gw6)
}
}
func TestCleanupIptableRules(t *testing.T) {
defer testutils.SetupTestOSContext(t)()
bridgeChain := []iptables.ChainInfo{
iptables.ChainInfo{Name: DockerChain, Table: iptables.Nat},
iptables.ChainInfo{Name: DockerChain, Table: iptables.Filter},
iptables.ChainInfo{Name: IsolationChain, Table: iptables.Filter},
}
if _, _, _, err := setupIPChains(&configuration{EnableIPTables: true}); err != nil {
t.Fatalf("Error setting up ip chains: %v", err)
}
for _, chainInfo := range bridgeChain {
if !iptables.ExistChain(chainInfo.Name, chainInfo.Table) {
t.Fatalf("iptables chain %s of %s table should have been created", chainInfo.Name, chainInfo.Table)
}
}
removeIPChains()
for _, chainInfo := range bridgeChain {
if iptables.ExistChain(chainInfo.Name, chainInfo.Table) {
t.Fatalf("iptables chain %s of %s table should have been deleted", chainInfo.Name, chainInfo.Table)
}
}
}

View File

@@ -0,0 +1,341 @@
package bridge
import (
"fmt"
"net"
)
// ErrConfigExists error is returned when driver already has a config applied.
type ErrConfigExists struct{}
func (ece *ErrConfigExists) Error() string {
return "configuration already exists, bridge configuration can be applied only once"
}
// Forbidden denotes the type of this error
func (ece *ErrConfigExists) Forbidden() {}
// ErrInvalidDriverConfig error is returned when Bridge Driver is passed an invalid config
type ErrInvalidDriverConfig struct{}
func (eidc *ErrInvalidDriverConfig) Error() string {
return "Invalid configuration passed to Bridge Driver"
}
// BadRequest denotes the type of this error
func (eidc *ErrInvalidDriverConfig) BadRequest() {}
// ErrInvalidNetworkConfig error is returned when a network is created on a driver without valid config.
type ErrInvalidNetworkConfig struct{}
func (einc *ErrInvalidNetworkConfig) Error() string {
return "trying to create a network on a driver without valid config"
}
// Forbidden denotes the type of this error
func (einc *ErrInvalidNetworkConfig) Forbidden() {}
// ErrInvalidContainerConfig error is returned when a endpoint create is attempted with an invalid configuration.
type ErrInvalidContainerConfig struct{}
func (eicc *ErrInvalidContainerConfig) Error() string {
return "Error in joining a container due to invalid configuration"
}
// BadRequest denotes the type of this error
func (eicc *ErrInvalidContainerConfig) BadRequest() {}
// ErrInvalidEndpointConfig error is returned when a endpoint create is attempted with an invalid endpoint configuration.
type ErrInvalidEndpointConfig struct{}
func (eiec *ErrInvalidEndpointConfig) Error() string {
return "trying to create an endpoint with an invalid endpoint configuration"
}
// BadRequest denotes the type of this error
func (eiec *ErrInvalidEndpointConfig) BadRequest() {}
// ErrNetworkExists error is returned when a network already exists and another network is created.
type ErrNetworkExists struct{}
func (ene *ErrNetworkExists) Error() string {
return "network already exists, bridge can only have one network"
}
// Forbidden denotes the type of this error
func (ene *ErrNetworkExists) Forbidden() {}
// ErrIfaceName error is returned when a new name could not be generated.
type ErrIfaceName struct{}
func (ein *ErrIfaceName) Error() string {
return "failed to find name for new interface"
}
// InternalError denotes the type of this error
func (ein *ErrIfaceName) InternalError() {}
// ErrNoIPAddr error is returned when bridge has no IPv4 address configured.
type ErrNoIPAddr struct{}
func (enip *ErrNoIPAddr) Error() string {
return "bridge has no IPv4 address configured"
}
// InternalError denotes the type of this error
func (enip *ErrNoIPAddr) InternalError() {}
// ErrInvalidGateway is returned when the user provided default gateway (v4/v6) is not not valid.
type ErrInvalidGateway struct{}
func (eig *ErrInvalidGateway) Error() string {
return "default gateway ip must be part of the network"
}
// BadRequest denotes the type of this error
func (eig *ErrInvalidGateway) BadRequest() {}
// ErrInvalidContainerSubnet is returned when the container subnet (FixedCIDR) is not valid.
type ErrInvalidContainerSubnet struct{}
func (eis *ErrInvalidContainerSubnet) Error() string {
return "container subnet must be a subset of bridge network"
}
// BadRequest denotes the type of this error
func (eis *ErrInvalidContainerSubnet) BadRequest() {}
// ErrInvalidMtu is returned when the user provided MTU is not valid.
type ErrInvalidMtu int
func (eim ErrInvalidMtu) Error() string {
return fmt.Sprintf("invalid MTU number: %d", int(eim))
}
// BadRequest denotes the type of this error
func (eim ErrInvalidMtu) BadRequest() {}
// ErrInvalidPort is returned when the container or host port specified in the port binding is not valid.
type ErrInvalidPort string
func (ip ErrInvalidPort) Error() string {
return fmt.Sprintf("invalid transport port: %s", string(ip))
}
// BadRequest denotes the type of this error
func (ip ErrInvalidPort) BadRequest() {}
// ErrUnsupportedAddressType is returned when the specified address type is not supported.
type ErrUnsupportedAddressType string
func (uat ErrUnsupportedAddressType) Error() string {
return fmt.Sprintf("unsupported address type: %s", string(uat))
}
// BadRequest denotes the type of this error
func (uat ErrUnsupportedAddressType) BadRequest() {}
// ErrInvalidAddressBinding is returned when the host address specified in the port binding is not valid.
type ErrInvalidAddressBinding string
func (iab ErrInvalidAddressBinding) Error() string {
return fmt.Sprintf("invalid host address in port binding: %s", string(iab))
}
// BadRequest denotes the type of this error
func (iab ErrInvalidAddressBinding) BadRequest() {}
// ActiveEndpointsError is returned when there are
// still active endpoints in the network being deleted.
type ActiveEndpointsError string
func (aee ActiveEndpointsError) Error() string {
return fmt.Sprintf("network %s has active endpoint", string(aee))
}
// Forbidden denotes the type of this error
func (aee ActiveEndpointsError) Forbidden() {}
// InvalidNetworkIDError is returned when the passed
// network id for an existing network is not a known id.
type InvalidNetworkIDError string
func (inie InvalidNetworkIDError) Error() string {
return fmt.Sprintf("invalid network id %s", string(inie))
}
// NotFound denotes the type of this error
func (inie InvalidNetworkIDError) NotFound() {}
// InvalidEndpointIDError is returned when the passed
// endpoint id is not valid.
type InvalidEndpointIDError string
func (ieie InvalidEndpointIDError) Error() string {
return fmt.Sprintf("invalid endpoint id: %s", string(ieie))
}
// BadRequest denotes the type of this error
func (ieie InvalidEndpointIDError) BadRequest() {}
// InvalidSandboxIDError is returned when the passed
// sandbox id is not valid.
type InvalidSandboxIDError string
func (isie InvalidSandboxIDError) Error() string {
return fmt.Sprintf("invalid sanbox id: %s", string(isie))
}
// BadRequest denotes the type of this error
func (isie InvalidSandboxIDError) BadRequest() {}
// EndpointNotFoundError is returned when the no endpoint
// with the passed endpoint id is found.
type EndpointNotFoundError string
func (enfe EndpointNotFoundError) Error() string {
return fmt.Sprintf("endpoint not found: %s", string(enfe))
}
// NotFound denotes the type of this error
func (enfe EndpointNotFoundError) NotFound() {}
// NonDefaultBridgeExistError is returned when a non-default
// bridge config is passed but it does not already exist.
type NonDefaultBridgeExistError string
func (ndbee NonDefaultBridgeExistError) Error() string {
return fmt.Sprintf("bridge device with non default name %s must be created manually", string(ndbee))
}
// Forbidden denotes the type of this error
func (ndbee NonDefaultBridgeExistError) Forbidden() {}
// NonDefaultBridgeNeedsIPError is returned when a non-default
// bridge config is passed but it has no ip configured
type NonDefaultBridgeNeedsIPError string
func (ndbee NonDefaultBridgeNeedsIPError) Error() string {
return fmt.Sprintf("bridge device with non default name %s must have a valid IP address", string(ndbee))
}
// Forbidden denotes the type of this error
func (ndbee NonDefaultBridgeNeedsIPError) Forbidden() {}
// FixedCIDRv4Error is returned when fixed-cidrv4 configuration
// failed.
type FixedCIDRv4Error struct {
Net *net.IPNet
Subnet *net.IPNet
Err error
}
func (fcv4 *FixedCIDRv4Error) Error() string {
return fmt.Sprintf("setup FixedCIDRv4 failed for subnet %s in %s: %v", fcv4.Subnet, fcv4.Net, fcv4.Err)
}
// InternalError denotes the type of this error
func (fcv4 *FixedCIDRv4Error) InternalError() {}
// FixedCIDRv6Error is returned when fixed-cidrv6 configuration
// failed.
type FixedCIDRv6Error struct {
Net *net.IPNet
Err error
}
func (fcv6 *FixedCIDRv6Error) Error() string {
return fmt.Sprintf("setup FixedCIDRv6 failed for subnet %s in %s: %v", fcv6.Net, fcv6.Net, fcv6.Err)
}
// InternalError denotes the type of this error
func (fcv6 *FixedCIDRv6Error) InternalError() {}
// IPTableCfgError is returned when an unexpected ip tables configuration is entered
type IPTableCfgError string
func (name IPTableCfgError) Error() string {
return fmt.Sprintf("unexpected request to set IP tables for interface: %s", string(name))
}
// BadRequest denotes the type of this error
func (name IPTableCfgError) BadRequest() {}
// InvalidIPTablesCfgError is returned when an invalid ip tables configuration is entered
type InvalidIPTablesCfgError string
func (action InvalidIPTablesCfgError) Error() string {
return fmt.Sprintf("Invalid IPTables action '%s'", string(action))
}
// BadRequest denotes the type of this error
func (action InvalidIPTablesCfgError) BadRequest() {}
// IPv4AddrRangeError is returned when a valid IP address range couldn't be found.
type IPv4AddrRangeError string
func (name IPv4AddrRangeError) Error() string {
return fmt.Sprintf("can't find an address range for interface %q", string(name))
}
// BadRequest denotes the type of this error
func (name IPv4AddrRangeError) BadRequest() {}
// IPv4AddrAddError is returned when IPv4 address could not be added to the bridge.
type IPv4AddrAddError struct {
IP *net.IPNet
Err error
}
func (ipv4 *IPv4AddrAddError) Error() string {
return fmt.Sprintf("failed to add IPv4 address %s to bridge: %v", ipv4.IP, ipv4.Err)
}
// InternalError denotes the type of this error
func (ipv4 *IPv4AddrAddError) InternalError() {}
// IPv6AddrAddError is returned when IPv6 address could not be added to the bridge.
type IPv6AddrAddError struct {
IP *net.IPNet
Err error
}
func (ipv6 *IPv6AddrAddError) Error() string {
return fmt.Sprintf("failed to add IPv6 address %s to bridge: %v", ipv6.IP, ipv6.Err)
}
// InternalError denotes the type of this error
func (ipv6 *IPv6AddrAddError) InternalError() {}
// IPv4AddrNoMatchError is returned when the bridge's IPv4 address does not match configured.
type IPv4AddrNoMatchError struct {
IP net.IP
CfgIP net.IP
}
func (ipv4 *IPv4AddrNoMatchError) Error() string {
return fmt.Sprintf("bridge IPv4 (%s) does not match requested configuration %s", ipv4.IP, ipv4.CfgIP)
}
// BadRequest denotes the type of this error
func (ipv4 *IPv4AddrNoMatchError) BadRequest() {}
// IPv6AddrNoMatchError is returned when the bridge's IPv6 address does not match configured.
type IPv6AddrNoMatchError net.IPNet
func (ipv6 *IPv6AddrNoMatchError) Error() string {
return fmt.Sprintf("bridge IPv6 addresses do not match the expected bridge configuration %s", (*net.IPNet)(ipv6).String())
}
// BadRequest denotes the type of this error
func (ipv6 *IPv6AddrNoMatchError) BadRequest() {}
// InvalidLinkIPAddrError is returned when a link is configured to a container with an invalid ip address
type InvalidLinkIPAddrError string
func (address InvalidLinkIPAddrError) Error() string {
return fmt.Sprintf("Cannot link to a container with Invalid IP Address '%s'", string(address))
}
// BadRequest denotes the type of this error
func (address InvalidLinkIPAddrError) BadRequest() {}

View File

@@ -0,0 +1,79 @@
package bridge
import (
"fmt"
"net"
"github.com/vishvananda/netlink"
)
const (
// DefaultBridgeName is the default name for the bridge interface managed
// by the driver when unspecified by the caller.
DefaultBridgeName = "docker0"
)
// Interface models the bridge network device.
type bridgeInterface struct {
Link netlink.Link
bridgeIPv4 *net.IPNet
bridgeIPv6 *net.IPNet
gatewayIPv4 net.IP
gatewayIPv6 net.IP
}
// newInterface creates a new bridge interface structure. It attempts to find
// an already existing device identified by the configuration BridgeName field,
// or the default bridge name when unspecified), but doesn't attempt to create
// one when missing
func newInterface(config *networkConfiguration) *bridgeInterface {
i := &bridgeInterface{}
// Initialize the bridge name to the default if unspecified.
if config.BridgeName == "" {
config.BridgeName = DefaultBridgeName
}
// Attempt to find an existing bridge named with the specified name.
i.Link, _ = netlink.LinkByName(config.BridgeName)
return i
}
// exists indicates if the existing bridge interface exists on the system.
func (i *bridgeInterface) exists() bool {
return i.Link != nil
}
// addresses returns a single IPv4 address and all IPv6 addresses for the
// bridge interface.
func (i *bridgeInterface) addresses() (netlink.Addr, []netlink.Addr, error) {
v4addr, err := netlink.AddrList(i.Link, netlink.FAMILY_V4)
if err != nil {
return netlink.Addr{}, nil, err
}
v6addr, err := netlink.AddrList(i.Link, netlink.FAMILY_V6)
if err != nil {
return netlink.Addr{}, nil, err
}
if len(v4addr) == 0 {
return netlink.Addr{}, v6addr, nil
}
return v4addr[0], v6addr, nil
}
func (i *bridgeInterface) programIPv6Address() error {
_, nlAddressList, err := i.addresses()
if err != nil {
return &IPv6AddrAddError{IP: i.bridgeIPv6, Err: fmt.Errorf("failed to retrieve address list: %v", err)}
}
nlAddr := netlink.Addr{IPNet: i.bridgeIPv6}
if findIPv6Address(nlAddr, nlAddressList) {
return nil
}
if err := netlink.AddrAdd(i.Link, &nlAddr); err != nil {
return &IPv6AddrAddError{IP: i.bridgeIPv6, Err: err}
}
return nil
}

View File

@@ -0,0 +1,33 @@
package bridge
import (
"testing"
"github.com/docker/libnetwork/testutils"
"github.com/vishvananda/netlink"
)
func TestInterfaceDefaultName(t *testing.T) {
defer testutils.SetupTestOSContext(t)()
config := &networkConfiguration{}
if _ = newInterface(config); config.BridgeName != DefaultBridgeName {
t.Fatalf("Expected default interface name %q, got %q", DefaultBridgeName, config.BridgeName)
}
}
func TestAddressesEmptyInterface(t *testing.T) {
defer testutils.SetupTestOSContext(t)()
inf := newInterface(&networkConfiguration{})
addrv4, addrsv6, err := inf.addresses()
if err != nil {
t.Fatalf("Failed to get addresses of default interface: %v", err)
}
if expected := (netlink.Addr{}); addrv4 != expected {
t.Fatalf("Default interface has unexpected IPv4: %s", addrv4)
}
if len(addrsv6) != 0 {
t.Fatalf("Default interface has unexpected IPv6: %v", addrsv6)
}
}

View File

@@ -0,0 +1,18 @@
package bridge
const (
// BridgeName label for bridge driver
BridgeName = "com.docker.network.bridge.name"
// EnableIPMasquerade label for bridge driver
EnableIPMasquerade = "com.docker.network.bridge.enable_ip_masquerade"
// EnableICC label
EnableICC = "com.docker.network.bridge.enable_icc"
// DefaultBindingIP label
DefaultBindingIP = "com.docker.network.bridge.host_binding_ipv4"
// DefaultBridge label
DefaultBridge = "com.docker.network.bridge.default_bridge"
)

View File

@@ -0,0 +1,85 @@
package bridge
import (
"fmt"
"net"
log "github.com/Sirupsen/logrus"
"github.com/docker/libnetwork/iptables"
"github.com/docker/libnetwork/types"
)
type link struct {
parentIP string
childIP string
ports []types.TransportPort
bridge string
}
func (l *link) String() string {
return fmt.Sprintf("%s <-> %s [%v] on %s", l.parentIP, l.childIP, l.ports, l.bridge)
}
func newLink(parentIP, childIP string, ports []types.TransportPort, bridge string) *link {
return &link{
childIP: childIP,
parentIP: parentIP,
ports: ports,
bridge: bridge,
}
}
func (l *link) Enable() error {
// -A == iptables append flag
linkFunction := func() error {
return linkContainers("-A", l.parentIP, l.childIP, l.ports, l.bridge, false)
}
iptables.OnReloaded(func() { linkFunction() })
return linkFunction()
}
func (l *link) Disable() {
// -D == iptables delete flag
err := linkContainers("-D", l.parentIP, l.childIP, l.ports, l.bridge, true)
if err != nil {
log.Errorf("Error removing IPTables rules for a link %s due to %s", l.String(), err.Error())
}
// Return proper error once we move to use a proper iptables package
// that returns typed errors
}
func linkContainers(action, parentIP, childIP string, ports []types.TransportPort, bridge string,
ignoreErrors bool) error {
var nfAction iptables.Action
switch action {
case "-A":
nfAction = iptables.Append
case "-I":
nfAction = iptables.Insert
case "-D":
nfAction = iptables.Delete
default:
return InvalidIPTablesCfgError(action)
}
ip1 := net.ParseIP(parentIP)
if ip1 == nil {
return InvalidLinkIPAddrError(parentIP)
}
ip2 := net.ParseIP(childIP)
if ip2 == nil {
return InvalidLinkIPAddrError(childIP)
}
chain := iptables.ChainInfo{Name: DockerChain}
for _, port := range ports {
err := chain.Link(nfAction, ip1, ip2, int(port.Port), port.Proto.String(), bridge)
if !ignoreErrors && err != nil {
return err
}
}
return nil
}

View File

@@ -0,0 +1,39 @@
package bridge
import (
"testing"
"github.com/docker/libnetwork/types"
)
func getPorts() []types.TransportPort {
return []types.TransportPort{
types.TransportPort{Proto: types.TCP, Port: uint16(5000)},
types.TransportPort{Proto: types.UDP, Port: uint16(400)},
types.TransportPort{Proto: types.TCP, Port: uint16(600)},
}
}
func TestLinkNew(t *testing.T) {
ports := getPorts()
link := newLink("172.0.17.3", "172.0.17.2", ports, "docker0")
if link == nil {
t.FailNow()
}
if link.parentIP != "172.0.17.3" {
t.Fail()
}
if link.childIP != "172.0.17.2" {
t.Fail()
}
for i, p := range link.ports {
if p != ports[i] {
t.Fail()
}
}
if link.bridge != "docker0" {
t.Fail()
}
}

View File

@@ -0,0 +1,131 @@
package bridge
import (
"fmt"
"math/rand"
"net"
"syscall"
"time"
"unsafe"
"github.com/docker/libnetwork/netutils"
)
const (
ifNameSize = 16
ioctlBrAdd = 0x89a0
ioctlBrAddIf = 0x89a2
)
type ifreqIndex struct {
IfrnName [ifNameSize]byte
IfruIndex int32
}
type ifreqHwaddr struct {
IfrnName [ifNameSize]byte
IfruHwaddr syscall.RawSockaddr
}
var rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
// THIS CODE DOES NOT COMMUNICATE WITH KERNEL VIA RTNETLINK INTERFACE
// IT IS HERE FOR BACKWARDS COMPATIBILITY WITH OLDER LINUX KERNELS
// WHICH SHIP WITH OLDER NOT ENTIRELY FUNCTIONAL VERSION OF NETLINK
func getIfSocket() (fd int, err error) {
for _, socket := range []int{
syscall.AF_INET,
syscall.AF_PACKET,
syscall.AF_INET6,
} {
if fd, err = syscall.Socket(socket, syscall.SOCK_DGRAM, 0); err == nil {
break
}
}
if err == nil {
return fd, nil
}
return -1, err
}
func ifIoctBridge(iface, master *net.Interface, op uintptr) error {
if len(master.Name) >= ifNameSize {
return fmt.Errorf("Interface name %s too long", master.Name)
}
s, err := getIfSocket()
if err != nil {
return err
}
defer syscall.Close(s)
ifr := ifreqIndex{}
copy(ifr.IfrnName[:len(ifr.IfrnName)-1], master.Name)
ifr.IfruIndex = int32(iface.Index)
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(s), op, uintptr(unsafe.Pointer(&ifr))); err != 0 {
return err
}
return nil
}
// Add a slave to a bridge device. This is more backward-compatible than
// netlink.NetworkSetMaster and works on RHEL 6.
func ioctlAddToBridge(iface, master *net.Interface) error {
return ifIoctBridge(iface, master, ioctlBrAddIf)
}
func ioctlSetMacAddress(name, addr string) error {
if len(name) >= ifNameSize {
return fmt.Errorf("Interface name %s too long", name)
}
hw, err := net.ParseMAC(addr)
if err != nil {
return err
}
s, err := getIfSocket()
if err != nil {
return err
}
defer syscall.Close(s)
ifr := ifreqHwaddr{}
ifr.IfruHwaddr.Family = syscall.ARPHRD_ETHER
copy(ifr.IfrnName[:len(ifr.IfrnName)-1], name)
for i := 0; i < 6; i++ {
ifr.IfruHwaddr.Data[i] = ifrDataByte(hw[i])
}
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(s), syscall.SIOCSIFHWADDR, uintptr(unsafe.Pointer(&ifr))); err != 0 {
return err
}
return nil
}
func ioctlCreateBridge(name string, setMacAddr bool) error {
if len(name) >= ifNameSize {
return fmt.Errorf("Interface name %s too long", name)
}
s, err := getIfSocket()
if err != nil {
return err
}
defer syscall.Close(s)
nameBytePtr, err := syscall.BytePtrFromString(name)
if err != nil {
return err
}
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(s), ioctlBrAdd, uintptr(unsafe.Pointer(nameBytePtr))); err != 0 {
return err
}
if setMacAddr {
return ioctlSetMacAddress(name, netutils.GenerateRandomMAC().String())
}
return nil
}

View File

@@ -0,0 +1,7 @@
// +build arm ppc64 ppc64le
package bridge
func ifrDataByte(b byte) uint8 {
return uint8(b)
}

View File

@@ -0,0 +1,7 @@
// +build !arm,!ppc64,!ppc64le
package bridge
func ifrDataByte(b byte) int8 {
return int8(b)
}

View File

@@ -0,0 +1,18 @@
// +build !linux
package bridge
import (
"errors"
"net"
)
// Add a slave to a bridge device. This is more backward-compatible than
// netlink.NetworkSetMaster and works on RHEL 6.
func ioctlAddToBridge(iface, master *net.Interface) error {
return errors.New("not implemented")
}
func ioctlCreateBridge(name string, setMacAddr bool) error {
return errors.New("not implemented")
}

View File

@@ -0,0 +1,217 @@
package bridge
import (
"testing"
"github.com/docker/libnetwork/driverapi"
"github.com/docker/libnetwork/netlabel"
"github.com/docker/libnetwork/testutils"
"github.com/vishvananda/netlink"
)
func TestLinkCreate(t *testing.T) {
defer testutils.SetupTestOSContext(t)()
d := newDriver()
if err := d.configure(nil); err != nil {
t.Fatalf("Failed to setup driver config: %v", err)
}
mtu := 1490
config := &networkConfiguration{
BridgeName: DefaultBridgeName,
Mtu: mtu,
EnableIPv6: true,
}
genericOption := make(map[string]interface{})
genericOption[netlabel.GenericData] = config
ipdList := getIPv4Data(t)
err := d.CreateNetwork("dummy", genericOption, ipdList, nil)
if err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
te := newTestEndpoint(ipdList[0].Pool, 10)
err = d.CreateEndpoint("dummy", "", te.Interface(), nil)
if err != nil {
if _, ok := err.(InvalidEndpointIDError); !ok {
t.Fatalf("Failed with a wrong error :%s", err.Error())
}
} else {
t.Fatalf("Failed to detect invalid config")
}
// Good endpoint creation
err = d.CreateEndpoint("dummy", "ep", te.Interface(), nil)
if err != nil {
t.Fatalf("Failed to create a link: %s", err.Error())
}
err = d.Join("dummy", "ep", "sbox", te, nil)
if err != nil {
t.Fatalf("Failed to create a link: %s", err.Error())
}
// Verify sbox endoint interface inherited MTU value from bridge config
sboxLnk, err := netlink.LinkByName(te.iface.srcName)
if err != nil {
t.Fatal(err)
}
if mtu != sboxLnk.Attrs().MTU {
t.Fatalf("Sandbox endpoint interface did not inherit bridge interface MTU config")
}
// TODO: if we could get peer name from (sboxLnk.(*netlink.Veth)).PeerName
// then we could check the MTU on hostLnk as well.
te1 := newTestEndpoint(ipdList[0].Pool, 11)
err = d.CreateEndpoint("dummy", "ep", te1.Interface(), nil)
if err == nil {
t.Fatalf("Failed to detect duplicate endpoint id on same network")
}
if te.iface.dstName == "" {
t.Fatal("Invalid Dstname returned")
}
_, err = netlink.LinkByName(te.iface.srcName)
if err != nil {
t.Fatalf("Could not find source link %s: %v", te.iface.srcName, err)
}
n, ok := d.networks["dummy"]
if !ok {
t.Fatalf("Cannot find network %s inside driver", "dummy")
}
ip := te.iface.addr.IP
if !n.bridge.bridgeIPv4.Contains(ip) {
t.Fatalf("IP %s is not a valid ip in the subnet %s", ip.String(), n.bridge.bridgeIPv4.String())
}
ip6 := te.iface.addrv6.IP
if !n.bridge.bridgeIPv6.Contains(ip6) {
t.Fatalf("IP %s is not a valid ip in the subnet %s", ip6.String(), bridgeIPv6.String())
}
if !te.gw.Equal(n.bridge.bridgeIPv4.IP) {
t.Fatalf("Invalid default gateway. Expected %s. Got %s", n.bridge.bridgeIPv4.IP.String(),
te.gw.String())
}
if !te.gw6.Equal(n.bridge.bridgeIPv6.IP) {
t.Fatalf("Invalid default gateway for IPv6. Expected %s. Got %s", n.bridge.bridgeIPv6.IP.String(),
te.gw6.String())
}
}
func TestLinkCreateTwo(t *testing.T) {
defer testutils.SetupTestOSContext(t)()
d := newDriver()
if err := d.configure(nil); err != nil {
t.Fatalf("Failed to setup driver config: %v", err)
}
config := &networkConfiguration{
BridgeName: DefaultBridgeName,
EnableIPv6: true}
genericOption := make(map[string]interface{})
genericOption[netlabel.GenericData] = config
ipdList := getIPv4Data(t)
err := d.CreateNetwork("dummy", genericOption, ipdList, nil)
if err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
te1 := newTestEndpoint(ipdList[0].Pool, 11)
err = d.CreateEndpoint("dummy", "ep", te1.Interface(), nil)
if err != nil {
t.Fatalf("Failed to create a link: %s", err.Error())
}
te2 := newTestEndpoint(ipdList[0].Pool, 12)
err = d.CreateEndpoint("dummy", "ep", te2.Interface(), nil)
if err != nil {
if _, ok := err.(driverapi.ErrEndpointExists); !ok {
t.Fatalf("Failed with a wrong error: %s", err.Error())
}
} else {
t.Fatalf("Expected to fail while trying to add same endpoint twice")
}
}
func TestLinkCreateNoEnableIPv6(t *testing.T) {
defer testutils.SetupTestOSContext(t)()
d := newDriver()
if err := d.configure(nil); err != nil {
t.Fatalf("Failed to setup driver config: %v", err)
}
config := &networkConfiguration{
BridgeName: DefaultBridgeName}
genericOption := make(map[string]interface{})
genericOption[netlabel.GenericData] = config
ipdList := getIPv4Data(t)
err := d.CreateNetwork("dummy", genericOption, ipdList, nil)
if err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
te := newTestEndpoint(ipdList[0].Pool, 30)
err = d.CreateEndpoint("dummy", "ep", te.Interface(), nil)
if err != nil {
t.Fatalf("Failed to create a link: %s", err.Error())
}
iface := te.iface
if iface.addrv6 != nil && iface.addrv6.IP.To16() != nil {
t.Fatalf("Expectd IPv6 address to be nil when IPv6 is not enabled. Got IPv6 = %s", iface.addrv6.String())
}
if te.gw6.To16() != nil {
t.Fatalf("Expected GatewayIPv6 to be nil when IPv6 is not enabled. Got GatewayIPv6 = %s", te.gw6.String())
}
}
func TestLinkDelete(t *testing.T) {
defer testutils.SetupTestOSContext(t)()
d := newDriver()
if err := d.configure(nil); err != nil {
t.Fatalf("Failed to setup driver config: %v", err)
}
config := &networkConfiguration{
BridgeName: DefaultBridgeName,
EnableIPv6: true}
genericOption := make(map[string]interface{})
genericOption[netlabel.GenericData] = config
ipdList := getIPv4Data(t)
err := d.CreateNetwork("dummy", genericOption, ipdList, nil)
if err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
te := newTestEndpoint(ipdList[0].Pool, 30)
err = d.CreateEndpoint("dummy", "ep1", te.Interface(), nil)
if err != nil {
t.Fatalf("Failed to create a link: %s", err.Error())
}
err = d.DeleteEndpoint("dummy", "")
if err != nil {
if _, ok := err.(InvalidEndpointIDError); !ok {
t.Fatalf("Failed with a wrong error :%s", err.Error())
}
} else {
t.Fatalf("Failed to detect invalid config")
}
err = d.DeleteEndpoint("dummy", "ep1")
if err != nil {
t.Fatal(err)
}
}

View File

@@ -0,0 +1,128 @@
package bridge
import (
"bytes"
"errors"
"fmt"
"net"
"github.com/Sirupsen/logrus"
"github.com/docker/libnetwork/types"
)
var (
defaultBindingIP = net.IPv4(0, 0, 0, 0)
)
func (n *bridgeNetwork) allocatePorts(epConfig *endpointConfiguration, ep *bridgeEndpoint, reqDefBindIP net.IP, ulPxyEnabled bool) ([]types.PortBinding, error) {
if epConfig == nil || epConfig.PortBindings == nil {
return nil, nil
}
defHostIP := defaultBindingIP
if reqDefBindIP != nil {
defHostIP = reqDefBindIP
}
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) {
bs := make([]types.PortBinding, 0, len(bindings))
for _, c := range bindings {
b := c.GetCopy()
if err := n.allocatePort(&b, containerIP, defHostIP, ulPxyEnabled); err != nil {
// On allocation failure, release previously allocated ports. On cleanup error, just log a warning message
if cuErr := n.releasePortsInternal(bs); cuErr != nil {
logrus.Warnf("Upon allocation failure for %v, failed to clear previously allocated port bindings: %v", b, cuErr)
}
return nil, err
}
bs = append(bs, b)
}
return bs, nil
}
func (n *bridgeNetwork) allocatePort(bnd *types.PortBinding, containerIP, defHostIP net.IP, ulPxyEnabled bool) error {
var (
host net.Addr
err error
)
// Store the container interface address in the operational binding
bnd.IP = containerIP
// Adjust the host address in the operational binding
if len(bnd.HostIP) == 0 {
bnd.HostIP = defHostIP
}
// Adjust HostPortEnd if this is not a range.
if bnd.HostPortEnd == 0 {
bnd.HostPortEnd = bnd.HostPort
}
// Construct the container side transport address
container, err := bnd.ContainerAddr()
if err != nil {
return err
}
// Try up to maxAllocatePortAttempts times to get a port that's not already allocated.
for i := 0; i < maxAllocatePortAttempts; i++ {
if host, err = n.portMapper.MapRange(container, bnd.HostIP, int(bnd.HostPort), int(bnd.HostPortEnd), ulPxyEnabled); err == nil {
break
}
// There is no point in immediately retrying to map an explicitly chosen port.
if bnd.HostPort != 0 {
logrus.Warnf("Failed to allocate and map port %d-%d: %s", bnd.HostPort, bnd.HostPortEnd, err)
break
}
logrus.Warnf("Failed to allocate and map port: %s, retry: %d", err, i+1)
}
if err != nil {
return err
}
// Save the host port (regardless it was or not specified in the binding)
switch netAddr := host.(type) {
case *net.TCPAddr:
bnd.HostPort = uint16(host.(*net.TCPAddr).Port)
return nil
case *net.UDPAddr:
bnd.HostPort = uint16(host.(*net.UDPAddr).Port)
return nil
default:
// For completeness
return ErrUnsupportedAddressType(fmt.Sprintf("%T", netAddr))
}
}
func (n *bridgeNetwork) releasePorts(ep *bridgeEndpoint) error {
return n.releasePortsInternal(ep.portMapping)
}
func (n *bridgeNetwork) releasePortsInternal(bindings []types.PortBinding) error {
var errorBuf bytes.Buffer
// Attempt to release all port bindings, do not stop on failure
for _, m := range bindings {
if err := n.releasePort(m); err != nil {
errorBuf.WriteString(fmt.Sprintf("\ncould not release %v because of %v", m, err))
}
}
if errorBuf.Len() != 0 {
return errors.New(errorBuf.String())
}
return nil
}
func (n *bridgeNetwork) releasePort(bnd types.PortBinding) error {
// Construct the host side transport address
host, err := bnd.HostAddr()
if err != nil {
return err
}
return n.portMapper.Unmap(host)
}

View File

@@ -0,0 +1,80 @@
package bridge
import (
"os"
"testing"
"github.com/docker/docker/pkg/reexec"
"github.com/docker/libnetwork/netlabel"
"github.com/docker/libnetwork/testutils"
"github.com/docker/libnetwork/types"
)
func TestMain(m *testing.M) {
if reexec.Init() {
return
}
os.Exit(m.Run())
}
func TestPortMappingConfig(t *testing.T) {
defer testutils.SetupTestOSContext(t)()
d := newDriver()
config := &configuration{
EnableIPTables: true,
}
genericOption := make(map[string]interface{})
genericOption[netlabel.GenericData] = config
if err := d.configure(genericOption); err != nil {
t.Fatalf("Failed to setup driver config: %v", err)
}
binding1 := types.PortBinding{Proto: types.UDP, Port: uint16(400), HostPort: uint16(54000)}
binding2 := types.PortBinding{Proto: types.TCP, Port: uint16(500), HostPort: uint16(65000)}
portBindings := []types.PortBinding{binding1, binding2}
epOptions := make(map[string]interface{})
epOptions[netlabel.PortMap] = portBindings
netConfig := &networkConfiguration{
BridgeName: DefaultBridgeName,
}
netOptions := make(map[string]interface{})
netOptions[netlabel.GenericData] = netConfig
ipdList := getIPv4Data(t)
err := d.CreateNetwork("dummy", netOptions, ipdList, nil)
if err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
te := newTestEndpoint(ipdList[0].Pool, 11)
err = d.CreateEndpoint("dummy", "ep1", te.Interface(), epOptions)
if err != nil {
t.Fatalf("Failed to create the endpoint: %s", err.Error())
}
network, ok := d.networks["dummy"]
if !ok {
t.Fatalf("Cannot find network %s inside driver", "dummy")
}
ep, _ := network.endpoints["ep1"]
if len(ep.portMapping) != 2 {
t.Fatalf("Failed to store the port bindings into the sandbox info. Found: %v", ep.portMapping)
}
if ep.portMapping[0].Proto != binding1.Proto || ep.portMapping[0].Port != binding1.Port ||
ep.portMapping[1].Proto != binding2.Proto || ep.portMapping[1].Port != binding2.Port {
t.Fatalf("bridgeEndpoint has incorrect port mapping values")
}
if ep.portMapping[0].HostIP == nil || ep.portMapping[0].HostPort == 0 ||
ep.portMapping[1].HostIP == nil || ep.portMapping[1].HostPort == 0 {
t.Fatalf("operational port mapping data not found on bridgeEndpoint")
}
err = network.releasePorts(ep)
if err != nil {
t.Fatalf("Failed to release mapped ports: %v", err)
}
}

View File

@@ -0,0 +1,67 @@
package bridge
import (
"bytes"
"io/ioutil"
"regexp"
)
const (
ipv4NumBlock = `(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)`
ipv4Address = `(` + ipv4NumBlock + `\.){3}` + ipv4NumBlock
// This is not an IPv6 address verifier as it will accept a super-set of IPv6, and also
// will *not match* IPv4-Embedded IPv6 Addresses (RFC6052), but that and other variants
// -- e.g. other link-local types -- either won't work in containers or are unnecessary.
// For readability and sufficiency for Docker purposes this seemed more reasonable than a
// 1000+ character regexp with exact and complete IPv6 validation
ipv6Address = `([0-9A-Fa-f]{0,4}:){2,7}([0-9A-Fa-f]{0,4})`
)
var nsRegexp = regexp.MustCompile(`^\s*nameserver\s*((` + ipv4Address + `)|(` + ipv6Address + `))\s*$`)
func readResolvConf() ([]byte, error) {
resolv, err := ioutil.ReadFile("/etc/resolv.conf")
if err != nil {
return nil, err
}
return resolv, nil
}
// getLines parses input into lines and strips away comments.
func getLines(input []byte, commentMarker []byte) [][]byte {
lines := bytes.Split(input, []byte("\n"))
var output [][]byte
for _, currentLine := range lines {
var commentIndex = bytes.Index(currentLine, commentMarker)
if commentIndex == -1 {
output = append(output, currentLine)
} else {
output = append(output, currentLine[:commentIndex])
}
}
return output
}
// GetNameserversAsCIDR returns nameservers (if any) listed in
// /etc/resolv.conf as CIDR blocks (e.g., "1.2.3.4/32")
// This function's output is intended for net.ParseCIDR
func getNameserversAsCIDR(resolvConf []byte) []string {
nameservers := []string{}
for _, nameserver := range getNameservers(resolvConf) {
nameservers = append(nameservers, nameserver+"/32")
}
return nameservers
}
// GetNameservers returns nameservers (if any) listed in /etc/resolv.conf
func getNameservers(resolvConf []byte) []string {
nameservers := []string{}
for _, line := range getLines(resolvConf, []byte("#")) {
var ns = nsRegexp.FindSubmatch(line)
if len(ns) > 0 {
nameservers = append(nameservers, string(ns[1]))
}
}
return nameservers
}

View File

@@ -0,0 +1,53 @@
package bridge
import (
"bytes"
"testing"
)
func TestResolveConfRead(t *testing.T) {
b, err := readResolvConf()
if err != nil {
t.Fatalf("Failed to read resolv.conf: %v", err)
}
if b == nil {
t.Fatal("Reading resolv.conf returned no content")
}
}
func TestResolveConfReadLines(t *testing.T) {
commentChar := []byte("#")
b, _ := readResolvConf()
lines := getLines(b, commentChar)
if lines == nil {
t.Fatal("Failed to read resolv.conf lines")
}
for _, line := range lines {
if bytes.Index(line, commentChar) != -1 {
t.Fatal("Returned comment content from resolv.conf")
}
}
}
func TestResolvConfNameserversAsCIDR(t *testing.T) {
resolvConf := `# Commented line
nameserver 1.2.3.4
nameserver 5.6.7.8 # Test
`
cidrs := getNameserversAsCIDR([]byte(resolvConf))
if expected := 2; len(cidrs) != expected {
t.Fatalf("Expected %d nameservers, got %d", expected, len(cidrs))
}
expected := []string{"1.2.3.4/32", "5.6.7.8/32"}
for i, exp := range expected {
if cidrs[i] != exp {
t.Fatalf("Expected nameservers %s, got %s", exp, cidrs[i])
}
}
}

View File

@@ -0,0 +1,26 @@
package bridge
type setupStep func(*networkConfiguration, *bridgeInterface) error
type bridgeSetup struct {
config *networkConfiguration
bridge *bridgeInterface
steps []setupStep
}
func newBridgeSetup(c *networkConfiguration, i *bridgeInterface) *bridgeSetup {
return &bridgeSetup{config: c, bridge: i}
}
func (b *bridgeSetup) apply() error {
for _, fn := range b.steps {
if err := fn(b.config, b.bridge); err != nil {
return err
}
}
return nil
}
func (b *bridgeSetup) queueStep(step setupStep) {
b.steps = append(b.steps, step)
}

View File

@@ -0,0 +1,162 @@
package bridge
import (
"fmt"
"io/ioutil"
"os"
"syscall"
"github.com/Sirupsen/logrus"
)
// Enumeration type saying which versions of IP protocol to process.
type ipVersion int
const (
ipvnone ipVersion = iota
ipv4
ipv6
ipvboth
)
//Gets the IP version in use ( [ipv4], [ipv6] or [ipv4 and ipv6] )
func getIPVersion(config *networkConfiguration) ipVersion {
ipVersion := ipv4
if config.AddressIPv6 != nil || config.EnableIPv6 {
ipVersion |= ipv6
}
return ipVersion
}
func setupBridgeNetFiltering(config *networkConfiguration, i *bridgeInterface) error {
err := checkBridgeNetFiltering(config, i)
if err != nil {
if ptherr, ok := err.(*os.PathError); ok {
if errno, ok := ptherr.Err.(syscall.Errno); ok && errno == syscall.ENOENT {
if isRunningInContainer() {
logrus.Warnf("running inside docker container, ignoring missing kernel params: %v", err)
err = nil
} else {
err = fmt.Errorf("please ensure that br_netfilter kernel module is loaded")
}
}
}
if err != nil {
return fmt.Errorf("cannot restrict inter-container communication: %v", err)
}
}
return nil
}
//Enable bridge net filtering if ip forwarding is enabled. See github issue #11404
func checkBridgeNetFiltering(config *networkConfiguration, i *bridgeInterface) error {
ipVer := getIPVersion(config)
iface := config.BridgeName
doEnable := func(ipVer ipVersion) error {
var ipVerName string
if ipVer == ipv4 {
ipVerName = "IPv4"
} else {
ipVerName = "IPv6"
}
enabled, err := isPacketForwardingEnabled(ipVer, iface)
if err != nil {
logrus.Warnf("failed to check %s forwarding: %v", ipVerName, err)
} else if enabled {
enabled, err := getKernelBoolParam(getBridgeNFKernelParam(ipVer))
if err != nil || enabled {
return err
}
return setKernelBoolParam(getBridgeNFKernelParam(ipVer), true)
}
return nil
}
switch ipVer {
case ipv4, ipv6:
return doEnable(ipVer)
case ipvboth:
v4err := doEnable(ipv4)
v6err := doEnable(ipv6)
if v4err == nil {
return v6err
}
return v4err
default:
return nil
}
}
// Get kernel param path saying whether IPv${ipVer} traffic is being forwarded
// on particular interface. Interface may be specified for IPv6 only. If
// `iface` is empty, `default` will be assumed, which represents default value
// for new interfaces.
func getForwardingKernelParam(ipVer ipVersion, iface string) string {
switch ipVer {
case ipv4:
return "/proc/sys/net/ipv4/ip_forward"
case ipv6:
if iface == "" {
iface = "default"
}
return fmt.Sprintf("/proc/sys/net/ipv6/conf/%s/forwarding", iface)
default:
return ""
}
}
// Get kernel param path saying whether bridged IPv${ipVer} traffic shall be
// passed to ip${ipVer}tables' chains.
func getBridgeNFKernelParam(ipVer ipVersion) string {
switch ipVer {
case ipv4:
return "/proc/sys/net/bridge/bridge-nf-call-iptables"
case ipv6:
return "/proc/sys/net/bridge/bridge-nf-call-ip6tables"
default:
return ""
}
}
//Gets the value of the kernel parameters located at the given path
func getKernelBoolParam(path string) (bool, error) {
enabled := false
line, err := ioutil.ReadFile(path)
if err != nil {
return false, err
}
if len(line) > 0 {
enabled = line[0] == '1'
}
return enabled, err
}
//Sets the value of the kernel parameter located at the given path
func setKernelBoolParam(path string, on bool) error {
value := byte('0')
if on {
value = byte('1')
}
return ioutil.WriteFile(path, []byte{value, '\n'}, 0644)
}
//Checks to see if packet forwarding is enabled
func isPacketForwardingEnabled(ipVer ipVersion, iface string) (bool, error) {
switch ipVer {
case ipv4, ipv6:
return getKernelBoolParam(getForwardingKernelParam(ipVer, iface))
case ipvboth:
enabled, err := getKernelBoolParam(getForwardingKernelParam(ipv4, ""))
if err != nil || !enabled {
return enabled, err
}
return getKernelBoolParam(getForwardingKernelParam(ipv6, iface))
default:
return true, nil
}
}
func isRunningInContainer() bool {
_, err := os.Stat("/.dockerenv")
return !os.IsNotExist(err)
}

View File

@@ -0,0 +1,12 @@
package bridge
import "testing"
func TestIPConstantValues(t *testing.T) {
if ipv4|ipv6 != ipvboth {
t.Fatalf("bitwise or of ipv4(%04b) and ipv6(%04b) must yield ipvboth(%04b)", ipv4, ipv6, ipvboth)
}
if ipvboth&(^(ipv4 | ipv6)) != ipvnone {
t.Fatalf("ipvboth(%04b) with unset ipv4(%04b) and ipv6(%04b) bits shall equal to ipvnone", ipvboth, ipv4, ipv6)
}
}

View File

@@ -0,0 +1,66 @@
package bridge
import (
"fmt"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/parsers/kernel"
"github.com/docker/libnetwork/netutils"
"github.com/vishvananda/netlink"
)
// SetupDevice create a new bridge interface/
func setupDevice(config *networkConfiguration, i *bridgeInterface) error {
var setMac bool
// We only attempt to create the bridge when the requested device name is
// the default one.
if config.BridgeName != DefaultBridgeName && config.DefaultBridge {
return NonDefaultBridgeExistError(config.BridgeName)
}
// Set the bridgeInterface netlink.Bridge.
i.Link = &netlink.Bridge{
LinkAttrs: netlink.LinkAttrs{
Name: config.BridgeName,
},
}
// Only set the bridge's MAC address if the kernel version is > 3.3, as it
// was not supported before that.
kv, err := kernel.GetKernelVersion()
if err != nil {
logrus.Errorf("Failed to check kernel versions: %v. Will not assign a MAC address to the bridge interface", err)
} else {
setMac = kv.Kernel > 3 || (kv.Kernel == 3 && kv.Major >= 3)
}
if err = netlink.LinkAdd(i.Link); err != nil {
logrus.Debugf("Failed to create bridge %s via netlink. Trying ioctl", config.BridgeName)
return ioctlCreateBridge(config.BridgeName, setMac)
}
if setMac {
hwAddr := netutils.GenerateRandomMAC()
if err = netlink.LinkSetHardwareAddr(i.Link, hwAddr); err != nil {
return fmt.Errorf("failed to set bridge mac-address %s : %s", hwAddr, err.Error())
}
logrus.Debugf("Setting bridge mac address to %s", hwAddr)
}
return err
}
// SetupDeviceUp ups the given bridge interface.
func setupDeviceUp(config *networkConfiguration, i *bridgeInterface) error {
err := netlink.LinkSetUp(i.Link)
if err != nil {
return err
}
// Attempt to update the bridge interface to refresh the flags status,
// ignoring any failure to do so.
if lnk, err := netlink.LinkByName(config.BridgeName); err == nil {
i.Link = lnk
}
return nil
}

View File

@@ -0,0 +1,76 @@
package bridge
import (
"bytes"
"net"
"testing"
"github.com/docker/libnetwork/netutils"
"github.com/docker/libnetwork/testutils"
"github.com/vishvananda/netlink"
)
func TestSetupNewBridge(t *testing.T) {
defer testutils.SetupTestOSContext(t)()
config := &networkConfiguration{BridgeName: DefaultBridgeName}
br := &bridgeInterface{}
if err := setupDevice(config, br); err != nil {
t.Fatalf("Bridge creation failed: %v", err)
}
if br.Link == nil {
t.Fatal("bridgeInterface link is nil (expected valid link)")
}
if _, err := netlink.LinkByName(DefaultBridgeName); err != nil {
t.Fatalf("Failed to retrieve bridge device: %v", err)
}
if br.Link.Attrs().Flags&net.FlagUp == net.FlagUp {
t.Fatalf("bridgeInterface should be created down")
}
}
func TestSetupNewNonDefaultBridge(t *testing.T) {
defer testutils.SetupTestOSContext(t)()
config := &networkConfiguration{BridgeName: "test0", DefaultBridge: true}
br := &bridgeInterface{}
err := setupDevice(config, br)
if err == nil {
t.Fatal("Expected bridge creation failure with \"non default name\", succeeded")
}
if _, ok := err.(NonDefaultBridgeExistError); !ok {
t.Fatalf("Did not fail with expected error. Actual error: %v", err)
}
}
func TestSetupDeviceUp(t *testing.T) {
defer testutils.SetupTestOSContext(t)()
config := &networkConfiguration{BridgeName: DefaultBridgeName}
br := &bridgeInterface{}
if err := setupDevice(config, br); err != nil {
t.Fatalf("Bridge creation failed: %v", err)
}
if err := setupDeviceUp(config, br); err != nil {
t.Fatalf("Failed to up bridge device: %v", err)
}
lnk, _ := netlink.LinkByName(DefaultBridgeName)
if lnk.Attrs().Flags&net.FlagUp != net.FlagUp {
t.Fatalf("bridgeInterface should be up")
}
}
func TestGenerateRandomMAC(t *testing.T) {
defer testutils.SetupTestOSContext(t)()
mac1 := netutils.GenerateRandomMAC()
mac2 := netutils.GenerateRandomMAC()
if bytes.Compare(mac1, mac2) == 0 {
t.Fatalf("Generated twice the same MAC address %v", mac1)
}
}

View File

@@ -0,0 +1,20 @@
package bridge
import "github.com/docker/libnetwork/iptables"
func (n *bridgeNetwork) setupFirewalld(config *networkConfiguration, i *bridgeInterface) error {
d := n.driver
d.Lock()
driverConfig := d.config
d.Unlock()
// Sanity check.
if driverConfig.EnableIPTables == false {
return IPTableCfgError(config.BridgeName)
}
iptables.OnReloaded(func() { n.setupIPTables(config, i) })
iptables.OnReloaded(n.portMapper.ReMapAll)
return nil
}

View File

@@ -0,0 +1,29 @@
package bridge
import (
"fmt"
"io/ioutil"
)
const (
ipv4ForwardConf = "/proc/sys/net/ipv4/ip_forward"
ipv4ForwardConfPerm = 0644
)
func setupIPForwarding() error {
// Get current IPv4 forward setup
ipv4ForwardData, err := ioutil.ReadFile(ipv4ForwardConf)
if err != nil {
return fmt.Errorf("Cannot read IP forwarding setup: %v", err)
}
// Enable IPv4 forwarding only if it is not already enabled
if ipv4ForwardData[0] != '1' {
// Enable IPv4 forwarding
if err := ioutil.WriteFile(ipv4ForwardConf, []byte{'1', '\n'}, ipv4ForwardConfPerm); err != nil {
return fmt.Errorf("Setup IP forwarding failed: %v", err)
}
}
return nil
}

View File

@@ -0,0 +1,51 @@
package bridge
import (
"bytes"
"io/ioutil"
"testing"
)
func TestSetupIPForwarding(t *testing.T) {
// Read current setting and ensure the original value gets restored
procSetting := readCurrentIPForwardingSetting(t)
defer reconcileIPForwardingSetting(t, procSetting)
// Disable IP Forwarding if enabled
if bytes.Compare(procSetting, []byte("1\n")) == 0 {
writeIPForwardingSetting(t, []byte{'0', '\n'})
}
// Set IP Forwarding
if err := setupIPForwarding(); err != nil {
t.Fatalf("Failed to setup IP forwarding: %v", err)
}
// Read new setting
procSetting = readCurrentIPForwardingSetting(t)
if bytes.Compare(procSetting, []byte("1\n")) != 0 {
t.Fatalf("Failed to effectively setup IP forwarding")
}
}
func readCurrentIPForwardingSetting(t *testing.T) []byte {
procSetting, err := ioutil.ReadFile(ipv4ForwardConf)
if err != nil {
t.Fatalf("Can't execute test: Failed to read current IP forwarding setting: %v", err)
}
return procSetting
}
func writeIPForwardingSetting(t *testing.T, chars []byte) {
err := ioutil.WriteFile(ipv4ForwardConf, chars, ipv4ForwardConfPerm)
if err != nil {
t.Fatalf("Can't execute or cleanup after test: Failed to reset IP forwarding: %v", err)
}
}
func reconcileIPForwardingSetting(t *testing.T, original []byte) {
current := readCurrentIPForwardingSetting(t)
if bytes.Compare(original, current) != 0 {
writeIPForwardingSetting(t, original)
}
}

View File

@@ -0,0 +1,345 @@
package bridge
import (
"fmt"
"net"
"github.com/Sirupsen/logrus"
"github.com/docker/libnetwork/iptables"
"github.com/docker/libnetwork/netutils"
)
// DockerChain: DOCKER iptable chain name
const (
DockerChain = "DOCKER"
IsolationChain = "DOCKER-ISOLATION"
)
func setupIPChains(config *configuration) (*iptables.ChainInfo, *iptables.ChainInfo, *iptables.ChainInfo, error) {
// Sanity check.
if config.EnableIPTables == false {
return nil, nil, nil, fmt.Errorf("cannot create new chains, EnableIPTable is disabled")
}
hairpinMode := !config.EnableUserlandProxy
natChain, err := iptables.NewChain(DockerChain, iptables.Nat, hairpinMode)
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to create NAT chain: %v", err)
}
defer func() {
if err != nil {
if err := iptables.RemoveExistingChain(DockerChain, iptables.Nat); err != nil {
logrus.Warnf("failed on removing iptables NAT chain on cleanup: %v", err)
}
}
}()
filterChain, err := iptables.NewChain(DockerChain, iptables.Filter, false)
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to create FILTER chain: %v", err)
}
defer func() {
if err != nil {
if err := iptables.RemoveExistingChain(DockerChain, iptables.Filter); err != nil {
logrus.Warnf("failed on removing iptables FILTER chain on cleanup: %v", err)
}
}
}()
isolationChain, err := iptables.NewChain(IsolationChain, iptables.Filter, false)
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to create FILTER isolation chain: %v", err)
}
if err := addReturnRule(IsolationChain); err != nil {
return nil, nil, nil, err
}
return natChain, filterChain, isolationChain, nil
}
func (n *bridgeNetwork) setupIPTables(config *networkConfiguration, i *bridgeInterface) error {
d := n.driver
d.Lock()
driverConfig := d.config
d.Unlock()
// Sanity check.
if driverConfig.EnableIPTables == false {
return fmt.Errorf("Cannot program chains, EnableIPTable is disabled")
}
// Pickup this configuraton option from driver
hairpinMode := !driverConfig.EnableUserlandProxy
addrv4, _, err := netutils.GetIfaceAddr(config.BridgeName)
if err != nil {
return fmt.Errorf("Failed to setup IP tables, cannot acquire Interface address: %s", err.Error())
}
ipnet := addrv4.(*net.IPNet)
maskedAddrv4 := &net.IPNet{
IP: ipnet.IP.Mask(ipnet.Mask),
Mask: ipnet.Mask,
}
if config.Internal {
if err = setupInternalNetworkRules(config.BridgeName, maskedAddrv4, true); err != nil {
return fmt.Errorf("Failed to Setup IP tables: %s", err.Error())
}
n.registerIptCleanFunc(func() error {
return setupInternalNetworkRules(config.BridgeName, maskedAddrv4, false)
})
} else {
if err = setupIPTablesInternal(config.BridgeName, maskedAddrv4, config.EnableICC, config.EnableIPMasquerade, hairpinMode, true); err != nil {
return fmt.Errorf("Failed to Setup IP tables: %s", err.Error())
}
n.registerIptCleanFunc(func() error {
return setupIPTablesInternal(config.BridgeName, maskedAddrv4, config.EnableICC, config.EnableIPMasquerade, hairpinMode, false)
})
natChain, filterChain, _, err := n.getDriverChains()
if err != nil {
return fmt.Errorf("Failed to setup IP tables, cannot acquire chain info %s", err.Error())
}
err = iptables.ProgramChain(natChain, config.BridgeName, hairpinMode, true)
if err != nil {
return fmt.Errorf("Failed to program NAT chain: %s", err.Error())
}
err = iptables.ProgramChain(filterChain, config.BridgeName, hairpinMode, true)
if err != nil {
return fmt.Errorf("Failed to program FILTER chain: %s", err.Error())
}
n.registerIptCleanFunc(func() error {
return iptables.ProgramChain(filterChain, config.BridgeName, hairpinMode, false)
})
n.portMapper.SetIptablesChain(filterChain, n.getNetworkBridgeName())
}
if err := ensureJumpRule("FORWARD", IsolationChain); err != nil {
return err
}
return nil
}
type iptRule struct {
table iptables.Table
chain string
preArgs []string
args []string
}
func setupIPTablesInternal(bridgeIface string, addr net.Addr, icc, ipmasq, hairpin, enable bool) error {
var (
address = addr.String()
natRule = iptRule{table: iptables.Nat, chain: "POSTROUTING", preArgs: []string{"-t", "nat"}, args: []string{"-s", address, "!", "-o", bridgeIface, "-j", "MASQUERADE"}}
hpNatRule = iptRule{table: iptables.Nat, chain: "POSTROUTING", preArgs: []string{"-t", "nat"}, args: []string{"-m", "addrtype", "--src-type", "LOCAL", "-o", bridgeIface, "-j", "MASQUERADE"}}
outRule = iptRule{table: iptables.Filter, chain: "FORWARD", args: []string{"-i", bridgeIface, "!", "-o", bridgeIface, "-j", "ACCEPT"}}
inRule = iptRule{table: iptables.Filter, chain: "FORWARD", args: []string{"-o", bridgeIface, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"}}
)
// Set NAT.
if ipmasq {
if err := programChainRule(natRule, "NAT", enable); err != nil {
return err
}
}
// In hairpin mode, masquerade traffic from localhost
if hairpin {
if err := programChainRule(hpNatRule, "MASQ LOCAL HOST", enable); err != nil {
return err
}
}
// Set Inter Container Communication.
if err := setIcc(bridgeIface, icc, enable); err != nil {
return err
}
// Set Accept on all non-intercontainer outgoing packets.
if err := programChainRule(outRule, "ACCEPT NON_ICC OUTGOING", enable); err != nil {
return err
}
// Set Accept on incoming packets for existing connections.
if err := programChainRule(inRule, "ACCEPT INCOMING", enable); err != nil {
return err
}
return nil
}
func programChainRule(rule iptRule, ruleDescr string, insert bool) error {
var (
prefix []string
operation string
condition bool
doesExist = iptables.Exists(rule.table, rule.chain, rule.args...)
)
if insert {
condition = !doesExist
prefix = []string{"-I", rule.chain}
operation = "enable"
} else {
condition = doesExist
prefix = []string{"-D", rule.chain}
operation = "disable"
}
if rule.preArgs != nil {
prefix = append(rule.preArgs, prefix...)
}
if condition {
if err := iptables.RawCombinedOutput(append(prefix, rule.args...)...); err != nil {
return fmt.Errorf("Unable to %s %s rule: %s", operation, ruleDescr, err.Error())
}
}
return nil
}
func setIcc(bridgeIface string, iccEnable, insert bool) error {
var (
table = iptables.Filter
chain = "FORWARD"
args = []string{"-i", bridgeIface, "-o", bridgeIface, "-j"}
acceptArgs = append(args, "ACCEPT")
dropArgs = append(args, "DROP")
)
if insert {
if !iccEnable {
iptables.Raw(append([]string{"-D", chain}, acceptArgs...)...)
if !iptables.Exists(table, chain, dropArgs...) {
if err := iptables.RawCombinedOutput(append([]string{"-A", chain}, dropArgs...)...); err != nil {
return fmt.Errorf("Unable to prevent intercontainer communication: %s", err.Error())
}
}
} else {
iptables.Raw(append([]string{"-D", chain}, dropArgs...)...)
if !iptables.Exists(table, chain, acceptArgs...) {
if err := iptables.RawCombinedOutput(append([]string{"-I", chain}, acceptArgs...)...); err != nil {
return fmt.Errorf("Unable to allow intercontainer communication: %s", err.Error())
}
}
}
} else {
// Remove any ICC rule.
if !iccEnable {
if iptables.Exists(table, chain, dropArgs...) {
iptables.Raw(append([]string{"-D", chain}, dropArgs...)...)
}
} else {
if iptables.Exists(table, chain, acceptArgs...) {
iptables.Raw(append([]string{"-D", chain}, acceptArgs...)...)
}
}
}
return nil
}
// Control Inter Network Communication. Install/remove only if it is not/is present.
func setINC(iface1, iface2 string, enable bool) error {
var (
table = iptables.Filter
chain = IsolationChain
args = [2][]string{{"-i", iface1, "-o", iface2, "-j", "DROP"}, {"-i", iface2, "-o", iface1, "-j", "DROP"}}
)
if enable {
for i := 0; i < 2; i++ {
if iptables.Exists(table, chain, args[i]...) {
continue
}
if err := iptables.RawCombinedOutput(append([]string{"-I", chain}, args[i]...)...); err != nil {
return fmt.Errorf("unable to add inter-network communication rule: %v", err)
}
}
} else {
for i := 0; i < 2; i++ {
if !iptables.Exists(table, chain, args[i]...) {
continue
}
if err := iptables.RawCombinedOutput(append([]string{"-D", chain}, args[i]...)...); err != nil {
return fmt.Errorf("unable to remove inter-network communication rule: %v", err)
}
}
}
return nil
}
func addReturnRule(chain string) error {
var (
table = iptables.Filter
args = []string{"-j", "RETURN"}
)
if iptables.Exists(table, chain, args...) {
return nil
}
err := iptables.RawCombinedOutput(append([]string{"-I", chain}, args...)...)
if err != nil {
return fmt.Errorf("unable to add return rule in %s chain: %s", chain, err.Error())
}
return nil
}
// Ensure the jump rule is on top
func ensureJumpRule(fromChain, toChain string) error {
var (
table = iptables.Filter
args = []string{"-j", toChain}
)
if iptables.Exists(table, fromChain, args...) {
err := iptables.RawCombinedOutput(append([]string{"-D", fromChain}, args...)...)
if err != nil {
return fmt.Errorf("unable to remove jump to %s rule in %s chain: %s", toChain, fromChain, err.Error())
}
}
err := iptables.RawCombinedOutput(append([]string{"-I", fromChain}, args...)...)
if err != nil {
return fmt.Errorf("unable to insert jump to %s rule in %s chain: %s", toChain, fromChain, err.Error())
}
return nil
}
func removeIPChains() {
for _, chainInfo := range []iptables.ChainInfo{
{Name: DockerChain, Table: iptables.Nat},
{Name: DockerChain, Table: iptables.Filter},
{Name: IsolationChain, Table: iptables.Filter},
} {
if err := chainInfo.Remove(); err != nil {
logrus.Warnf("Failed to remove existing iptables entries in table %s chain %s : %v", chainInfo.Table, chainInfo.Name, err)
}
}
}
func setupInternalNetworkRules(bridgeIface string, addr net.Addr, insert bool) error {
var (
inDropRule = iptRule{table: iptables.Filter, chain: IsolationChain, args: []string{"-i", bridgeIface, "!", "-d", addr.String(), "-j", "DROP"}}
outDropRule = iptRule{table: iptables.Filter, chain: IsolationChain, args: []string{"-o", bridgeIface, "!", "-s", addr.String(), "-j", "DROP"}}
)
if err := programChainRule(inDropRule, "DROP INCOMING", insert); err != nil {
return err
}
if err := programChainRule(outDropRule, "DROP OUTGOING", insert); err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,124 @@
package bridge
import (
"net"
"testing"
"github.com/docker/libnetwork/iptables"
"github.com/docker/libnetwork/portmapper"
"github.com/docker/libnetwork/testutils"
)
const (
iptablesTestBridgeIP = "192.168.42.1"
)
func TestProgramIPTable(t *testing.T) {
// Create a test bridge with a basic bridge configuration (name + IPv4).
defer testutils.SetupTestOSContext(t)()
createTestBridge(getBasicTestConfig(), &bridgeInterface{}, t)
// Store various iptables chain rules we care for.
rules := []struct {
rule iptRule
descr string
}{
{iptRule{table: iptables.Filter, chain: "FORWARD", args: []string{"-d", "127.1.2.3", "-i", "lo", "-o", "lo", "-j", "DROP"}}, "Test Loopback"},
{iptRule{table: iptables.Nat, chain: "POSTROUTING", preArgs: []string{"-t", "nat"}, args: []string{"-s", iptablesTestBridgeIP, "!", "-o", DefaultBridgeName, "-j", "MASQUERADE"}}, "NAT Test"},
{iptRule{table: iptables.Filter, chain: "FORWARD", args: []string{"-i", DefaultBridgeName, "!", "-o", DefaultBridgeName, "-j", "ACCEPT"}}, "Test ACCEPT NON_ICC OUTGOING"},
{iptRule{table: iptables.Filter, chain: "FORWARD", args: []string{"-o", DefaultBridgeName, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"}}, "Test ACCEPT INCOMING"},
{iptRule{table: iptables.Filter, chain: "FORWARD", args: []string{"-i", DefaultBridgeName, "-o", DefaultBridgeName, "-j", "ACCEPT"}}, "Test enable ICC"},
{iptRule{table: iptables.Filter, chain: "FORWARD", args: []string{"-i", DefaultBridgeName, "-o", DefaultBridgeName, "-j", "DROP"}}, "Test disable ICC"},
}
// Assert the chain rules' insertion and removal.
for _, c := range rules {
assertIPTableChainProgramming(c.rule, c.descr, t)
}
}
func TestSetupIPChains(t *testing.T) {
// Create a test bridge with a basic bridge configuration (name + IPv4).
defer testutils.SetupTestOSContext(t)()
driverconfig := &configuration{
EnableIPTables: true,
}
d := &driver{
config: driverconfig,
}
assertChainConfig(d, t)
config := getBasicTestConfig()
br := &bridgeInterface{}
createTestBridge(config, br, t)
assertBridgeConfig(config, br, d, t)
config.EnableIPMasquerade = true
assertBridgeConfig(config, br, d, t)
config.EnableICC = true
assertBridgeConfig(config, br, d, t)
config.EnableIPMasquerade = false
assertBridgeConfig(config, br, d, t)
}
func getBasicTestConfig() *networkConfiguration {
config := &networkConfiguration{
BridgeName: DefaultBridgeName,
AddressIPv4: &net.IPNet{IP: net.ParseIP(iptablesTestBridgeIP), Mask: net.CIDRMask(16, 32)}}
return config
}
func createTestBridge(config *networkConfiguration, br *bridgeInterface, t *testing.T) {
if err := setupDevice(config, br); err != nil {
t.Fatalf("Failed to create the testing Bridge: %s", err.Error())
}
if err := setupBridgeIPv4(config, br); err != nil {
t.Fatalf("Failed to bring up the testing Bridge: %s", err.Error())
}
}
// Assert base function which pushes iptables chain rules on insertion and removal.
func assertIPTableChainProgramming(rule iptRule, descr string, t *testing.T) {
// Add
if err := programChainRule(rule, descr, true); err != nil {
t.Fatalf("Failed to program iptable rule %s: %s", descr, err.Error())
}
if iptables.Exists(rule.table, rule.chain, rule.args...) == false {
t.Fatalf("Failed to effectively program iptable rule: %s", descr)
}
// Remove
if err := programChainRule(rule, descr, false); err != nil {
t.Fatalf("Failed to remove iptable rule %s: %s", descr, err.Error())
}
if iptables.Exists(rule.table, rule.chain, rule.args...) == true {
t.Fatalf("Failed to effectively remove iptable rule: %s", descr)
}
}
// Assert function which create chains.
func assertChainConfig(d *driver, t *testing.T) {
var err error
d.natChain, d.filterChain, d.isolationChain, err = setupIPChains(d.config)
if err != nil {
t.Fatal(err)
}
}
// Assert function which pushes chains based on bridge config parameters.
func assertBridgeConfig(config *networkConfiguration, br *bridgeInterface, d *driver, t *testing.T) {
nw := bridgeNetwork{portMapper: portmapper.New(),
config: config}
nw.driver = d
// Attempt programming of ip tables.
err := nw.setupIPTables(config, br)
if err != nil {
t.Fatalf("%v", err)
}
}

View File

@@ -0,0 +1,62 @@
package bridge
import (
"fmt"
"io/ioutil"
"path/filepath"
log "github.com/Sirupsen/logrus"
"github.com/docker/libnetwork/types"
"github.com/vishvananda/netlink"
)
func setupBridgeIPv4(config *networkConfiguration, i *bridgeInterface) error {
addrv4, _, err := i.addresses()
if err != nil {
return fmt.Errorf("failed to retrieve bridge interface addresses: %v", err)
}
if !types.CompareIPNet(addrv4.IPNet, config.AddressIPv4) {
if addrv4.IPNet != nil {
if err := netlink.AddrDel(i.Link, &addrv4); err != nil {
return fmt.Errorf("failed to remove current ip address from bridge: %v", err)
}
}
log.Debugf("Assigning address to bridge interface %s: %s", config.BridgeName, config.AddressIPv4)
if err := netlink.AddrAdd(i.Link, &netlink.Addr{IPNet: config.AddressIPv4}); err != nil {
return &IPv4AddrAddError{IP: config.AddressIPv4, Err: err}
}
}
// Store bridge network and default gateway
i.bridgeIPv4 = config.AddressIPv4
i.gatewayIPv4 = config.AddressIPv4.IP
return nil
}
func setupGatewayIPv4(config *networkConfiguration, i *bridgeInterface) error {
if !i.bridgeIPv4.Contains(config.DefaultGatewayIPv4) {
return &ErrInvalidGateway{}
}
// Store requested default gateway
i.gatewayIPv4 = config.DefaultGatewayIPv4
return nil
}
func setupLoopbackAdressesRouting(config *networkConfiguration, i *bridgeInterface) error {
sysPath := filepath.Join("/proc/sys/net/ipv4/conf", config.BridgeName, "route_localnet")
ipv4LoRoutingData, err := ioutil.ReadFile(sysPath)
if err != nil {
return fmt.Errorf("Cannot read IPv4 local routing setup: %v", err)
}
// Enable loopback adresses routing only if it isn't already enabled
if ipv4LoRoutingData[0] != '1' {
if err := ioutil.WriteFile(sysPath, []byte{'1', '\n'}, 0644); err != nil {
return fmt.Errorf("Unable to enable local routing for hairpin mode: %v", err)
}
}
return nil
}

View File

@@ -0,0 +1,74 @@
package bridge
import (
"net"
"testing"
"github.com/docker/libnetwork/testutils"
"github.com/vishvananda/netlink"
)
func setupTestInterface(t *testing.T) (*networkConfiguration, *bridgeInterface) {
config := &networkConfiguration{
BridgeName: DefaultBridgeName}
br := &bridgeInterface{}
if err := setupDevice(config, br); err != nil {
t.Fatalf("Bridge creation failed: %v", err)
}
return config, br
}
func TestSetupBridgeIPv4Fixed(t *testing.T) {
defer testutils.SetupTestOSContext(t)()
ip, netw, err := net.ParseCIDR("192.168.1.1/24")
if err != nil {
t.Fatalf("Failed to parse bridge IPv4: %v", err)
}
config, br := setupTestInterface(t)
config.AddressIPv4 = &net.IPNet{IP: ip, Mask: netw.Mask}
if err := setupBridgeIPv4(config, br); err != nil {
t.Fatalf("Failed to setup bridge IPv4: %v", err)
}
addrsv4, err := netlink.AddrList(br.Link, netlink.FAMILY_V4)
if err != nil {
t.Fatalf("Failed to list device IPv4 addresses: %v", err)
}
var found bool
for _, addr := range addrsv4 {
if config.AddressIPv4.String() == addr.IPNet.String() {
found = true
break
}
}
if !found {
t.Fatalf("Bridge device does not have requested IPv4 address %v", config.AddressIPv4)
}
}
func TestSetupGatewayIPv4(t *testing.T) {
defer testutils.SetupTestOSContext(t)()
ip, nw, _ := net.ParseCIDR("192.168.0.24/16")
nw.IP = ip
gw := net.ParseIP("192.168.2.254")
config := &networkConfiguration{
BridgeName: DefaultBridgeName,
DefaultGatewayIPv4: gw}
br := &bridgeInterface{bridgeIPv4: nw}
if err := setupGatewayIPv4(config, br); err != nil {
t.Fatalf("Set Default Gateway failed: %v", err)
}
if !gw.Equal(br.gatewayIPv4) {
t.Fatalf("Set Default Gateway failed. Expected %v, Found %v", gw, br.gatewayIPv4)
}
}

View File

@@ -0,0 +1,119 @@
package bridge
import (
"fmt"
"io/ioutil"
"net"
"os"
"github.com/Sirupsen/logrus"
"github.com/docker/libnetwork/types"
"github.com/vishvananda/netlink"
)
var bridgeIPv6 *net.IPNet
const (
bridgeIPv6Str = "fe80::1/64"
ipv6ForwardConfPerm = 0644
ipv6ForwardConfDefault = "/proc/sys/net/ipv6/conf/default/forwarding"
ipv6ForwardConfAll = "/proc/sys/net/ipv6/conf/all/forwarding"
)
func init() {
// We allow ourselves to panic in this special case because we indicate a
// failure to parse a compile-time define constant.
var err error
if bridgeIPv6, err = types.ParseCIDR(bridgeIPv6Str); err != nil {
panic(fmt.Sprintf("Cannot parse default bridge IPv6 address %q: %v", bridgeIPv6Str, err))
}
}
func setupBridgeIPv6(config *networkConfiguration, i *bridgeInterface) error {
procFile := "/proc/sys/net/ipv6/conf/" + config.BridgeName + "/disable_ipv6"
ipv6BridgeData, err := ioutil.ReadFile(procFile)
if err != nil {
return fmt.Errorf("Cannot read IPv6 setup for bridge %v: %v", config.BridgeName, err)
}
// Enable IPv6 on the bridge only if it isn't already enabled
if ipv6BridgeData[0] != '0' {
if err := ioutil.WriteFile(procFile, []byte{'0', '\n'}, ipv6ForwardConfPerm); err != nil {
return fmt.Errorf("Unable to enable IPv6 addresses on bridge: %v", err)
}
}
// Store bridge network and default gateway
i.bridgeIPv6 = bridgeIPv6
i.gatewayIPv6 = i.bridgeIPv6.IP
if err := i.programIPv6Address(); err != nil {
return err
}
if config.AddressIPv6 == nil {
return nil
}
// Store the user specified bridge network and network gateway and program it
i.bridgeIPv6 = config.AddressIPv6
i.gatewayIPv6 = config.AddressIPv6.IP
if err := i.programIPv6Address(); err != nil {
return err
}
// Setting route to global IPv6 subnet
logrus.Debugf("Adding route to IPv6 network %s via device %s", config.AddressIPv6.String(), config.BridgeName)
err = netlink.RouteAdd(&netlink.Route{
Scope: netlink.SCOPE_UNIVERSE,
LinkIndex: i.Link.Attrs().Index,
Dst: config.AddressIPv6,
})
if err != nil && !os.IsExist(err) {
logrus.Errorf("Could not add route to IPv6 network %s via device %s", config.AddressIPv6.String(), config.BridgeName)
}
return nil
}
func setupGatewayIPv6(config *networkConfiguration, i *bridgeInterface) error {
if config.AddressIPv6 == nil {
return &ErrInvalidContainerSubnet{}
}
if !config.AddressIPv6.Contains(config.DefaultGatewayIPv6) {
return &ErrInvalidGateway{}
}
// Store requested default gateway
i.gatewayIPv6 = config.DefaultGatewayIPv6
return nil
}
func setupIPv6Forwarding(config *networkConfiguration, i *bridgeInterface) error {
// Get current IPv6 default forwarding setup
ipv6ForwardDataDefault, err := ioutil.ReadFile(ipv6ForwardConfDefault)
if err != nil {
return fmt.Errorf("Cannot read IPv6 default forwarding setup: %v", err)
}
// Enable IPv6 default forwarding only if it is not already enabled
if ipv6ForwardDataDefault[0] != '1' {
if err := ioutil.WriteFile(ipv6ForwardConfDefault, []byte{'1', '\n'}, ipv6ForwardConfPerm); err != nil {
logrus.Warnf("Unable to enable IPv6 default forwarding: %v", err)
}
}
// Get current IPv6 all forwarding setup
ipv6ForwardDataAll, err := ioutil.ReadFile(ipv6ForwardConfAll)
if err != nil {
return fmt.Errorf("Cannot read IPv6 all forwarding setup: %v", err)
}
// Enable IPv6 all forwarding only if it is not already enabled
if ipv6ForwardDataAll[0] != '1' {
if err := ioutil.WriteFile(ipv6ForwardConfAll, []byte{'1', '\n'}, ipv6ForwardConfPerm); err != nil {
logrus.Warnf("Unable to enable IPv6 all forwarding: %v", err)
}
}
return nil
}

View File

@@ -0,0 +1,70 @@
package bridge
import (
"bytes"
"fmt"
"io/ioutil"
"net"
"testing"
"github.com/docker/libnetwork/testutils"
"github.com/vishvananda/netlink"
)
func TestSetupIPv6(t *testing.T) {
defer testutils.SetupTestOSContext(t)()
config, br := setupTestInterface(t)
if err := setupBridgeIPv6(config, br); err != nil {
t.Fatalf("Failed to setup bridge IPv6: %v", err)
}
procSetting, err := ioutil.ReadFile(fmt.Sprintf("/proc/sys/net/ipv6/conf/%s/disable_ipv6", config.BridgeName))
if err != nil {
t.Fatalf("Failed to read disable_ipv6 kernel setting: %v", err)
}
if expected := []byte("0\n"); bytes.Compare(expected, procSetting) != 0 {
t.Fatalf("Invalid kernel setting disable_ipv6: expected %q, got %q", string(expected), string(procSetting))
}
addrsv6, err := netlink.AddrList(br.Link, netlink.FAMILY_V6)
if err != nil {
t.Fatalf("Failed to list device IPv6 addresses: %v", err)
}
var found bool
for _, addr := range addrsv6 {
if bridgeIPv6Str == addr.IPNet.String() {
found = true
break
}
}
if !found {
t.Fatalf("Bridge device does not have requested IPv6 address %v", bridgeIPv6Str)
}
}
func TestSetupGatewayIPv6(t *testing.T) {
defer testutils.SetupTestOSContext(t)()
_, nw, _ := net.ParseCIDR("2001:db8:ea9:9abc:ffff::/80")
gw := net.ParseIP("2001:db8:ea9:9abc:ffff::254")
config := &networkConfiguration{
BridgeName: DefaultBridgeName,
AddressIPv6: nw,
DefaultGatewayIPv6: gw}
br := &bridgeInterface{}
if err := setupGatewayIPv6(config, br); err != nil {
t.Fatalf("Set Default Gateway failed: %v", err)
}
if !gw.Equal(br.gatewayIPv6) {
t.Fatalf("Set Default Gateway failed. Expected %v, Found %v", gw, br.gatewayIPv6)
}
}

View File

@@ -0,0 +1,51 @@
package bridge
import (
log "github.com/Sirupsen/logrus"
"github.com/docker/libnetwork/types"
"github.com/vishvananda/netlink"
)
func setupVerifyAndReconcile(config *networkConfiguration, i *bridgeInterface) error {
// Fetch a single IPv4 and a slice of IPv6 addresses from the bridge.
addrv4, addrsv6, err := i.addresses()
if err != nil {
return err
}
// Verify that the bridge does have an IPv4 address.
if addrv4.IPNet == nil {
return &ErrNoIPAddr{}
}
// Verify that the bridge IPv4 address matches the requested configuration.
if config.AddressIPv4 != nil && !addrv4.IP.Equal(config.AddressIPv4.IP) {
return &IPv4AddrNoMatchError{IP: addrv4.IP, CfgIP: config.AddressIPv4.IP}
}
// Verify that one of the bridge IPv6 addresses matches the requested
// configuration.
if config.EnableIPv6 && !findIPv6Address(netlink.Addr{IPNet: bridgeIPv6}, addrsv6) {
return (*IPv6AddrNoMatchError)(bridgeIPv6)
}
// Release any residual IPv6 address that might be there because of older daemon instances
for _, addrv6 := range addrsv6 {
if addrv6.IP.IsGlobalUnicast() && !types.CompareIPNet(addrv6.IPNet, i.bridgeIPv6) {
if err := netlink.AddrDel(i.Link, &addrv6); err != nil {
log.Warnf("Failed to remove residual IPv6 address %s from bridge: %v", addrv6.IPNet, err)
}
}
}
return nil
}
func findIPv6Address(addr netlink.Addr, addresses []netlink.Addr) bool {
for _, addrv6 := range addresses {
if addrv6.String() == addr.String() {
return true
}
}
return false
}

View File

@@ -0,0 +1,110 @@
package bridge
import (
"net"
"testing"
"github.com/docker/libnetwork/testutils"
"github.com/vishvananda/netlink"
)
func setupVerifyTest(t *testing.T) *bridgeInterface {
inf := &bridgeInterface{}
br := netlink.Bridge{}
br.LinkAttrs.Name = "default0"
if err := netlink.LinkAdd(&br); err == nil {
inf.Link = &br
} else {
t.Fatalf("Failed to create bridge interface: %v", err)
}
return inf
}
func TestSetupVerify(t *testing.T) {
defer testutils.SetupTestOSContext(t)()
addrv4 := net.IPv4(192, 168, 1, 1)
inf := setupVerifyTest(t)
config := &networkConfiguration{}
config.AddressIPv4 = &net.IPNet{IP: addrv4, Mask: addrv4.DefaultMask()}
if err := netlink.AddrAdd(inf.Link, &netlink.Addr{IPNet: config.AddressIPv4}); err != nil {
t.Fatalf("Failed to assign IPv4 %s to interface: %v", config.AddressIPv4, err)
}
if err := setupVerifyAndReconcile(config, inf); err != nil {
t.Fatalf("Address verification failed: %v", err)
}
}
func TestSetupVerifyBad(t *testing.T) {
defer testutils.SetupTestOSContext(t)()
addrv4 := net.IPv4(192, 168, 1, 1)
inf := setupVerifyTest(t)
config := &networkConfiguration{}
config.AddressIPv4 = &net.IPNet{IP: addrv4, Mask: addrv4.DefaultMask()}
ipnet := &net.IPNet{IP: net.IPv4(192, 168, 1, 2), Mask: addrv4.DefaultMask()}
if err := netlink.AddrAdd(inf.Link, &netlink.Addr{IPNet: ipnet}); err != nil {
t.Fatalf("Failed to assign IPv4 %s to interface: %v", ipnet, err)
}
if err := setupVerifyAndReconcile(config, inf); err == nil {
t.Fatal("Address verification was expected to fail")
}
}
func TestSetupVerifyMissing(t *testing.T) {
defer testutils.SetupTestOSContext(t)()
addrv4 := net.IPv4(192, 168, 1, 1)
inf := setupVerifyTest(t)
config := &networkConfiguration{}
config.AddressIPv4 = &net.IPNet{IP: addrv4, Mask: addrv4.DefaultMask()}
if err := setupVerifyAndReconcile(config, inf); err == nil {
t.Fatal("Address verification was expected to fail")
}
}
func TestSetupVerifyIPv6(t *testing.T) {
defer testutils.SetupTestOSContext(t)()
addrv4 := net.IPv4(192, 168, 1, 1)
inf := setupVerifyTest(t)
config := &networkConfiguration{}
config.AddressIPv4 = &net.IPNet{IP: addrv4, Mask: addrv4.DefaultMask()}
config.EnableIPv6 = true
if err := netlink.AddrAdd(inf.Link, &netlink.Addr{IPNet: bridgeIPv6}); err != nil {
t.Fatalf("Failed to assign IPv6 %s to interface: %v", bridgeIPv6, err)
}
if err := netlink.AddrAdd(inf.Link, &netlink.Addr{IPNet: config.AddressIPv4}); err != nil {
t.Fatalf("Failed to assign IPv4 %s to interface: %v", config.AddressIPv4, err)
}
if err := setupVerifyAndReconcile(config, inf); err != nil {
t.Fatalf("Address verification failed: %v", err)
}
}
func TestSetupVerifyIPv6Missing(t *testing.T) {
defer testutils.SetupTestOSContext(t)()
addrv4 := net.IPv4(192, 168, 1, 1)
inf := setupVerifyTest(t)
config := &networkConfiguration{}
config.AddressIPv4 = &net.IPNet{IP: addrv4, Mask: addrv4.DefaultMask()}
config.EnableIPv6 = true
if err := netlink.AddrAdd(inf.Link, &netlink.Addr{IPNet: config.AddressIPv4}); err != nil {
t.Fatalf("Failed to assign IPv4 %s to interface: %v", config.AddressIPv4, err)
}
if err := setupVerifyAndReconcile(config, inf); err == nil {
t.Fatal("Address verification was expected to fail")
}
}

View File

@@ -0,0 +1,77 @@
package host
import (
"sync"
"github.com/docker/libnetwork/datastore"
"github.com/docker/libnetwork/driverapi"
"github.com/docker/libnetwork/types"
)
const networkType = "host"
type driver struct {
network string
sync.Mutex
}
// Init registers a new instance of host driver
func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
c := driverapi.Capability{
DataScope: datastore.LocalScope,
}
return dc.RegisterDriver(networkType, &driver{}, c)
}
func (d *driver) CreateNetwork(id string, option map[string]interface{}, ipV4Data, ipV6Data []driverapi.IPAMData) error {
d.Lock()
defer d.Unlock()
if d.network != "" {
return types.ForbiddenErrorf("only one instance of \"%s\" network is allowed", networkType)
}
d.network = id
return nil
}
func (d *driver) DeleteNetwork(nid string) error {
return types.ForbiddenErrorf("network of type \"%s\" cannot be deleted", networkType)
}
func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo, epOptions map[string]interface{}) error {
return nil
}
func (d *driver) DeleteEndpoint(nid, eid string) error {
return nil
}
func (d *driver) EndpointOperInfo(nid, eid string) (map[string]interface{}, error) {
return make(map[string]interface{}, 0), nil
}
// Join method is invoked when a Sandbox is attached to an endpoint.
func (d *driver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) error {
return nil
}
// Leave method is invoked when a Sandbox detaches from an endpoint.
func (d *driver) Leave(nid, eid string) error {
return nil
}
func (d *driver) Type() string {
return networkType
}
// DiscoverNew is a notification for a new discovery event, such as a new node joining a cluster
func (d *driver) DiscoverNew(dType driverapi.DiscoveryType, data interface{}) error {
return nil
}
// DiscoverDelete is a notification for a discovery delete event, such as a node leaving a cluster
func (d *driver) DiscoverDelete(dType driverapi.DiscoveryType, data interface{}) error {
return nil
}

View File

@@ -0,0 +1,50 @@
package host
import (
"testing"
_ "github.com/docker/libnetwork/testutils"
"github.com/docker/libnetwork/types"
)
func TestDriver(t *testing.T) {
d := &driver{}
if d.Type() != networkType {
t.Fatalf("Unexpected network type returned by driver")
}
err := d.CreateNetwork("first", nil, nil, nil)
if err != nil {
t.Fatal(err)
}
if d.network != "first" {
t.Fatalf("Unexpected network id stored")
}
err = d.CreateNetwork("second", nil, nil, nil)
if err == nil {
t.Fatalf("Second network creation should fail on this driver")
}
if _, ok := err.(types.ForbiddenError); !ok {
t.Fatalf("Second network creation failed with unexpected error type")
}
err = d.DeleteNetwork("first")
if err == nil {
t.Fatalf("network deletion should fail on this driver")
}
if _, ok := err.(types.ForbiddenError); !ok {
t.Fatalf("network deletion failed with unexpected error type")
}
// we don't really check if it is there or not, delete is not allowed for this driver, period.
err = d.DeleteNetwork("unknown")
if err == nil {
t.Fatalf("any network deletion should fail on this driver")
}
if _, ok := err.(types.ForbiddenError); !ok {
t.Fatalf("any network deletion failed with unexpected error type")
}
}

View File

@@ -0,0 +1,77 @@
package null
import (
"sync"
"github.com/docker/libnetwork/datastore"
"github.com/docker/libnetwork/driverapi"
"github.com/docker/libnetwork/types"
)
const networkType = "null"
type driver struct {
network string
sync.Mutex
}
// Init registers a new instance of null driver
func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
c := driverapi.Capability{
DataScope: datastore.LocalScope,
}
return dc.RegisterDriver(networkType, &driver{}, c)
}
func (d *driver) CreateNetwork(id string, option map[string]interface{}, ipV4Data, ipV6Data []driverapi.IPAMData) error {
d.Lock()
defer d.Unlock()
if d.network != "" {
return types.ForbiddenErrorf("only one instance of \"%s\" network is allowed", networkType)
}
d.network = id
return nil
}
func (d *driver) DeleteNetwork(nid string) error {
return types.ForbiddenErrorf("network of type \"%s\" cannot be deleted", networkType)
}
func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo, epOptions map[string]interface{}) error {
return nil
}
func (d *driver) DeleteEndpoint(nid, eid string) error {
return nil
}
func (d *driver) EndpointOperInfo(nid, eid string) (map[string]interface{}, error) {
return make(map[string]interface{}, 0), nil
}
// Join method is invoked when a Sandbox is attached to an endpoint.
func (d *driver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) error {
return nil
}
// Leave method is invoked when a Sandbox detaches from an endpoint.
func (d *driver) Leave(nid, eid string) error {
return nil
}
func (d *driver) Type() string {
return networkType
}
// DiscoverNew is a notification for a new discovery event, such as a new node joining a cluster
func (d *driver) DiscoverNew(dType driverapi.DiscoveryType, data interface{}) error {
return nil
}
// DiscoverDelete is a notification for a discovery delete event, such as a node leaving a cluster
func (d *driver) DiscoverDelete(dType driverapi.DiscoveryType, data interface{}) error {
return nil
}

View File

@@ -0,0 +1,50 @@
package null
import (
"testing"
_ "github.com/docker/libnetwork/testutils"
"github.com/docker/libnetwork/types"
)
func TestDriver(t *testing.T) {
d := &driver{}
if d.Type() != networkType {
t.Fatalf("Unexpected network type returned by driver")
}
err := d.CreateNetwork("first", nil, nil, nil)
if err != nil {
t.Fatal(err)
}
if d.network != "first" {
t.Fatalf("Unexpected network id stored")
}
err = d.CreateNetwork("second", nil, nil, nil)
if err == nil {
t.Fatalf("Second network creation should fail on this driver")
}
if _, ok := err.(types.ForbiddenError); !ok {
t.Fatalf("Second network creation failed with unexpected error type")
}
err = d.DeleteNetwork("first")
if err == nil {
t.Fatalf("network deletion should fail on this driver")
}
if _, ok := err.(types.ForbiddenError); !ok {
t.Fatalf("network deletion failed with unexpected error type")
}
// we don't really check if it is there or not, delete is not allowed for this driver, period.
err = d.DeleteNetwork("unknown")
if err == nil {
t.Fatalf("any network deletion should fail on this driver")
}
if _, ok := err.(types.ForbiddenError); !ok {
t.Fatalf("any network deletion failed with unexpected error type")
}
}

View File

@@ -0,0 +1,123 @@
package overlay
import (
"fmt"
"sync"
"github.com/Sirupsen/logrus"
"github.com/docker/libnetwork/iptables"
)
const globalChain = "DOCKER-OVERLAY"
var filterOnce sync.Once
func chainExists(cname string) bool {
if _, err := iptables.Raw("-L", cname); err != nil {
return false
}
return true
}
func setupGlobalChain() {
if err := iptables.RawCombinedOutput("-N", globalChain); err != nil {
logrus.Errorf("could not create global overlay chain: %v", err)
return
}
if err := iptables.RawCombinedOutput("-A", globalChain, "-j", "RETURN"); err != nil {
logrus.Errorf("could not install default return chain in the overlay global chain: %v", err)
return
}
}
func setNetworkChain(cname string, remove bool) error {
// Initialize the onetime global overlay chain
filterOnce.Do(setupGlobalChain)
exists := chainExists(cname)
opt := "-N"
// In case of remove, make sure to flush the rules in the chain
if remove && exists {
if err := iptables.RawCombinedOutput("-F", cname); err != nil {
return fmt.Errorf("failed to flush overlay network chain %s rules: %v", cname, err)
}
opt = "-X"
}
if (!remove && !exists) || (remove && exists) {
if err := iptables.RawCombinedOutput(opt, cname); err != nil {
return fmt.Errorf("failed network chain operation %q for chain %s: %v", opt, cname, err)
}
}
if !remove {
if !iptables.Exists(iptables.Filter, cname, "-j", "DROP") {
if err := iptables.RawCombinedOutput("-A", cname, "-j", "DROP"); err != nil {
return fmt.Errorf("failed adding default drop rule to overlay network chain %s: %v", cname, err)
}
}
}
return nil
}
func addNetworkChain(cname string) error {
return setNetworkChain(cname, false)
}
func removeNetworkChain(cname string) error {
return setNetworkChain(cname, true)
}
func setFilters(cname, brName string, remove bool) error {
opt := "-I"
if remove {
opt = "-D"
}
// Everytime we set filters for a new subnet make sure to move the global overlay hook to the top of the both the OUTPUT and forward chains
if !remove {
for _, chain := range []string{"OUTPUT", "FORWARD"} {
exists := iptables.Exists(iptables.Filter, chain, "-j", globalChain)
if exists {
if err := iptables.RawCombinedOutput("-D", chain, "-j", globalChain); err != nil {
return fmt.Errorf("failed to delete overlay hook in chain %s while moving the hook: %v", chain, err)
}
}
if err := iptables.RawCombinedOutput("-I", chain, "-j", globalChain); err != nil {
return fmt.Errorf("failed to insert overlay hook in chain %s: %v", chain, err)
}
}
}
// Insert/Delete the rule to jump to per-bridge chain
exists := iptables.Exists(iptables.Filter, globalChain, "-o", brName, "-j", cname)
if (!remove && !exists) || (remove && exists) {
if err := iptables.RawCombinedOutput(opt, globalChain, "-o", brName, "-j", cname); err != nil {
return fmt.Errorf("failed to add per-bridge filter rule for bridge %s, network chain %s: %v", brName, cname, err)
}
}
exists = iptables.Exists(iptables.Filter, cname, "-i", brName, "-j", "ACCEPT")
if (!remove && exists) || (remove && !exists) {
return nil
}
if err := iptables.RawCombinedOutput(opt, cname, "-i", brName, "-j", "ACCEPT"); err != nil {
return fmt.Errorf("failed to add overlay filter rile for network chain %s, bridge %s: %v", cname, brName, err)
}
return nil
}
func addFilters(cname, brName string) error {
return setFilters(cname, brName, false)
}
func removeFilters(cname, brName string) error {
return setFilters(cname, brName, true)
}

View File

@@ -0,0 +1,149 @@
package overlay
import (
"fmt"
"net"
log "github.com/Sirupsen/logrus"
"github.com/docker/libnetwork/driverapi"
"github.com/docker/libnetwork/types"
"github.com/vishvananda/netlink"
)
// Join method is invoked when a Sandbox is attached to an endpoint.
func (d *driver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) error {
if err := validateID(nid, eid); err != nil {
return err
}
n := d.network(nid)
if n == nil {
return fmt.Errorf("could not find network with id %s", nid)
}
ep := n.endpoint(eid)
if ep == nil {
return fmt.Errorf("could not find endpoint with id %s", eid)
}
s := n.getSubnetforIP(ep.addr)
if s == nil {
return fmt.Errorf("could not find subnet for endpoint %s", eid)
}
if err := n.obtainVxlanID(s); err != nil {
return fmt.Errorf("couldn't get vxlan id for %q: %v", s.subnetIP.String(), err)
}
if err := n.joinSandbox(); err != nil {
return fmt.Errorf("network sandbox join failed: %v", err)
}
if err := n.joinSubnetSandbox(s); err != nil {
return fmt.Errorf("subnet sandbox join failed for %q: %v", s.subnetIP.String(), err)
}
// joinSubnetSandbox gets called when an endpoint comes up on a new subnet in the
// overlay network. Hence the Endpoint count should be updated outside joinSubnetSandbox
n.incEndpointCount()
sbox := n.sandbox()
name1, name2, err := createVethPair()
if err != nil {
return err
}
ep.ifName = name2
// Set the container interface and its peer MTU to 1450 to allow
// for 50 bytes vxlan encap (inner eth header(14) + outer IP(20) +
// outer UDP(8) + vxlan header(8))
veth, err := netlink.LinkByName(name1)
if err != nil {
return fmt.Errorf("cound not find link by name %s: %v", name1, err)
}
err = netlink.LinkSetMTU(veth, vxlanVethMTU)
if err != nil {
return err
}
if err := sbox.AddInterface(name1, "veth",
sbox.InterfaceOptions().Master(s.brName)); err != nil {
return fmt.Errorf("could not add veth pair inside the network sandbox: %v", err)
}
veth, err = netlink.LinkByName(name2)
if err != nil {
return fmt.Errorf("could not find link by name %s: %v", name2, err)
}
err = netlink.LinkSetMTU(veth, vxlanVethMTU)
if err != nil {
return err
}
if err := netlink.LinkSetHardwareAddr(veth, ep.mac); err != nil {
return fmt.Errorf("could not set mac address (%v) to the container interface: %v", ep.mac, err)
}
for _, sub := range n.subnets {
if sub == s {
continue
}
if err := jinfo.AddStaticRoute(sub.subnetIP, types.NEXTHOP, s.gwIP.IP); err != nil {
log.Errorf("Adding subnet %s static route in network %q failed\n", s.subnetIP, n.id)
}
}
if iNames := jinfo.InterfaceName(); iNames != nil {
err = iNames.SetNames(name2, "eth")
if err != nil {
return err
}
}
d.peerDbAdd(nid, eid, ep.addr.IP, ep.addr.Mask, ep.mac,
net.ParseIP(d.bindAddress), true)
d.pushLocalEndpointEvent("join", nid, eid)
return nil
}
// Leave method is invoked when a Sandbox detaches from an endpoint.
func (d *driver) Leave(nid, eid string) error {
if err := validateID(nid, eid); err != nil {
return err
}
n := d.network(nid)
if n == nil {
return fmt.Errorf("could not find network with id %s", nid)
}
ep := n.endpoint(eid)
if ep == nil {
return types.InternalMaskableErrorf("could not find endpoint with id %s", eid)
}
if d.notifyCh != nil {
d.notifyCh <- ovNotify{
action: "leave",
nid: nid,
eid: eid,
}
}
n.leaveSandbox()
link, err := netlink.LinkByName(ep.ifName)
if err != nil {
log.Warnf("Failed to retrieve interface link for interface removal on endpoint leave: %v", err)
return nil
}
if err := netlink.LinkDel(link); err != nil {
log.Warnf("Failed to delete interface link on endpoint leave: %v", err)
}
return nil
}

View File

@@ -0,0 +1,105 @@
package overlay
import (
"fmt"
"net"
"github.com/docker/libnetwork/driverapi"
"github.com/docker/libnetwork/netutils"
)
type endpointTable map[string]*endpoint
type endpoint struct {
id string
ifName string
mac net.HardwareAddr
addr *net.IPNet
}
func (n *network) endpoint(eid string) *endpoint {
n.Lock()
defer n.Unlock()
return n.endpoints[eid]
}
func (n *network) addEndpoint(ep *endpoint) {
n.Lock()
n.endpoints[ep.id] = ep
n.Unlock()
}
func (n *network) deleteEndpoint(eid string) {
n.Lock()
delete(n.endpoints, eid)
n.Unlock()
}
func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo,
epOptions map[string]interface{}) error {
var err error
if err = validateID(nid, eid); err != nil {
return err
}
// Since we perform lazy configuration make sure we try
// configuring the driver when we enter CreateEndpoint since
// CreateNetwork may not be called in every node.
if err := d.configure(); err != nil {
return err
}
n := d.network(nid)
if n == nil {
return fmt.Errorf("network id %q not found", nid)
}
ep := &endpoint{
id: eid,
addr: ifInfo.Address(),
mac: ifInfo.MacAddress(),
}
if ep.addr == nil {
return fmt.Errorf("create endpoint was not passed interface IP address")
}
if s := n.getSubnetforIP(ep.addr); s == nil {
return fmt.Errorf("no matching subnet for IP %q in network %q\n", ep.addr, nid)
}
if ep.mac == nil {
ep.mac = netutils.GenerateMACFromIP(ep.addr.IP)
if err := ifInfo.SetMacAddress(ep.mac); err != nil {
return err
}
}
n.addEndpoint(ep)
return nil
}
func (d *driver) DeleteEndpoint(nid, eid string) error {
if err := validateID(nid, eid); err != nil {
return err
}
n := d.network(nid)
if n == nil {
return fmt.Errorf("network id %q not found", nid)
}
ep := n.endpoint(eid)
if ep == nil {
return fmt.Errorf("endpoint id %q not found", eid)
}
n.deleteEndpoint(eid)
return nil
}
func (d *driver) EndpointOperInfo(nid, eid string) (map[string]interface{}, error) {
return make(map[string]interface{}, 0), nil
}

View File

@@ -0,0 +1,633 @@
package overlay
import (
"encoding/json"
"fmt"
"net"
"os"
"sync"
"syscall"
"github.com/Sirupsen/logrus"
"github.com/docker/libnetwork/datastore"
"github.com/docker/libnetwork/driverapi"
"github.com/docker/libnetwork/netutils"
"github.com/docker/libnetwork/osl"
"github.com/docker/libnetwork/resolvconf"
"github.com/docker/libnetwork/types"
"github.com/vishvananda/netlink"
"github.com/vishvananda/netlink/nl"
)
var (
hostMode bool
hostModeOnce sync.Once
)
type networkTable map[string]*network
type subnet struct {
once *sync.Once
vxlanName string
brName string
vni uint32
initErr error
subnetIP *net.IPNet
gwIP *net.IPNet
}
type subnetJSON struct {
SubnetIP string
GwIP string
Vni uint32
}
type network struct {
id string
dbIndex uint64
dbExists bool
sbox osl.Sandbox
endpoints endpointTable
driver *driver
joinCnt int
once *sync.Once
initEpoch int
initErr error
subnets []*subnet
sync.Mutex
}
func (d *driver) CreateNetwork(id string, option map[string]interface{}, ipV4Data, ipV6Data []driverapi.IPAMData) error {
if id == "" {
return fmt.Errorf("invalid network id")
}
// Since we perform lazy configuration make sure we try
// configuring the driver when we enter CreateNetwork
if err := d.configure(); err != nil {
return err
}
n := &network{
id: id,
driver: d,
endpoints: endpointTable{},
once: &sync.Once{},
subnets: []*subnet{},
}
for _, ipd := range ipV4Data {
s := &subnet{
subnetIP: ipd.Pool,
gwIP: ipd.Gateway,
once: &sync.Once{},
}
n.subnets = append(n.subnets, s)
}
if err := n.writeToStore(); err != nil {
return fmt.Errorf("failed to update data store for network %v: %v", n.id, err)
}
d.addNetwork(n)
return nil
}
func (d *driver) DeleteNetwork(nid string) error {
if nid == "" {
return fmt.Errorf("invalid network id")
}
n := d.network(nid)
if n == nil {
return fmt.Errorf("could not find network with id %s", nid)
}
d.deleteNetwork(nid)
return n.releaseVxlanID()
}
func (n *network) incEndpointCount() {
n.Lock()
defer n.Unlock()
n.joinCnt++
}
func (n *network) joinSandbox() error {
// If there is a race between two go routines here only one will win
// the other will wait.
n.once.Do(func() {
// save the error status of initSandbox in n.initErr so that
// all the racing go routines are able to know the status.
n.initErr = n.initSandbox()
})
return n.initErr
}
func (n *network) joinSubnetSandbox(s *subnet) error {
s.once.Do(func() {
s.initErr = n.initSubnetSandbox(s)
})
return s.initErr
}
func (n *network) leaveSandbox() {
n.Lock()
n.joinCnt--
if n.joinCnt != 0 {
n.Unlock()
return
}
// We are about to destroy sandbox since the container is leaving the network
// Reinitialize the once variable so that we will be able to trigger one time
// sandbox initialization(again) when another container joins subsequently.
n.once = &sync.Once{}
for _, s := range n.subnets {
s.once = &sync.Once{}
}
n.Unlock()
n.destroySandbox()
}
func (n *network) destroySandbox() {
sbox := n.sandbox()
if sbox != nil {
for _, iface := range sbox.Info().Interfaces() {
iface.Remove()
}
for _, s := range n.subnets {
if hostMode {
if err := removeFilters(n.id[:12], s.brName); err != nil {
logrus.Warnf("Could not remove overlay filters: %v", err)
}
}
if s.vxlanName != "" {
err := deleteInterface(s.vxlanName)
if err != nil {
logrus.Warnf("could not cleanup sandbox properly: %v", err)
}
}
}
if hostMode {
if err := removeNetworkChain(n.id[:12]); err != nil {
logrus.Warnf("could not remove network chain: %v", err)
}
}
sbox.Destroy()
n.setSandbox(nil)
}
}
func setHostMode() {
if os.Getenv("_OVERLAY_HOST_MODE") != "" {
hostMode = true
return
}
err := createVxlan("testvxlan", 1)
if err != nil {
logrus.Errorf("Failed to create testvxlan interface: %v", err)
return
}
defer deleteInterface("testvxlan")
path := "/proc/self/ns/net"
f, err := os.OpenFile(path, os.O_RDONLY, 0)
if err != nil {
logrus.Errorf("Failed to open path %s for network namespace for setting host mode: %v", path, err)
return
}
defer f.Close()
nsFD := f.Fd()
iface, err := netlink.LinkByName("testvxlan")
if err != nil {
logrus.Errorf("Failed to get link testvxlan: %v", err)
return
}
// If we are not able to move the vxlan interface to a namespace
// then fallback to host mode
if err := netlink.LinkSetNsFd(iface, int(nsFD)); err != nil {
hostMode = true
}
}
func (n *network) generateVxlanName(s *subnet) string {
return "vx-" + fmt.Sprintf("%06x", n.vxlanID(s)) + "-" + n.id[:5]
}
func (n *network) generateBridgeName(s *subnet) string {
return "ov-" + fmt.Sprintf("%06x", n.vxlanID(s)) + "-" + n.id[:5]
}
func isOverlap(nw *net.IPNet) bool {
var nameservers []string
if rc, err := resolvconf.Get(); err == nil {
nameservers = resolvconf.GetNameserversAsCIDR(rc.Content)
}
if err := netutils.CheckNameserverOverlaps(nameservers, nw); err != nil {
return true
}
if err := netutils.CheckRouteOverlaps(nw); err != nil {
return true
}
return false
}
func (n *network) initSubnetSandbox(s *subnet) error {
brName := n.generateBridgeName(s)
vxlanName := n.generateVxlanName(s)
if hostMode {
// Try to delete stale bridge interface if it exists
deleteInterface(brName)
// Try to delete the vxlan interface by vni if already present
deleteVxlanByVNI(n.vxlanID(s))
if isOverlap(s.subnetIP) {
return fmt.Errorf("overlay subnet %s has conflicts in the host while running in host mode", s.subnetIP.String())
}
}
// create a bridge and vxlan device for this subnet and move it to the sandbox
sbox := n.sandbox()
if err := sbox.AddInterface(brName, "br",
sbox.InterfaceOptions().Address(s.gwIP),
sbox.InterfaceOptions().Bridge(true)); err != nil {
return fmt.Errorf("bridge creation in sandbox failed for subnet %q: %v", s.subnetIP.String(), err)
}
err := createVxlan(vxlanName, n.vxlanID(s))
if err != nil {
return err
}
if err := sbox.AddInterface(vxlanName, "vxlan",
sbox.InterfaceOptions().Master(brName)); err != nil {
return fmt.Errorf("vxlan interface creation failed for subnet %q: %v", s.subnetIP.String(), err)
}
if hostMode {
if err := addFilters(n.id[:12], brName); err != nil {
return err
}
}
n.Lock()
s.vxlanName = vxlanName
s.brName = brName
n.Unlock()
return nil
}
func (n *network) initSandbox() error {
n.Lock()
n.initEpoch++
n.Unlock()
hostModeOnce.Do(setHostMode)
if hostMode {
if err := addNetworkChain(n.id[:12]); err != nil {
return err
}
}
sbox, err := osl.NewSandbox(
osl.GenerateKey(fmt.Sprintf("%d-", n.initEpoch)+n.id), !hostMode)
if err != nil {
return fmt.Errorf("could not create network sandbox: %v", err)
}
n.setSandbox(sbox)
n.driver.peerDbUpdateSandbox(n.id)
var nlSock *nl.NetlinkSocket
sbox.InvokeFunc(func() {
nlSock, err = nl.Subscribe(syscall.NETLINK_ROUTE, syscall.RTNLGRP_NEIGH)
if err != nil {
err = fmt.Errorf("failed to subscribe to neighbor group netlink messages")
}
})
go n.watchMiss(nlSock)
return nil
}
func (n *network) watchMiss(nlSock *nl.NetlinkSocket) {
for {
msgs, err := nlSock.Receive()
if err != nil {
logrus.Errorf("Failed to receive from netlink: %v ", err)
continue
}
for _, msg := range msgs {
if msg.Header.Type != syscall.RTM_GETNEIGH && msg.Header.Type != syscall.RTM_NEWNEIGH {
continue
}
neigh, err := netlink.NeighDeserialize(msg.Data)
if err != nil {
logrus.Errorf("Failed to deserialize netlink ndmsg: %v", err)
continue
}
if neigh.IP.To16() != nil {
continue
}
if neigh.State&(netlink.NUD_STALE|netlink.NUD_INCOMPLETE) == 0 {
continue
}
mac, IPmask, vtep, err := n.driver.resolvePeer(n.id, neigh.IP)
if err != nil {
logrus.Errorf("could not resolve peer %q: %v", neigh.IP, err)
continue
}
if err := n.driver.peerAdd(n.id, "dummy", neigh.IP, IPmask, mac, vtep, true); err != nil {
logrus.Errorf("could not add neighbor entry for missed peer %q: %v", neigh.IP, err)
}
}
}
}
func (d *driver) addNetwork(n *network) {
d.Lock()
d.networks[n.id] = n
d.Unlock()
}
func (d *driver) deleteNetwork(nid string) {
d.Lock()
delete(d.networks, nid)
d.Unlock()
}
func (d *driver) network(nid string) *network {
d.Lock()
networks := d.networks
d.Unlock()
n, ok := networks[nid]
if !ok {
n = d.getNetworkFromStore(nid)
if n != nil {
n.driver = d
n.endpoints = endpointTable{}
n.once = &sync.Once{}
networks[nid] = n
}
}
return n
}
func (d *driver) getNetworkFromStore(nid string) *network {
if d.store == nil {
return nil
}
n := &network{id: nid}
if err := d.store.GetObject(datastore.Key(n.Key()...), n); err != nil {
return nil
}
return n
}
func (n *network) sandbox() osl.Sandbox {
n.Lock()
defer n.Unlock()
return n.sbox
}
func (n *network) setSandbox(sbox osl.Sandbox) {
n.Lock()
n.sbox = sbox
n.Unlock()
}
func (n *network) vxlanID(s *subnet) uint32 {
n.Lock()
defer n.Unlock()
return s.vni
}
func (n *network) setVxlanID(s *subnet, vni uint32) {
n.Lock()
s.vni = vni
n.Unlock()
}
func (n *network) Key() []string {
return []string{"overlay", "network", n.id}
}
func (n *network) KeyPrefix() []string {
return []string{"overlay", "network"}
}
func (n *network) Value() []byte {
netJSON := []*subnetJSON{}
for _, s := range n.subnets {
sj := &subnetJSON{
SubnetIP: s.subnetIP.String(),
GwIP: s.gwIP.String(),
Vni: s.vni,
}
netJSON = append(netJSON, sj)
}
b, err := json.Marshal(netJSON)
if err != nil {
return []byte{}
}
return b
}
func (n *network) Index() uint64 {
return n.dbIndex
}
func (n *network) SetIndex(index uint64) {
n.dbIndex = index
n.dbExists = true
}
func (n *network) Exists() bool {
return n.dbExists
}
func (n *network) Skip() bool {
return false
}
func (n *network) SetValue(value []byte) error {
var newNet bool
netJSON := []*subnetJSON{}
err := json.Unmarshal(value, &netJSON)
if err != nil {
return err
}
if len(n.subnets) == 0 {
newNet = true
}
for _, sj := range netJSON {
subnetIPstr := sj.SubnetIP
gwIPstr := sj.GwIP
vni := sj.Vni
subnetIP, _ := types.ParseCIDR(subnetIPstr)
gwIP, _ := types.ParseCIDR(gwIPstr)
if newNet {
s := &subnet{
subnetIP: subnetIP,
gwIP: gwIP,
vni: vni,
once: &sync.Once{},
}
n.subnets = append(n.subnets, s)
} else {
sNet := n.getMatchingSubnet(subnetIP)
if sNet != nil {
sNet.vni = vni
}
}
}
return nil
}
func (n *network) DataScope() string {
return datastore.GlobalScope
}
func (n *network) writeToStore() error {
return n.driver.store.PutObjectAtomic(n)
}
func (n *network) releaseVxlanID() error {
if n.driver.store == nil {
return fmt.Errorf("no datastore configured. cannot release vxlan id")
}
if len(n.subnets) == 0 {
return nil
}
if err := n.driver.store.DeleteObjectAtomic(n); err != nil {
if err == datastore.ErrKeyModified || err == datastore.ErrKeyNotFound {
// In both the above cases we can safely assume that the key has been removed by some other
// instance and so simply get out of here
return nil
}
return fmt.Errorf("failed to delete network to vxlan id map: %v", err)
}
for _, s := range n.subnets {
n.driver.vxlanIdm.Release(uint64(n.vxlanID(s)))
n.setVxlanID(s, 0)
}
return nil
}
func (n *network) obtainVxlanID(s *subnet) error {
//return if the subnet already has a vxlan id assigned
if s.vni != 0 {
return nil
}
if n.driver.store == nil {
return fmt.Errorf("no datastore configured. cannot obtain vxlan id")
}
for {
if err := n.driver.store.GetObject(datastore.Key(n.Key()...), n); err != nil {
return fmt.Errorf("getting network %q from datastore failed %v", n.id, err)
}
if s.vni == 0 {
vxlanID, err := n.driver.vxlanIdm.GetID()
if err != nil {
return fmt.Errorf("failed to allocate vxlan id: %v", err)
}
n.setVxlanID(s, uint32(vxlanID))
if err := n.writeToStore(); err != nil {
n.driver.vxlanIdm.Release(uint64(n.vxlanID(s)))
n.setVxlanID(s, 0)
if err == datastore.ErrKeyModified {
continue
}
return fmt.Errorf("network %q failed to update data store: %v", n.id, err)
}
return nil
}
return nil
}
}
// getSubnetforIP returns the subnet to which the given IP belongs
func (n *network) getSubnetforIP(ip *net.IPNet) *subnet {
for _, s := range n.subnets {
// first check if the mask lengths are the same
i, _ := s.subnetIP.Mask.Size()
j, _ := ip.Mask.Size()
if i != j {
continue
}
if s.subnetIP.Contains(ip.IP) {
return s
}
}
return nil
}
// getMatchingSubnet return the network's subnet that matches the input
func (n *network) getMatchingSubnet(ip *net.IPNet) *subnet {
if ip == nil {
return nil
}
for _, s := range n.subnets {
// first check if the mask lengths are the same
i, _ := s.subnetIP.Mask.Size()
j, _ := ip.Mask.Size()
if i != j {
continue
}
if s.subnetIP.IP.Equal(ip.IP) {
return s
}
}
return nil
}

View File

@@ -0,0 +1,234 @@
package overlay
import (
"fmt"
"net"
"strings"
"time"
"github.com/Sirupsen/logrus"
"github.com/hashicorp/serf/serf"
)
type ovNotify struct {
action string
eid string
nid string
}
type logWriter struct{}
func (l *logWriter) Write(p []byte) (int, error) {
str := string(p)
switch {
case strings.Contains(str, "[WARN]"):
logrus.Warn(str)
case strings.Contains(str, "[DEBUG]"):
logrus.Debug(str)
case strings.Contains(str, "[INFO]"):
logrus.Info(str)
case strings.Contains(str, "[ERR]"):
logrus.Error(str)
}
return len(p), nil
}
func (d *driver) serfInit() error {
var err error
config := serf.DefaultConfig()
config.Init()
config.MemberlistConfig.BindAddr = d.bindAddress
d.eventCh = make(chan serf.Event, 4)
config.EventCh = d.eventCh
config.UserCoalescePeriod = 1 * time.Second
config.UserQuiescentPeriod = 50 * time.Millisecond
config.LogOutput = &logWriter{}
config.MemberlistConfig.LogOutput = config.LogOutput
s, err := serf.Create(config)
if err != nil {
return fmt.Errorf("failed to create cluster node: %v", err)
}
defer func() {
if err != nil {
s.Shutdown()
}
}()
d.serfInstance = s
d.notifyCh = make(chan ovNotify)
d.exitCh = make(chan chan struct{})
go d.startSerfLoop(d.eventCh, d.notifyCh, d.exitCh)
return nil
}
func (d *driver) serfJoin(neighIP string) error {
if neighIP == "" {
return fmt.Errorf("no neighbor to join")
}
if _, err := d.serfInstance.Join([]string{neighIP}, false); err != nil {
return fmt.Errorf("Failed to join the cluster at neigh IP %s: %v",
neighIP, err)
}
return nil
}
func (d *driver) notifyEvent(event ovNotify) {
n := d.network(event.nid)
ep := n.endpoint(event.eid)
ePayload := fmt.Sprintf("%s %s %s %s", event.action, ep.addr.IP.String(),
net.IP(ep.addr.Mask).String(), ep.mac.String())
eName := fmt.Sprintf("jl %s %s %s", d.serfInstance.LocalMember().Addr.String(),
event.nid, event.eid)
if err := d.serfInstance.UserEvent(eName, []byte(ePayload), true); err != nil {
logrus.Errorf("Sending user event failed: %v\n", err)
}
}
func (d *driver) processEvent(u serf.UserEvent) {
logrus.Debugf("Received user event name:%s, payload:%s\n", u.Name,
string(u.Payload))
var dummy, action, vtepStr, nid, eid, ipStr, maskStr, macStr string
if _, err := fmt.Sscan(u.Name, &dummy, &vtepStr, &nid, &eid); err != nil {
fmt.Printf("Failed to scan name string: %v\n", err)
}
if _, err := fmt.Sscan(string(u.Payload), &action,
&ipStr, &maskStr, &macStr); err != nil {
fmt.Printf("Failed to scan value string: %v\n", err)
}
logrus.Debugf("Parsed data = %s/%s/%s/%s/%s/%s\n", nid, eid, vtepStr, ipStr, maskStr, macStr)
mac, err := net.ParseMAC(macStr)
if err != nil {
logrus.Errorf("Failed to parse mac: %v\n", err)
}
if d.serfInstance.LocalMember().Addr.String() == vtepStr {
return
}
switch action {
case "join":
if err := d.peerAdd(nid, eid, net.ParseIP(ipStr), net.IPMask(net.ParseIP(maskStr).To4()), mac,
net.ParseIP(vtepStr), true); err != nil {
logrus.Errorf("Peer add failed in the driver: %v\n", err)
}
case "leave":
if err := d.peerDelete(nid, eid, net.ParseIP(ipStr), net.IPMask(net.ParseIP(maskStr).To4()), mac,
net.ParseIP(vtepStr), true); err != nil {
logrus.Errorf("Peer delete failed in the driver: %v\n", err)
}
}
}
func (d *driver) processQuery(q *serf.Query) {
logrus.Debugf("Received query name:%s, payload:%s\n", q.Name,
string(q.Payload))
var nid, ipStr string
if _, err := fmt.Sscan(string(q.Payload), &nid, &ipStr); err != nil {
fmt.Printf("Failed to scan query payload string: %v\n", err)
}
peerMac, peerIPMask, vtep, err := d.peerDbSearch(nid, net.ParseIP(ipStr))
if err != nil {
return
}
q.Respond([]byte(fmt.Sprintf("%s %s %s", peerMac.String(), net.IP(peerIPMask).String(), vtep.String())))
}
func (d *driver) resolvePeer(nid string, peerIP net.IP) (net.HardwareAddr, net.IPMask, net.IP, error) {
if d.serfInstance == nil {
return nil, nil, nil, fmt.Errorf("could not resolve peer: serf instance not initialized")
}
qPayload := fmt.Sprintf("%s %s", string(nid), peerIP.String())
resp, err := d.serfInstance.Query("peerlookup", []byte(qPayload), nil)
if err != nil {
return nil, nil, nil, fmt.Errorf("resolving peer by querying the cluster failed: %v", err)
}
respCh := resp.ResponseCh()
select {
case r := <-respCh:
var macStr, maskStr, vtepStr string
if _, err := fmt.Sscan(string(r.Payload), &macStr, &maskStr, &vtepStr); err != nil {
return nil, nil, nil, fmt.Errorf("bad response %q for the resolve query: %v", string(r.Payload), err)
}
mac, err := net.ParseMAC(macStr)
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to parse mac: %v", err)
}
return mac, net.IPMask(net.ParseIP(maskStr).To4()), net.ParseIP(vtepStr), nil
case <-time.After(time.Second):
return nil, nil, nil, fmt.Errorf("timed out resolving peer by querying the cluster")
}
}
func (d *driver) startSerfLoop(eventCh chan serf.Event, notifyCh chan ovNotify,
exitCh chan chan struct{}) {
for {
select {
case notify, ok := <-notifyCh:
if !ok {
break
}
d.notifyEvent(notify)
case ch, ok := <-exitCh:
if !ok {
break
}
if err := d.serfInstance.Leave(); err != nil {
logrus.Errorf("failed leaving the cluster: %v\n", err)
}
d.serfInstance.Shutdown()
close(ch)
return
case e, ok := <-eventCh:
if !ok {
break
}
if e.EventType() == serf.EventQuery {
d.processQuery(e.(*serf.Query))
break
}
u, ok := e.(serf.UserEvent)
if !ok {
break
}
d.processEvent(u)
}
}
}
func (d *driver) isSerfAlive() bool {
d.Lock()
serfInstance := d.serfInstance
d.Unlock()
if serfInstance == nil || serfInstance.State() != serf.SerfAlive {
return false
}
return true
}

View File

@@ -0,0 +1,105 @@
package overlay
import (
"fmt"
"github.com/docker/libnetwork/netutils"
"github.com/docker/libnetwork/osl"
"github.com/vishvananda/netlink"
"github.com/vishvananda/netlink/nl"
)
func validateID(nid, eid string) error {
if nid == "" {
return fmt.Errorf("invalid network id")
}
if eid == "" {
return fmt.Errorf("invalid endpoint id")
}
return nil
}
func createVethPair() (string, string, error) {
defer osl.InitOSContext()()
// Generate a name for what will be the host side pipe interface
name1, err := netutils.GenerateIfaceName(vethPrefix, vethLen)
if err != nil {
return "", "", fmt.Errorf("error generating veth name1: %v", err)
}
// Generate a name for what will be the sandbox side pipe interface
name2, err := netutils.GenerateIfaceName(vethPrefix, vethLen)
if err != nil {
return "", "", fmt.Errorf("error generating veth name2: %v", err)
}
// Generate and add the interface pipe host <-> sandbox
veth := &netlink.Veth{
LinkAttrs: netlink.LinkAttrs{Name: name1, TxQLen: 0},
PeerName: name2}
if err := netlink.LinkAdd(veth); err != nil {
return "", "", fmt.Errorf("error creating veth pair: %v", err)
}
return name1, name2, nil
}
func createVxlan(name string, vni uint32) error {
defer osl.InitOSContext()()
vxlan := &netlink.Vxlan{
LinkAttrs: netlink.LinkAttrs{Name: name},
VxlanId: int(vni),
Learning: true,
Port: int(nl.Swap16(vxlanPort)), //network endian order
Proxy: true,
L3miss: true,
L2miss: true,
}
if err := netlink.LinkAdd(vxlan); err != nil {
return fmt.Errorf("error creating vxlan interface: %v", err)
}
return nil
}
func deleteInterface(name string) error {
defer osl.InitOSContext()()
link, err := netlink.LinkByName(name)
if err != nil {
return fmt.Errorf("failed to find interface with name %s: %v", name, err)
}
if err := netlink.LinkDel(link); err != nil {
return fmt.Errorf("error deleting interface with name %s: %v", name, err)
}
return nil
}
func deleteVxlanByVNI(vni uint32) error {
defer osl.InitOSContext()()
links, err := netlink.LinkList()
if err != nil {
return fmt.Errorf("failed to list interfaces while deleting vxlan interface by vni: %v", err)
}
for _, l := range links {
if l.Type() == "vxlan" && l.(*netlink.Vxlan).VxlanId == int(vni) {
err = netlink.LinkDel(l)
if err != nil {
return fmt.Errorf("error deleting vxlan interface with id %d: %v", vni, err)
}
return nil
}
}
return fmt.Errorf("could not find a vxlan interface to delete with id %d", vni)
}

View File

@@ -0,0 +1,209 @@
package overlay
import (
"fmt"
"net"
"sync"
"github.com/Sirupsen/logrus"
"github.com/docker/libkv/store"
"github.com/docker/libnetwork/datastore"
"github.com/docker/libnetwork/driverapi"
"github.com/docker/libnetwork/idm"
"github.com/docker/libnetwork/netlabel"
"github.com/hashicorp/serf/serf"
)
const (
networkType = "overlay"
vethPrefix = "veth"
vethLen = 7
vxlanIDStart = 256
vxlanIDEnd = 1000
vxlanPort = 4789
vxlanVethMTU = 1450
)
type driver struct {
eventCh chan serf.Event
notifyCh chan ovNotify
exitCh chan chan struct{}
bindAddress string
neighIP string
config map[string]interface{}
peerDb peerNetworkMap
serfInstance *serf.Serf
networks networkTable
store datastore.DataStore
ipAllocator *idm.Idm
vxlanIdm *idm.Idm
once sync.Once
joinOnce sync.Once
sync.Mutex
}
// Init registers a new instance of overlay driver
func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
c := driverapi.Capability{
DataScope: datastore.GlobalScope,
}
d := &driver{
networks: networkTable{},
peerDb: peerNetworkMap{
mp: map[string]*peerMap{},
},
config: config,
}
return dc.RegisterDriver(networkType, d, c)
}
// Fini cleans up the driver resources
func Fini(drv driverapi.Driver) {
d := drv.(*driver)
if d.exitCh != nil {
waitCh := make(chan struct{})
d.exitCh <- waitCh
<-waitCh
}
}
func (d *driver) configure() error {
var err error
if len(d.config) == 0 {
return nil
}
d.once.Do(func() {
provider, provOk := d.config[netlabel.GlobalKVProvider]
provURL, urlOk := d.config[netlabel.GlobalKVProviderURL]
if provOk && urlOk {
cfg := &datastore.ScopeCfg{
Client: datastore.ScopeClientCfg{
Provider: provider.(string),
Address: provURL.(string),
},
}
provConfig, confOk := d.config[netlabel.GlobalKVProviderConfig]
if confOk {
cfg.Client.Config = provConfig.(*store.Config)
}
d.store, err = datastore.NewDataStore(datastore.GlobalScope, cfg)
if err != nil {
err = fmt.Errorf("failed to initialize data store: %v", err)
return
}
}
d.vxlanIdm, err = idm.New(d.store, "vxlan-id", vxlanIDStart, vxlanIDEnd)
if err != nil {
err = fmt.Errorf("failed to initialize vxlan id manager: %v", err)
return
}
d.ipAllocator, err = idm.New(d.store, "ipam-id", 1, 0xFFFF-2)
if err != nil {
err = fmt.Errorf("failed to initalize ipam id manager: %v", err)
return
}
})
return err
}
func (d *driver) Type() string {
return networkType
}
func validateSelf(node string) error {
advIP := net.ParseIP(node)
if advIP == nil {
return fmt.Errorf("invalid self address (%s)", node)
}
addrs, err := net.InterfaceAddrs()
if err != nil {
return fmt.Errorf("Unable to get interface addresses %v", err)
}
for _, addr := range addrs {
ip, _, err := net.ParseCIDR(addr.String())
if err == nil && ip.Equal(advIP) {
return nil
}
}
return fmt.Errorf("Multi-Host overlay networking requires cluster-advertise(%s) to be configured with a local ip-address that is reachable within the cluster", advIP.String())
}
func (d *driver) nodeJoin(node string, self bool) {
if self && !d.isSerfAlive() {
if err := validateSelf(node); err != nil {
logrus.Errorf("%s", err.Error())
}
d.Lock()
d.bindAddress = node
d.Unlock()
err := d.serfInit()
if err != nil {
logrus.Errorf("initializing serf instance failed: %v", err)
return
}
}
d.Lock()
if !self {
d.neighIP = node
}
neighIP := d.neighIP
d.Unlock()
if d.serfInstance != nil && neighIP != "" {
var err error
d.joinOnce.Do(func() {
err = d.serfJoin(neighIP)
if err == nil {
d.pushLocalDb()
}
})
if err != nil {
logrus.Errorf("joining serf neighbor %s failed: %v", node, err)
d.Lock()
d.joinOnce = sync.Once{}
d.Unlock()
return
}
}
}
func (d *driver) pushLocalEndpointEvent(action, nid, eid string) {
if !d.isSerfAlive() {
return
}
d.notifyCh <- ovNotify{
action: "join",
nid: nid,
eid: eid,
}
}
// DiscoverNew is a notification for a new discovery event, such as a new node joining a cluster
func (d *driver) DiscoverNew(dType driverapi.DiscoveryType, data interface{}) error {
if dType == driverapi.NodeDiscovery {
nodeData, ok := data.(driverapi.NodeDiscoveryData)
if !ok || nodeData.Address == "" {
return fmt.Errorf("invalid discovery data")
}
d.nodeJoin(nodeData.Address, nodeData.Self)
}
return nil
}
// DiscoverDelete is a notification for a discovery delete event, such as a node leaving a cluster
func (d *driver) DiscoverDelete(dType driverapi.DiscoveryType, data interface{}) error {
return nil
}

View File

@@ -0,0 +1,134 @@
package overlay
import (
"net"
"testing"
"time"
"github.com/docker/libnetwork/driverapi"
_ "github.com/docker/libnetwork/testutils"
)
type driverTester struct {
t *testing.T
d *driver
}
const testNetworkType = "overlay"
func setupDriver(t *testing.T) *driverTester {
dt := &driverTester{t: t}
if err := Init(dt, nil); err != nil {
t.Fatal(err)
}
if err := dt.d.configure(); err != nil {
t.Fatal(err)
}
iface, err := net.InterfaceByName("eth0")
if err != nil {
t.Fatal(err)
}
addrs, err := iface.Addrs()
if err != nil || len(addrs) == 0 {
t.Fatal(err)
}
data := driverapi.NodeDiscoveryData{
Address: addrs[0].String(),
Self: true,
}
dt.d.DiscoverNew(driverapi.NodeDiscovery, data)
return dt
}
func cleanupDriver(t *testing.T, dt *driverTester) {
ch := make(chan struct{})
go func() {
Fini(dt.d)
close(ch)
}()
select {
case <-ch:
case <-time.After(10 * time.Second):
t.Fatal("test timed out because Fini() did not return on time")
}
}
func (dt *driverTester) RegisterDriver(name string, drv driverapi.Driver,
cap driverapi.Capability) error {
if name != testNetworkType {
dt.t.Fatalf("Expected driver register name to be %q. Instead got %q",
testNetworkType, name)
}
if _, ok := drv.(*driver); !ok {
dt.t.Fatalf("Expected driver type to be %T. Instead got %T",
&driver{}, drv)
}
dt.d = drv.(*driver)
return nil
}
func TestOverlayInit(t *testing.T) {
if err := Init(&driverTester{t: t}, nil); err != nil {
t.Fatal(err)
}
}
func TestOverlayFiniWithoutConfig(t *testing.T) {
dt := &driverTester{t: t}
if err := Init(dt, nil); err != nil {
t.Fatal(err)
}
cleanupDriver(t, dt)
}
func TestOverlayNilConfig(t *testing.T) {
dt := &driverTester{t: t}
if err := Init(dt, nil); err != nil {
t.Fatal(err)
}
if err := dt.d.configure(); err != nil {
t.Fatal(err)
}
cleanupDriver(t, dt)
}
func TestOverlayConfig(t *testing.T) {
dt := setupDriver(t)
time.Sleep(1 * time.Second)
d := dt.d
if d.notifyCh == nil {
t.Fatal("Driver notify channel wasn't initialzed after Config method")
}
if d.exitCh == nil {
t.Fatal("Driver serfloop exit channel wasn't initialzed after Config method")
}
if d.serfInstance == nil {
t.Fatal("Driver serfinstance hasn't been initialized after Config method")
}
cleanupDriver(t, dt)
}
func TestOverlayType(t *testing.T) {
dt := &driverTester{t: t}
if err := Init(dt, nil); err != nil {
t.Fatal(err)
}
if dt.d.Type() != testNetworkType {
t.Fatalf("Expected Type() to return %q. Instead got %q", testNetworkType,
dt.d.Type())
}
}

View File

@@ -0,0 +1,329 @@
package overlay
import (
"fmt"
"net"
"sync"
"syscall"
)
type peerKey struct {
peerIP net.IP
peerMac net.HardwareAddr
}
type peerEntry struct {
eid string
vtep net.IP
peerIPMask net.IPMask
inSandbox bool
isLocal bool
}
type peerMap struct {
mp map[string]peerEntry
sync.Mutex
}
type peerNetworkMap struct {
mp map[string]*peerMap
sync.Mutex
}
func (pKey peerKey) String() string {
return fmt.Sprintf("%s %s", pKey.peerIP, pKey.peerMac)
}
func (pKey *peerKey) Scan(state fmt.ScanState, verb rune) error {
ipB, err := state.Token(true, nil)
if err != nil {
return err
}
pKey.peerIP = net.ParseIP(string(ipB))
macB, err := state.Token(true, nil)
if err != nil {
return err
}
pKey.peerMac, err = net.ParseMAC(string(macB))
if err != nil {
return err
}
return nil
}
var peerDbWg sync.WaitGroup
func (d *driver) peerDbWalk(f func(string, *peerKey, *peerEntry) bool) error {
d.peerDb.Lock()
nids := []string{}
for nid := range d.peerDb.mp {
nids = append(nids, nid)
}
d.peerDb.Unlock()
for _, nid := range nids {
d.peerDbNetworkWalk(nid, func(pKey *peerKey, pEntry *peerEntry) bool {
return f(nid, pKey, pEntry)
})
}
return nil
}
func (d *driver) peerDbNetworkWalk(nid string, f func(*peerKey, *peerEntry) bool) error {
d.peerDb.Lock()
pMap, ok := d.peerDb.mp[nid]
if !ok {
d.peerDb.Unlock()
return nil
}
d.peerDb.Unlock()
pMap.Lock()
for pKeyStr, pEntry := range pMap.mp {
var pKey peerKey
if _, err := fmt.Sscan(pKeyStr, &pKey); err != nil {
fmt.Printf("peer key scan failed: %v", err)
}
if f(&pKey, &pEntry) {
pMap.Unlock()
return nil
}
}
pMap.Unlock()
return nil
}
func (d *driver) peerDbSearch(nid string, peerIP net.IP) (net.HardwareAddr, net.IPMask, net.IP, error) {
var (
peerMac net.HardwareAddr
vtep net.IP
peerIPMask net.IPMask
found bool
)
err := d.peerDbNetworkWalk(nid, func(pKey *peerKey, pEntry *peerEntry) bool {
if pKey.peerIP.Equal(peerIP) {
peerMac = pKey.peerMac
peerIPMask = pEntry.peerIPMask
vtep = pEntry.vtep
found = true
return found
}
return found
})
if err != nil {
return nil, nil, nil, fmt.Errorf("peerdb search for peer ip %q failed: %v", peerIP, err)
}
if !found {
return nil, nil, nil, fmt.Errorf("peer ip %q not found in peerdb", peerIP)
}
return peerMac, peerIPMask, vtep, nil
}
func (d *driver) peerDbAdd(nid, eid string, peerIP net.IP, peerIPMask net.IPMask,
peerMac net.HardwareAddr, vtep net.IP, isLocal bool) {
peerDbWg.Wait()
d.peerDb.Lock()
pMap, ok := d.peerDb.mp[nid]
if !ok {
d.peerDb.mp[nid] = &peerMap{
mp: make(map[string]peerEntry),
}
pMap = d.peerDb.mp[nid]
}
d.peerDb.Unlock()
pKey := peerKey{
peerIP: peerIP,
peerMac: peerMac,
}
pEntry := peerEntry{
eid: eid,
vtep: vtep,
peerIPMask: peerIPMask,
isLocal: isLocal,
}
pMap.Lock()
pMap.mp[pKey.String()] = pEntry
pMap.Unlock()
}
func (d *driver) peerDbDelete(nid, eid string, peerIP net.IP, peerIPMask net.IPMask,
peerMac net.HardwareAddr, vtep net.IP) {
peerDbWg.Wait()
d.peerDb.Lock()
pMap, ok := d.peerDb.mp[nid]
if !ok {
d.peerDb.Unlock()
return
}
d.peerDb.Unlock()
pKey := peerKey{
peerIP: peerIP,
peerMac: peerMac,
}
pMap.Lock()
delete(pMap.mp, pKey.String())
pMap.Unlock()
}
func (d *driver) peerDbUpdateSandbox(nid string) {
d.peerDb.Lock()
pMap, ok := d.peerDb.mp[nid]
if !ok {
d.peerDb.Unlock()
return
}
d.peerDb.Unlock()
peerDbWg.Add(1)
var peerOps []func()
pMap.Lock()
for pKeyStr, pEntry := range pMap.mp {
var pKey peerKey
if _, err := fmt.Sscan(pKeyStr, &pKey); err != nil {
fmt.Printf("peer key scan failed: %v", err)
}
if pEntry.isLocal {
continue
}
// Go captures variables by reference. The pEntry could be
// pointing to the same memory location for every iteration. Make
// a copy of pEntry before capturing it in the following closure.
entry := pEntry
op := func() {
if err := d.peerAdd(nid, entry.eid, pKey.peerIP, entry.peerIPMask,
pKey.peerMac, entry.vtep,
false); err != nil {
fmt.Printf("peerdbupdate in sandbox failed for ip %s and mac %s: %v",
pKey.peerIP, pKey.peerMac, err)
}
}
peerOps = append(peerOps, op)
}
pMap.Unlock()
for _, op := range peerOps {
op()
}
peerDbWg.Done()
}
func (d *driver) peerAdd(nid, eid string, peerIP net.IP, peerIPMask net.IPMask,
peerMac net.HardwareAddr, vtep net.IP, updateDb bool) error {
if err := validateID(nid, eid); err != nil {
return err
}
if updateDb {
d.peerDbAdd(nid, eid, peerIP, peerIPMask, peerMac, vtep, false)
}
n := d.network(nid)
if n == nil {
return nil
}
sbox := n.sandbox()
if sbox == nil {
return nil
}
IP := &net.IPNet{
IP: peerIP,
Mask: peerIPMask,
}
s := n.getSubnetforIP(IP)
if s == nil {
return fmt.Errorf("couldn't find the subnet %q in network %q\n", IP.String(), n.id)
}
if err := n.obtainVxlanID(s); err != nil {
return fmt.Errorf("couldn't get vxlan id for %q: %v", s.subnetIP.String(), err)
}
if err := n.joinSubnetSandbox(s); err != nil {
return fmt.Errorf("subnet sandbox join failed for %q: %v", s.subnetIP.String(), err)
}
// Add neighbor entry for the peer IP
if err := sbox.AddNeighbor(peerIP, peerMac, sbox.NeighborOptions().LinkName(s.vxlanName)); err != nil {
return fmt.Errorf("could not add neigbor entry into the sandbox: %v", err)
}
// Add fdb entry to the bridge for the peer mac
if err := sbox.AddNeighbor(vtep, peerMac, sbox.NeighborOptions().LinkName(s.vxlanName),
sbox.NeighborOptions().Family(syscall.AF_BRIDGE)); err != nil {
return fmt.Errorf("could not add fdb entry into the sandbox: %v", err)
}
return nil
}
func (d *driver) peerDelete(nid, eid string, peerIP net.IP, peerIPMask net.IPMask,
peerMac net.HardwareAddr, vtep net.IP, updateDb bool) error {
if err := validateID(nid, eid); err != nil {
return err
}
if updateDb {
d.peerDbDelete(nid, eid, peerIP, peerIPMask, peerMac, vtep)
}
n := d.network(nid)
if n == nil {
return nil
}
sbox := n.sandbox()
if sbox == nil {
return nil
}
// Delete fdb entry to the bridge for the peer mac
if err := sbox.DeleteNeighbor(vtep, peerMac); err != nil {
return fmt.Errorf("could not delete fdb entry into the sandbox: %v", err)
}
// Delete neighbor entry for the peer IP
if err := sbox.DeleteNeighbor(peerIP, peerMac); err != nil {
return fmt.Errorf("could not delete neigbor entry into the sandbox: %v", err)
}
return nil
}
func (d *driver) pushLocalDb() {
d.peerDbWalk(func(nid string, pKey *peerKey, pEntry *peerEntry) bool {
if pEntry.isLocal {
d.pushLocalEndpointEvent("join", nid, pEntry.eid)
}
return false
})
}

View File

@@ -0,0 +1,164 @@
/*
Package api represents all requests and responses suitable for conversation
with a remote driver.
*/
package api
import (
"net"
"github.com/docker/libnetwork/driverapi"
)
// Response is the basic response structure used in all responses.
type Response struct {
Err string
}
// GetError returns the error from the response, if any.
func (r *Response) GetError() string {
return r.Err
}
// GetCapabilityResponse is the response of GetCapability request
type GetCapabilityResponse struct {
Response
Scope string
}
// CreateNetworkRequest requests a new network.
type CreateNetworkRequest struct {
// A network ID that remote plugins are expected to store for future
// reference.
NetworkID string
// A free form map->object interface for communication of options.
Options map[string]interface{}
// IPAMData contains the address pool information for this network
IPv4Data, IPv6Data []driverapi.IPAMData
}
// CreateNetworkResponse is the response to the CreateNetworkRequest.
type CreateNetworkResponse struct {
Response
}
// DeleteNetworkRequest is the request to delete an existing network.
type DeleteNetworkRequest struct {
// The ID of the network to delete.
NetworkID string
}
// DeleteNetworkResponse is the response to a request for deleting a network.
type DeleteNetworkResponse struct {
Response
}
// CreateEndpointRequest is the request to create an endpoint within a network.
type CreateEndpointRequest struct {
// Provided at create time, this will be the network id referenced.
NetworkID string
// The ID of the endpoint for later reference.
EndpointID string
Interface *EndpointInterface
Options map[string]interface{}
}
// EndpointInterface represents an interface endpoint.
type EndpointInterface struct {
Address string
AddressIPv6 string
MacAddress string
}
// CreateEndpointResponse is the response to the CreateEndpoint action.
type CreateEndpointResponse struct {
Response
Interface *EndpointInterface
}
// Interface is the representation of a linux interface.
type Interface struct {
Address *net.IPNet
AddressIPv6 *net.IPNet
MacAddress net.HardwareAddr
}
// DeleteEndpointRequest describes the API for deleting an endpoint.
type DeleteEndpointRequest struct {
NetworkID string
EndpointID string
}
// DeleteEndpointResponse is the response to the DeleteEndpoint action.
type DeleteEndpointResponse struct {
Response
}
// EndpointInfoRequest retrieves information about the endpoint from the network driver.
type EndpointInfoRequest struct {
NetworkID string
EndpointID string
}
// EndpointInfoResponse is the response to an EndpointInfoRequest.
type EndpointInfoResponse struct {
Response
Value map[string]interface{}
}
// JoinRequest describes the API for joining an endpoint to a sandbox.
type JoinRequest struct {
NetworkID string
EndpointID string
SandboxKey string
Options map[string]interface{}
}
// InterfaceName is the struct represetation of a pair of devices with source
// and destination, for the purposes of putting an endpoint into a container.
type InterfaceName struct {
SrcName string
DstName string
DstPrefix string
}
// StaticRoute is the plain JSON representation of a static route.
type StaticRoute struct {
Destination string
RouteType int
NextHop string
}
// JoinResponse is the response to a JoinRequest.
type JoinResponse struct {
Response
InterfaceName *InterfaceName
Gateway string
GatewayIPv6 string
StaticRoutes []StaticRoute
DisableGatewayService bool
}
// LeaveRequest describes the API for detaching an endpoint from a sandbox.
type LeaveRequest struct {
NetworkID string
EndpointID string
}
// LeaveResponse is the answer to LeaveRequest.
type LeaveResponse struct {
Response
}
// DiscoveryNotification represents a discovery notification
type DiscoveryNotification struct {
DiscoveryType driverapi.DiscoveryType
DiscoveryData interface{}
}
// DiscoveryResponse is used by libnetwork to log any plugin error processing the discovery notifications
type DiscoveryResponse struct {
Response
}

View File

@@ -0,0 +1,327 @@
package remote
import (
"fmt"
"net"
log "github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/plugins"
"github.com/docker/libnetwork/datastore"
"github.com/docker/libnetwork/driverapi"
"github.com/docker/libnetwork/drivers/remote/api"
"github.com/docker/libnetwork/types"
)
type driver struct {
endpoint *plugins.Client
networkType string
}
type maybeError interface {
GetError() string
}
func newDriver(name string, client *plugins.Client) driverapi.Driver {
return &driver{networkType: name, endpoint: client}
}
// Init makes sure a remote driver is registered when a network driver
// plugin is activated.
func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
plugins.Handle(driverapi.NetworkPluginEndpointType, func(name string, client *plugins.Client) {
// negotiate driver capability with client
d := newDriver(name, client)
c, err := d.(*driver).getCapabilities()
if err != nil {
log.Errorf("error getting capability for %s due to %v", name, err)
return
}
if err = dc.RegisterDriver(name, d, *c); err != nil {
log.Errorf("error registering driver for %s due to %v", name, err)
}
})
return nil
}
// Get capability from client
func (d *driver) getCapabilities() (*driverapi.Capability, error) {
var capResp api.GetCapabilityResponse
if err := d.call("GetCapabilities", nil, &capResp); err != nil {
return nil, err
}
c := &driverapi.Capability{}
switch capResp.Scope {
case "global":
c.DataScope = datastore.GlobalScope
case "local":
c.DataScope = datastore.LocalScope
default:
return nil, fmt.Errorf("invalid capability: expecting 'local' or 'global', got %s", capResp.Scope)
}
return c, nil
}
// Config is not implemented for remote drivers, since it is assumed
// to be supplied to the remote process out-of-band (e.g., as command
// line arguments).
func (d *driver) Config(option map[string]interface{}) error {
return &driverapi.ErrNotImplemented{}
}
func (d *driver) call(methodName string, arg interface{}, retVal maybeError) error {
method := driverapi.NetworkPluginEndpointType + "." + methodName
err := d.endpoint.Call(method, arg, retVal)
if err != nil {
return err
}
if e := retVal.GetError(); e != "" {
return fmt.Errorf("remote: %s", e)
}
return nil
}
func (d *driver) CreateNetwork(id string, options map[string]interface{}, ipV4Data, ipV6Data []driverapi.IPAMData) error {
create := &api.CreateNetworkRequest{
NetworkID: id,
Options: options,
IPv4Data: ipV4Data,
IPv6Data: ipV6Data,
}
return d.call("CreateNetwork", create, &api.CreateNetworkResponse{})
}
func (d *driver) DeleteNetwork(nid string) error {
delete := &api.DeleteNetworkRequest{NetworkID: nid}
return d.call("DeleteNetwork", delete, &api.DeleteNetworkResponse{})
}
func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo, epOptions map[string]interface{}) error {
if ifInfo == nil {
return fmt.Errorf("must not be called with nil InterfaceInfo")
}
reqIface := &api.EndpointInterface{}
if ifInfo.Address() != nil {
reqIface.Address = ifInfo.Address().String()
}
if ifInfo.AddressIPv6() != nil {
reqIface.AddressIPv6 = ifInfo.AddressIPv6().String()
}
if ifInfo.MacAddress() != nil {
reqIface.MacAddress = ifInfo.MacAddress().String()
}
create := &api.CreateEndpointRequest{
NetworkID: nid,
EndpointID: eid,
Interface: reqIface,
Options: epOptions,
}
var res api.CreateEndpointResponse
if err := d.call("CreateEndpoint", create, &res); err != nil {
return err
}
inIface, err := parseInterface(res)
if err != nil {
return err
}
if inIface == nil {
// Remote driver did not set any field
return nil
}
if inIface.MacAddress != nil {
if err := ifInfo.SetMacAddress(inIface.MacAddress); err != nil {
return errorWithRollback(fmt.Sprintf("driver modified interface MAC address: %v", err), d.DeleteEndpoint(nid, eid))
}
}
if inIface.Address != nil {
if err := ifInfo.SetIPAddress(inIface.Address); err != nil {
return errorWithRollback(fmt.Sprintf("driver modified interface address: %v", err), d.DeleteEndpoint(nid, eid))
}
}
if inIface.AddressIPv6 != nil {
if err := ifInfo.SetIPAddress(inIface.AddressIPv6); err != nil {
return errorWithRollback(fmt.Sprintf("driver modified interface address: %v", err), d.DeleteEndpoint(nid, eid))
}
}
return nil
}
func errorWithRollback(msg string, err error) error {
rollback := "rolled back"
if err != nil {
rollback = "failed to roll back: " + err.Error()
}
return fmt.Errorf("%s; %s", msg, rollback)
}
func (d *driver) DeleteEndpoint(nid, eid string) error {
delete := &api.DeleteEndpointRequest{
NetworkID: nid,
EndpointID: eid,
}
return d.call("DeleteEndpoint", delete, &api.DeleteEndpointResponse{})
}
func (d *driver) EndpointOperInfo(nid, eid string) (map[string]interface{}, error) {
info := &api.EndpointInfoRequest{
NetworkID: nid,
EndpointID: eid,
}
var res api.EndpointInfoResponse
if err := d.call("EndpointOperInfo", info, &res); err != nil {
return nil, err
}
return res.Value, nil
}
// Join method is invoked when a Sandbox is attached to an endpoint.
func (d *driver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) error {
join := &api.JoinRequest{
NetworkID: nid,
EndpointID: eid,
SandboxKey: sboxKey,
Options: options,
}
var (
res api.JoinResponse
err error
)
if err = d.call("Join", join, &res); err != nil {
return err
}
ifaceName := res.InterfaceName
if iface := jinfo.InterfaceName(); iface != nil && ifaceName != nil {
if err := iface.SetNames(ifaceName.SrcName, ifaceName.DstPrefix); err != nil {
return errorWithRollback(fmt.Sprintf("failed to set interface name: %s", err), d.Leave(nid, eid))
}
}
var addr net.IP
if res.Gateway != "" {
if addr = net.ParseIP(res.Gateway); addr == nil {
return fmt.Errorf(`unable to parse Gateway "%s"`, res.Gateway)
}
if jinfo.SetGateway(addr) != nil {
return errorWithRollback(fmt.Sprintf("failed to set gateway: %v", addr), d.Leave(nid, eid))
}
}
if res.GatewayIPv6 != "" {
if addr = net.ParseIP(res.GatewayIPv6); addr == nil {
return fmt.Errorf(`unable to parse GatewayIPv6 "%s"`, res.GatewayIPv6)
}
if jinfo.SetGatewayIPv6(addr) != nil {
return errorWithRollback(fmt.Sprintf("failed to set gateway IPv6: %v", addr), d.Leave(nid, eid))
}
}
if len(res.StaticRoutes) > 0 {
routes, err := parseStaticRoutes(res)
if err != nil {
return err
}
for _, route := range routes {
if jinfo.AddStaticRoute(route.Destination, route.RouteType, route.NextHop) != nil {
return errorWithRollback(fmt.Sprintf("failed to set static route: %v", route), d.Leave(nid, eid))
}
}
}
if res.DisableGatewayService {
jinfo.DisableGatewayService()
}
return nil
}
// Leave method is invoked when a Sandbox detaches from an endpoint.
func (d *driver) Leave(nid, eid string) error {
leave := &api.LeaveRequest{
NetworkID: nid,
EndpointID: eid,
}
return d.call("Leave", leave, &api.LeaveResponse{})
}
func (d *driver) Type() string {
return d.networkType
}
// DiscoverNew is a notification for a new discovery event, such as a new node joining a cluster
func (d *driver) DiscoverNew(dType driverapi.DiscoveryType, data interface{}) error {
if dType != driverapi.NodeDiscovery {
return fmt.Errorf("Unknown discovery type : %v", dType)
}
notif := &api.DiscoveryNotification{
DiscoveryType: dType,
DiscoveryData: data,
}
return d.call("DiscoverNew", notif, &api.DiscoveryResponse{})
}
// DiscoverDelete is a notification for a discovery delete event, such as a node leaving a cluster
func (d *driver) DiscoverDelete(dType driverapi.DiscoveryType, data interface{}) error {
if dType != driverapi.NodeDiscovery {
return fmt.Errorf("Unknown discovery type : %v", dType)
}
notif := &api.DiscoveryNotification{
DiscoveryType: dType,
DiscoveryData: data,
}
return d.call("DiscoverDelete", notif, &api.DiscoveryResponse{})
}
func parseStaticRoutes(r api.JoinResponse) ([]*types.StaticRoute, error) {
var routes = make([]*types.StaticRoute, len(r.StaticRoutes))
for i, inRoute := range r.StaticRoutes {
var err error
outRoute := &types.StaticRoute{RouteType: inRoute.RouteType}
if inRoute.Destination != "" {
if outRoute.Destination, err = types.ParseCIDR(inRoute.Destination); err != nil {
return nil, err
}
}
if inRoute.NextHop != "" {
outRoute.NextHop = net.ParseIP(inRoute.NextHop)
if outRoute.NextHop == nil {
return nil, fmt.Errorf("failed to parse nexthop IP %s", inRoute.NextHop)
}
}
routes[i] = outRoute
}
return routes, nil
}
// parseInterfaces validates all the parameters of an Interface and returns them.
func parseInterface(r api.CreateEndpointResponse) (*api.Interface, error) {
var outIf *api.Interface
inIf := r.Interface
if inIf != nil {
var err error
outIf = &api.Interface{}
if inIf.Address != "" {
if outIf.Address, err = types.ParseCIDR(inIf.Address); err != nil {
return nil, err
}
}
if inIf.AddressIPv6 != "" {
if outIf.AddressIPv6, err = types.ParseCIDR(inIf.AddressIPv6); err != nil {
return nil, err
}
}
if inIf.MacAddress != "" {
if outIf.MacAddress, err = net.ParseMAC(inIf.MacAddress); err != nil {
return nil, err
}
}
}
return outIf, nil
}

View File

@@ -0,0 +1,559 @@
package remote
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/http/httptest"
"os"
"testing"
"github.com/docker/docker/pkg/plugins"
"github.com/docker/libnetwork/datastore"
"github.com/docker/libnetwork/driverapi"
_ "github.com/docker/libnetwork/testutils"
"github.com/docker/libnetwork/types"
)
func decodeToMap(r *http.Request) (res map[string]interface{}, err error) {
err = json.NewDecoder(r.Body).Decode(&res)
return
}
func handle(t *testing.T, mux *http.ServeMux, method string, h func(map[string]interface{}) interface{}) {
mux.HandleFunc(fmt.Sprintf("/%s.%s", driverapi.NetworkPluginEndpointType, method), func(w http.ResponseWriter, r *http.Request) {
ask, err := decodeToMap(r)
if err != nil && err != io.EOF {
t.Fatal(err)
}
answer := h(ask)
err = json.NewEncoder(w).Encode(&answer)
if err != nil {
t.Fatal(err)
}
})
}
func setupPlugin(t *testing.T, name string, mux *http.ServeMux) func() {
if err := os.MkdirAll("/etc/docker/plugins", 0755); err != nil {
t.Fatal(err)
}
server := httptest.NewServer(mux)
if server == nil {
t.Fatal("Failed to start a HTTP Server")
}
if err := ioutil.WriteFile(fmt.Sprintf("/etc/docker/plugins/%s.spec", name), []byte(server.URL), 0644); err != nil {
t.Fatal(err)
}
mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
fmt.Fprintf(w, `{"Implements": ["%s"]}`, driverapi.NetworkPluginEndpointType)
})
return func() {
if err := os.RemoveAll("/etc/docker/plugins"); err != nil {
t.Fatal(err)
}
server.Close()
}
}
type testEndpoint struct {
t *testing.T
src string
dst string
address string
addressIPv6 string
macAddress string
gateway string
gatewayIPv6 string
resolvConfPath string
hostsPath string
nextHop string
destination string
routeType int
disableGatewayService bool
}
func (test *testEndpoint) Interface() driverapi.InterfaceInfo {
return test
}
func (test *testEndpoint) Address() *net.IPNet {
if test.address == "" {
return nil
}
nw, _ := types.ParseCIDR(test.address)
return nw
}
func (test *testEndpoint) AddressIPv6() *net.IPNet {
if test.addressIPv6 == "" {
return nil
}
nw, _ := types.ParseCIDR(test.addressIPv6)
return nw
}
func (test *testEndpoint) MacAddress() net.HardwareAddr {
if test.macAddress == "" {
return nil
}
mac, _ := net.ParseMAC(test.macAddress)
return mac
}
func (test *testEndpoint) SetMacAddress(mac net.HardwareAddr) error {
if test.macAddress != "" {
return types.ForbiddenErrorf("endpoint interface MAC address present (%s). Cannot be modified with %s.", test.macAddress, mac)
}
if mac == nil {
return types.BadRequestErrorf("tried to set nil MAC address to endpoint interface")
}
test.macAddress = mac.String()
return nil
}
func (test *testEndpoint) SetIPAddress(address *net.IPNet) error {
if address.IP == nil {
return types.BadRequestErrorf("tried to set nil IP address to endpoint interface")
}
if address.IP.To4() == nil {
return setAddress(&test.addressIPv6, address)
}
return setAddress(&test.address, address)
}
func setAddress(ifaceAddr *string, address *net.IPNet) error {
if *ifaceAddr != "" {
return types.ForbiddenErrorf("endpoint interface IP present (%s). Cannot be modified with (%s).", *ifaceAddr, address)
}
*ifaceAddr = address.String()
return nil
}
func (test *testEndpoint) InterfaceName() driverapi.InterfaceNameInfo {
return test
}
func compareIPs(t *testing.T, kind string, shouldBe string, supplied net.IP) {
ip := net.ParseIP(shouldBe)
if ip == nil {
t.Fatalf(`Invalid IP to test against: "%s"`, shouldBe)
}
if !ip.Equal(supplied) {
t.Fatalf(`%s IPs are not equal: expected "%s", got %v`, kind, shouldBe, supplied)
}
}
func compareIPNets(t *testing.T, kind string, shouldBe string, supplied net.IPNet) {
_, net, _ := net.ParseCIDR(shouldBe)
if net == nil {
t.Fatalf(`Invalid IP network to test against: "%s"`, shouldBe)
}
if !types.CompareIPNet(net, &supplied) {
t.Fatalf(`%s IP networks are not equal: expected "%s", got %v`, kind, shouldBe, supplied)
}
}
func (test *testEndpoint) SetGateway(ipv4 net.IP) error {
compareIPs(test.t, "Gateway", test.gateway, ipv4)
return nil
}
func (test *testEndpoint) SetGatewayIPv6(ipv6 net.IP) error {
compareIPs(test.t, "GatewayIPv6", test.gatewayIPv6, ipv6)
return nil
}
func (test *testEndpoint) SetNames(src string, dst string) error {
if test.src != src {
test.t.Fatalf(`Wrong SrcName; expected "%s", got "%s"`, test.src, src)
}
if test.dst != dst {
test.t.Fatalf(`Wrong DstPrefix; expected "%s", got "%s"`, test.dst, dst)
}
return nil
}
func (test *testEndpoint) AddStaticRoute(destination *net.IPNet, routeType int, nextHop net.IP) error {
compareIPNets(test.t, "Destination", test.destination, *destination)
compareIPs(test.t, "NextHop", test.nextHop, nextHop)
if test.routeType != routeType {
test.t.Fatalf(`Wrong RouteType; expected "%d", got "%d"`, test.routeType, routeType)
}
return nil
}
func (test *testEndpoint) DisableGatewayService() {
test.disableGatewayService = true
}
func TestGetEmptyCapabilities(t *testing.T) {
var plugin = "test-net-driver-empty-cap"
mux := http.NewServeMux()
defer setupPlugin(t, plugin, mux)()
handle(t, mux, "GetCapabilities", func(msg map[string]interface{}) interface{} {
return map[string]interface{}{}
})
p, err := plugins.Get(plugin, driverapi.NetworkPluginEndpointType)
if err != nil {
t.Fatal(err)
}
d := newDriver(plugin, p.Client)
if d.Type() != plugin {
t.Fatal("Driver type does not match that given")
}
_, err = d.(*driver).getCapabilities()
if err == nil {
t.Fatal("There should be error reported when get empty capability")
}
}
func TestGetExtraCapabilities(t *testing.T) {
var plugin = "test-net-driver-extra-cap"
mux := http.NewServeMux()
defer setupPlugin(t, plugin, mux)()
handle(t, mux, "GetCapabilities", func(msg map[string]interface{}) interface{} {
return map[string]interface{}{
"Scope": "local",
"foo": "bar",
}
})
p, err := plugins.Get(plugin, driverapi.NetworkPluginEndpointType)
if err != nil {
t.Fatal(err)
}
d := newDriver(plugin, p.Client)
if d.Type() != plugin {
t.Fatal("Driver type does not match that given")
}
c, err := d.(*driver).getCapabilities()
if err != nil {
t.Fatal(err)
} else if c.DataScope != datastore.LocalScope {
t.Fatalf("get capability '%s', expecting 'local'", c.DataScope)
}
}
func TestGetInvalidCapabilities(t *testing.T) {
var plugin = "test-net-driver-invalid-cap"
mux := http.NewServeMux()
defer setupPlugin(t, plugin, mux)()
handle(t, mux, "GetCapabilities", func(msg map[string]interface{}) interface{} {
return map[string]interface{}{
"Scope": "fake",
}
})
p, err := plugins.Get(plugin, driverapi.NetworkPluginEndpointType)
if err != nil {
t.Fatal(err)
}
d := newDriver(plugin, p.Client)
if d.Type() != plugin {
t.Fatal("Driver type does not match that given")
}
_, err = d.(*driver).getCapabilities()
if err == nil {
t.Fatal("There should be error reported when get invalid capability")
}
}
func TestRemoteDriver(t *testing.T) {
var plugin = "test-net-driver"
ep := &testEndpoint{
t: t,
src: "vethsrc",
dst: "vethdst",
address: "192.168.5.7/16",
addressIPv6: "2001:DB8::5:7/48",
macAddress: "",
gateway: "192.168.0.1",
gatewayIPv6: "2001:DB8::1",
hostsPath: "/here/comes/the/host/path",
resolvConfPath: "/there/goes/the/resolv/conf",
destination: "10.0.0.0/8",
nextHop: "10.0.0.1",
routeType: 1,
}
mux := http.NewServeMux()
defer setupPlugin(t, plugin, mux)()
var networkID string
handle(t, mux, "GetCapabilities", func(msg map[string]interface{}) interface{} {
return map[string]interface{}{
"Scope": "global",
}
})
handle(t, mux, "CreateNetwork", func(msg map[string]interface{}) interface{} {
nid := msg["NetworkID"]
var ok bool
if networkID, ok = nid.(string); !ok {
t.Fatal("RPC did not include network ID string")
}
return map[string]interface{}{}
})
handle(t, mux, "DeleteNetwork", func(msg map[string]interface{}) interface{} {
if nid, ok := msg["NetworkID"]; !ok || nid != networkID {
t.Fatal("Network ID missing or does not match that created")
}
return map[string]interface{}{}
})
handle(t, mux, "CreateEndpoint", func(msg map[string]interface{}) interface{} {
iface := map[string]interface{}{
"MacAddress": ep.macAddress,
}
return map[string]interface{}{
"Interface": iface,
}
})
handle(t, mux, "Join", func(msg map[string]interface{}) interface{} {
options := msg["Options"].(map[string]interface{})
foo, ok := options["foo"].(string)
if !ok || foo != "fooValue" {
t.Fatalf("Did not receive expected foo string in request options: %+v", msg)
}
return map[string]interface{}{
"Gateway": ep.gateway,
"GatewayIPv6": ep.gatewayIPv6,
"HostsPath": ep.hostsPath,
"ResolvConfPath": ep.resolvConfPath,
"InterfaceName": map[string]interface{}{
"SrcName": ep.src,
"DstPrefix": ep.dst,
},
"StaticRoutes": []map[string]interface{}{
{
"Destination": ep.destination,
"RouteType": ep.routeType,
"NextHop": ep.nextHop,
},
},
}
})
handle(t, mux, "Leave", func(msg map[string]interface{}) interface{} {
return map[string]string{}
})
handle(t, mux, "DeleteEndpoint", func(msg map[string]interface{}) interface{} {
return map[string]interface{}{}
})
handle(t, mux, "EndpointOperInfo", func(msg map[string]interface{}) interface{} {
return map[string]interface{}{
"Value": map[string]string{
"Arbitrary": "key",
"Value": "pairs?",
},
}
})
handle(t, mux, "DiscoverNew", func(msg map[string]interface{}) interface{} {
return map[string]string{}
})
handle(t, mux, "DiscoverDelete", func(msg map[string]interface{}) interface{} {
return map[string]interface{}{}
})
p, err := plugins.Get(plugin, driverapi.NetworkPluginEndpointType)
if err != nil {
t.Fatal(err)
}
d := newDriver(plugin, p.Client)
if d.Type() != plugin {
t.Fatal("Driver type does not match that given")
}
c, err := d.(*driver).getCapabilities()
if err != nil {
t.Fatal(err)
} else if c.DataScope != datastore.GlobalScope {
t.Fatalf("get capability '%s', expecting 'global'", c.DataScope)
}
netID := "dummy-network"
err = d.CreateNetwork(netID, map[string]interface{}{}, nil, nil)
if err != nil {
t.Fatal(err)
}
endID := "dummy-endpoint"
err = d.CreateEndpoint(netID, endID, ep, map[string]interface{}{})
if err != nil {
t.Fatal(err)
}
joinOpts := map[string]interface{}{"foo": "fooValue"}
err = d.Join(netID, endID, "sandbox-key", ep, joinOpts)
if err != nil {
t.Fatal(err)
}
if _, err = d.EndpointOperInfo(netID, endID); err != nil {
t.Fatal(err)
}
if err = d.Leave(netID, endID); err != nil {
t.Fatal(err)
}
if err = d.DeleteEndpoint(netID, endID); err != nil {
t.Fatal(err)
}
if err = d.DeleteNetwork(netID); err != nil {
t.Fatal(err)
}
data := driverapi.NodeDiscoveryData{
Address: "192.168.1.1",
}
if err = d.DiscoverNew(driverapi.NodeDiscovery, data); err != nil {
t.Fatal(err)
}
if err = d.DiscoverDelete(driverapi.NodeDiscovery, data); err != nil {
t.Fatal(err)
}
}
func TestDriverError(t *testing.T) {
var plugin = "test-net-driver-error"
mux := http.NewServeMux()
defer setupPlugin(t, plugin, mux)()
handle(t, mux, "CreateEndpoint", func(msg map[string]interface{}) interface{} {
return map[string]interface{}{
"Err": "this should get raised as an error",
}
})
p, err := plugins.Get(plugin, driverapi.NetworkPluginEndpointType)
if err != nil {
t.Fatal(err)
}
driver := newDriver(plugin, p.Client)
if err := driver.CreateEndpoint("dummy", "dummy", &testEndpoint{t: t}, map[string]interface{}{}); err == nil {
t.Fatalf("Expected error from driver")
}
}
func TestMissingValues(t *testing.T) {
var plugin = "test-net-driver-missing"
mux := http.NewServeMux()
defer setupPlugin(t, plugin, mux)()
ep := &testEndpoint{
t: t,
}
handle(t, mux, "CreateEndpoint", func(msg map[string]interface{}) interface{} {
iface := map[string]interface{}{
"Address": ep.address,
"AddressIPv6": ep.addressIPv6,
"MacAddress": ep.macAddress,
}
return map[string]interface{}{
"Interface": iface,
}
})
p, err := plugins.Get(plugin, driverapi.NetworkPluginEndpointType)
if err != nil {
t.Fatal(err)
}
driver := newDriver(plugin, p.Client)
if err := driver.CreateEndpoint("dummy", "dummy", ep, map[string]interface{}{}); err != nil {
t.Fatal(err)
}
}
type rollbackEndpoint struct {
}
func (r *rollbackEndpoint) Interface() driverapi.InterfaceInfo {
return r
}
func (r *rollbackEndpoint) MacAddress() net.HardwareAddr {
return nil
}
func (r *rollbackEndpoint) Address() *net.IPNet {
return nil
}
func (r *rollbackEndpoint) AddressIPv6() *net.IPNet {
return nil
}
func (r *rollbackEndpoint) SetMacAddress(mac net.HardwareAddr) error {
return fmt.Errorf("invalid mac")
}
func (r *rollbackEndpoint) SetIPAddress(ip *net.IPNet) error {
return fmt.Errorf("invalid ip")
}
func TestRollback(t *testing.T) {
var plugin = "test-net-driver-rollback"
mux := http.NewServeMux()
defer setupPlugin(t, plugin, mux)()
rolledback := false
handle(t, mux, "CreateEndpoint", func(msg map[string]interface{}) interface{} {
iface := map[string]interface{}{
"Address": "192.168.4.5/16",
"AddressIPv6": "",
"MacAddress": "7a:12:34:56:78:90",
}
return map[string]interface{}{
"Interface": interface{}(iface),
}
})
handle(t, mux, "DeleteEndpoint", func(msg map[string]interface{}) interface{} {
rolledback = true
return map[string]interface{}{}
})
p, err := plugins.Get(plugin, driverapi.NetworkPluginEndpointType)
if err != nil {
t.Fatal(err)
}
driver := newDriver(plugin, p.Client)
ep := &rollbackEndpoint{}
if err := driver.CreateEndpoint("dummy", "dummy", ep.Interface(), map[string]interface{}{}); err == nil {
t.Fatalf("Expected error from driver")
}
if !rolledback {
t.Fatalf("Expected to have had DeleteEndpoint called")
}
}

View File

@@ -0,0 +1,64 @@
package windows
import (
"github.com/docker/libnetwork/datastore"
"github.com/docker/libnetwork/driverapi"
)
const networkType = "windows"
// TODO Windows. This is a placeholder for now
type driver struct{}
// Init registers a new instance of null driver
func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
c := driverapi.Capability{
DataScope: datastore.LocalScope,
}
return dc.RegisterDriver(networkType, &driver{}, c)
}
func (d *driver) CreateNetwork(id string, option map[string]interface{}, ipV4Data, ipV6Data []driverapi.IPAMData) error {
return nil
}
func (d *driver) DeleteNetwork(nid string) error {
return nil
}
func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo, epOptions map[string]interface{}) error {
return nil
}
func (d *driver) DeleteEndpoint(nid, eid string) error {
return nil
}
func (d *driver) EndpointOperInfo(nid, eid string) (map[string]interface{}, error) {
return make(map[string]interface{}, 0), nil
}
// Join method is invoked when a Sandbox is attached to an endpoint.
func (d *driver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) error {
return nil
}
// Leave method is invoked when a Sandbox detaches from an endpoint.
func (d *driver) Leave(nid, eid string) error {
return nil
}
func (d *driver) Type() string {
return networkType
}
// DiscoverNew is a notification for a new discovery event, such as a new node joining a cluster
func (d *driver) DiscoverNew(dType driverapi.DiscoveryType, data interface{}) error {
return nil
}
// DiscoverDelete is a notification for a discovery delete event, such as a node leaving a cluster
func (d *driver) DiscoverDelete(dType driverapi.DiscoveryType, data interface{}) error {
return nil
}