Explorar o código

Merge pull request #1225 from puneetpruthi/solaris_integ

libnetwork support for docker on Solaris
Madhu Venugopal %!s(int64=8) %!d(string=hai) anos
pai
achega
9dce786794

+ 43 - 2
libnetwork/Makefile

@@ -8,6 +8,14 @@ ciargs = -e CIRCLECI -e "COVERALLS_TOKEN=$$COVERALLS_TOKEN" -e "INSIDECONTAINER=
 cidocker = docker run ${dockerargs} ${ciargs} $$EXTRA_ARGS ${container_env} ${build_image}
 CROSS_PLATFORMS = linux/amd64 linux/386 linux/arm windows/amd64
 export PATH := $(CURDIR)/bin:$(PATH)
+hostOS = ${shell go env GOHOSTOS}
+ifeq (${hostOS}, solaris)
+	gnufind=gfind
+	gnutail=gtail
+else
+	gnufind=find
+	gnutail=tail
+endif
 
 all: ${build_image}.created build check integration-tests clean
 
@@ -62,7 +70,40 @@ check-format:
 run-tests:
 	@echo "Running tests... "
 	@echo "mode: count" > coverage.coverprofile
-	@for dir in $$(find . -maxdepth 10 -not -path './.git*' -not -path '*/_*' -type d); do \
+	@for dir in $$( ${gnufind} . -maxdepth 10 -not -path './.git*' -not -path '*/_*' -type d); do \
+	    if [ ${hostOS} == solaris ]; then \
+	        case "$$dir" in \
+		    "./cmd/dnet" ) \
+		    ;& \
+		    "./cmd/ovrouter" ) \
+		    ;& \
+		    "./ns" ) \
+		    ;& \
+		    "./iptables" ) \
+		    ;& \
+		    "./ipvs" ) \
+		    ;& \
+		    "./drivers/bridge" ) \
+		    ;& \
+		    "./drivers/host" ) \
+		    ;& \
+		    "./drivers/ipvlan" ) \
+		    ;& \
+		    "./drivers/macvlan" ) \
+		    ;& \
+		    "./drivers/overlay" ) \
+		    ;& \
+		    "./drivers/remote" ) \
+		    ;& \
+		    "./drivers/windows" ) \
+			echo "Skipping $$dir on solaris host... "; \
+			continue; \
+			;; \
+		    * )\
+			echo "Entering $$dir ... "; \
+			;; \
+	        esac; \
+	    fi; \
 	    if ls $$dir/*.go &> /dev/null; then \
 		pushd . &> /dev/null ; \
 		cd $$dir ; \
@@ -71,7 +112,7 @@ run-tests:
 		if [ $$ret -ne 0 ]; then exit $$ret; fi ;\
 		popd &> /dev/null; \
 		if [ -f $$dir/profile.tmp ]; then \
-			cat $$dir/profile.tmp | tail -n +2 >> coverage.coverprofile ; \
+			cat $$dir/profile.tmp | ${gnutail} -n +2 >> coverage.coverprofile ; \
 				rm $$dir/profile.tmp ; \
 	    fi ; \
 	fi ; \

+ 18 - 0
libnetwork/api/api_linux_test.go

@@ -0,0 +1,18 @@
+package api
+
+import (
+	"github.com/docker/libnetwork/drivers/bridge"
+	"github.com/docker/libnetwork/netlabel"
+)
+
+func GetOpsMap(bridgeName, defaultMTU string) map[string]string {
+	if defaultMTU == "" {
+		return map[string]string{
+			bridge.BridgeName: bridgeName,
+		}
+	}
+	return map[string]string{
+		bridge.BridgeName:  bridgeName,
+		netlabel.DriverMTU: defaultMTU,
+	}
+}

+ 18 - 0
libnetwork/api/api_solaris_test.go

@@ -0,0 +1,18 @@
+package api
+
+import (
+	"github.com/docker/libnetwork/drivers/solaris/bridge"
+	"github.com/docker/libnetwork/netlabel"
+)
+
+func GetOpsMap(bridgeName, defaultMTU string) map[string]string {
+	if defaultMTU == "" {
+		return map[string]string{
+			bridge.BridgeName: bridgeName,
+		}
+	}
+	return map[string]string{
+		bridge.BridgeName:  bridgeName,
+		netlabel.DriverMTU: defaultMTU,
+	}
+}

+ 4 - 12
libnetwork/api/api_test.go

@@ -15,7 +15,6 @@ import (
 	"github.com/docker/docker/pkg/reexec"
 	"github.com/docker/libnetwork"
 	"github.com/docker/libnetwork/datastore"
-	"github.com/docker/libnetwork/drivers/bridge"
 	"github.com/docker/libnetwork/netlabel"
 	"github.com/docker/libnetwork/options"
 	"github.com/docker/libnetwork/testutils"
@@ -225,9 +224,7 @@ func TestCreateDeleteNetwork(t *testing.T) {
 		t.Fatalf("Expected StatusBadRequest status code, got: %v", errRsp)
 	}
 
-	dops := map[string]string{
-		bridge.BridgeName: "abc",
-	}
+	dops := GetOpsMap("abc", "")
 	nops := map[string]string{
 		netlabel.EnableIPv6: "true",
 	}
@@ -273,9 +270,7 @@ func TestGetNetworksAndEndpoints(t *testing.T) {
 	}
 	defer c.Stop()
 
-	ops := map[string]string{
-		bridge.BridgeName: "api_test_nw",
-	}
+	ops := GetOpsMap("api_test_nw", "")
 	nc := networkCreate{Name: "sh", NetworkType: bridgeNetType, DriverOpts: ops}
 	body, err := json.Marshal(nc)
 	if err != nil {
@@ -544,7 +539,7 @@ func TestProcGetServices(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	netName2 := "work-dev"
+	netName2 := "workdev"
 	netOption = options.Generic{
 		netlabel.GenericData: options.Generic{
 			"BridgeName": netName2,
@@ -1811,10 +1806,7 @@ func TestEndToEnd(t *testing.T) {
 
 	handleRequest := NewHTTPHandler(c)
 
-	dops := map[string]string{
-		bridge.BridgeName:  "cdef",
-		netlabel.DriverMTU: "1460",
-	}
+	dops := GetOpsMap("cdef", "1460")
 	nops := map[string]string{
 		netlabel.EnableIPv6: "true",
 	}

+ 1254 - 0
libnetwork/drivers/solaris/bridge/bridge.go

@@ -0,0 +1,1254 @@
+// +build solaris
+
+package bridge
+
+import (
+	"bufio"
+	"errors"
+	"fmt"
+	"net"
+	"os"
+	"os/exec"
+	"strconv"
+	"strings"
+	"sync"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/docker/libnetwork/datastore"
+	"github.com/docker/libnetwork/discoverapi"
+	"github.com/docker/libnetwork/driverapi"
+	"github.com/docker/libnetwork/iptables"
+	"github.com/docker/libnetwork/netlabel"
+	"github.com/docker/libnetwork/netutils"
+	"github.com/docker/libnetwork/options"
+	"github.com/docker/libnetwork/portmapper"
+	"github.com/docker/libnetwork/types"
+)
+
+const (
+	networkType = "bridge"
+
+	// DefaultBridgeName is the default name for the bridge interface managed
+	// by the driver when unspecified by the caller.
+	DefaultBridgeName = "docker0"
+
+	// 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"
+
+	// DefaultGatewayV4AuxKey represents the default-gateway configured by the user
+	DefaultGatewayV4AuxKey = "DefaultGatewayIPv4"
+
+	// DefaultGatewayV6AuxKey represents the ipv6 default-gateway configured by the user
+	DefaultGatewayV6AuxKey = "DefaultGatewayIPv6"
+)
+
+// configuration info for the "bridge" driver.
+type configuration struct {
+	EnableIPForwarding  bool
+	EnableIPTables      bool
+	EnableUserlandProxy bool
+}
+
+// networkConfiguration for network specific configuration
+type networkConfiguration struct {
+	ID                 string
+	BridgeName         string
+	BridgeNameInternal string
+	EnableIPv6         bool
+	EnableIPMasquerade bool
+	EnableICC          bool
+	Mtu                int
+	DefaultBindingIntf string
+	DefaultBindingIP   net.IP
+	DefaultBridge      bool
+	// Internal fields set after ipam data parsing
+	AddressIPv4        *net.IPNet
+	AddressIPv6        *net.IPNet
+	DefaultGatewayIPv4 net.IP
+	DefaultGatewayIPv6 net.IP
+	dbIndex            uint64
+	dbExists           bool
+	Internal           bool
+}
+
+// endpointConfiguration represents the user specified configuration for the sandbox endpoint
+type endpointConfiguration struct {
+	MacAddress   net.HardwareAddr
+	PortBindings []types.PortBinding
+	ExposedPorts []types.TransportPort
+}
+
+// containerConfiguration represents the user specified configuration for a container
+type containerConfiguration struct {
+	ParentEndpoints []string
+	ChildEndpoints  []string
+}
+
+// cnnectivityConfiguration represents the user specified configuration regarding the external connectivity
+type connectivityConfiguration struct {
+	PortBindings []types.PortBinding
+	ExposedPorts []types.TransportPort
+}
+
+type bridgeEndpoint struct {
+	id              string
+	nid             string
+	srcName         string
+	addr            *net.IPNet
+	addrv6          *net.IPNet
+	macAddress      net.HardwareAddr
+	config          *endpointConfiguration // User specified parameters
+	containerConfig *containerConfiguration
+	extConnConfig   *connectivityConfiguration
+	portMapping     []types.PortBinding // Operation port bindings
+	dbIndex         uint64
+	dbExists        bool
+}
+
+type bridgeInterface struct {
+	bridgeIPv4  *net.IPNet
+	bridgeIPv6  *net.IPNet
+	gatewayIPv4 net.IP
+	gatewayIPv6 net.IP
+}
+
+type bridgeNetwork struct {
+	id         string
+	bridge     *bridgeInterface
+	config     *networkConfiguration
+	endpoints  map[string]*bridgeEndpoint // key: endpoint id
+	portMapper *portmapper.PortMapper
+	driver     *driver // The network's driver
+	sync.Mutex
+}
+
+type driver struct {
+	config         *configuration
+	network        *bridgeNetwork
+	natChain       *iptables.ChainInfo
+	filterChain    *iptables.ChainInfo
+	isolationChain *iptables.ChainInfo
+	networks       map[string]*bridgeNetwork
+	store          datastore.DataStore
+	sync.Mutex
+	defrouteIP net.IP
+}
+
+// New constructs a new bridge driver
+func newDriver() *driver {
+	return &driver{networks: map[string]*bridgeNetwork{}}
+}
+
+// Init registers a new instance of bridge driver
+func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
+	d := newDriver()
+	if err := d.configure(config); err != nil {
+		return err
+	}
+
+	c := driverapi.Capability{
+		DataScope: datastore.LocalScope,
+	}
+	return dc.RegisterDriver(networkType, d, c)
+}
+
+func (d *driver) NetworkAllocate(id string, option map[string]string, ipV4Data, ipV6Data []driverapi.IPAMData) (map[string]string, error) {
+	return nil, types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) NetworkFree(id string) error {
+	return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) {
+}
+
+func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo driverapi.NetworkInfo, ipV4Data, ipV6Data []driverapi.IPAMData) error {
+	if len(ipV4Data) == 0 || ipV4Data[0].Pool.String() == "0.0.0.0/0" {
+		return types.BadRequestErrorf("ipv4 pool is empty")
+	}
+	// Sanity checks
+	d.Lock()
+	if _, ok := d.networks[id]; ok {
+		d.Unlock()
+		return types.ForbiddenErrorf("network %s exists", id)
+	}
+	d.Unlock()
+
+	// Parse and validate the config. It should not conflict with existing networks' config
+	config, err := parseNetworkOptions(d, id, option)
+	if err != nil {
+		return err
+	}
+
+	err = config.processIPAM(id, ipV4Data, ipV6Data)
+	if err != nil {
+		return err
+	}
+
+	if err = d.createNetwork(config); err != nil {
+		return err
+	}
+
+	return d.storeUpdate(config)
+}
+
+func newInterface(config *networkConfiguration) *bridgeInterface {
+	i := &bridgeInterface{}
+
+	i.bridgeIPv4 = config.AddressIPv4
+	i.gatewayIPv4 = config.AddressIPv4.IP
+	if config.BridgeName == "" {
+		config.BridgeName = DefaultBridgeName
+	}
+	return i
+}
+
+// This function prunes the pf.conf for the firewall
+// that enable the service successfully.
+func fixPFConf() error {
+	conf := "/etc/firewall/pf.conf"
+	f, err := os.Open("/etc/firewall/pf.conf")
+	if err != nil {
+		return fmt.Errorf("cannot open %s: %v", conf, err)
+	}
+	defer f.Close()
+
+	// Look for line beginning with "REMOVE THIS LINE"
+	modify := false
+	lines := []string{}
+	scanner := bufio.NewScanner(f)
+	for scanner.Scan() {
+		l := scanner.Text()
+		if strings.Contains(l, "REMOVE THIS LINE") {
+			modify = true
+			continue
+		}
+		lines = append(lines, fmt.Sprintf("%s\n", l))
+	}
+	if err = scanner.Err(); err != nil {
+		return fmt.Errorf("cannot open %s: %v", conf, err)
+	}
+
+	// No changes needed to fix pf.conf
+	if !modify {
+		return nil
+	}
+
+	// Write back the file removing the line found above
+	tmpname := "/etc/firewall/pf.conf.tmp." + strconv.Itoa(os.Getpid())
+	tmp, err := os.OpenFile(tmpname,
+		os.O_CREATE|os.O_TRUNC|os.O_WRONLY|os.O_APPEND, 0644)
+	if err != nil {
+		return fmt.Errorf("cannot open %s: %v", tmpname, err)
+	}
+	defer tmp.Close()
+	for _, l := range lines {
+		_, err = tmp.WriteString(l)
+		if err != nil {
+			return fmt.Errorf("cannot write to %s: %v",
+				tmpname, err)
+		}
+	}
+	if err = tmp.Sync(); err != nil {
+		return fmt.Errorf("cannot sync %s: %v", tmpname, err)
+	}
+	if err = os.Rename(tmpname, conf); err != nil {
+		return fmt.Errorf("cannot rename %s to %s: %v",
+			tmpname, conf, err)
+	}
+	return nil
+}
+
+func (d *driver) initFirewall() error {
+	out, err := exec.Command("/usr/bin/svcs", "-Ho", "state",
+		"firewall").Output()
+	if err != nil {
+		return fmt.Errorf("cannot check firewall state: %v", err)
+	}
+	state := strings.TrimSpace(string(out))
+	if state != "online" {
+		if state != "disabled" {
+			return fmt.Errorf("firewall service is in %s state. "+
+				"please enable service manually.", state)
+		}
+		if err = fixPFConf(); err != nil {
+			return fmt.Errorf("cannot verify pf.conf: %v", err)
+		}
+		err = exec.Command("/usr/sbin/svcadm", "enable", "-ts",
+			"firewall").Run()
+		if err != nil {
+			return fmt.Errorf("cannot enable firewall service: %v", err)
+		}
+	}
+	out, err = exec.Command("/usr/sbin/pfctl", "-sr").Output()
+	if err != nil {
+		return fmt.Errorf("failed to list firewall rules: %v", err)
+	}
+	if strings.Contains(string(out), "anchor \"_auto/docker/*\" all") {
+		return nil
+	}
+	pfctlCmd := "(/usr/sbin/pfctl -sr; " +
+		"/usr/bin/echo \"anchor \\\"_auto/docker/*\\\"\") |" +
+		"/usr/sbin/pfctl -f -"
+	err = exec.Command("/usr/bin/bash", "-c", pfctlCmd).Run()
+	if err != nil {
+		return fmt.Errorf("failed to add docker firewall rules: %v", err)
+	}
+	return nil
+}
+
+func (d *driver) initRouting() error {
+	err := exec.Command("/usr/sbin/ipadm", "set-prop", "-t",
+		"-p", "forwarding=on", "ipv4").Run()
+	if err != nil {
+		return fmt.Errorf("cannot switch-on IP forwarding: %v", err)
+	}
+	routeCmd := "/usr/sbin/ipadm show-addr -p -o addr " +
+		"`/usr/sbin/route get default | /usr/bin/grep interface | " +
+		"/usr/bin/awk '{print $2}'`"
+	out, err := exec.Command("/usr/bin/bash", "-c", routeCmd).Output()
+	if err != nil {
+		return fmt.Errorf("cannot get default route: %v", err)
+	}
+	defroute := strings.SplitN(string(out), "/", 2)
+	d.defrouteIP = net.ParseIP(defroute[0])
+	if d.defrouteIP == nil {
+		return &ErrNoIPAddr{}
+	}
+	return nil
+}
+
+func (d *driver) configure(option map[string]interface{}) error {
+	var err error
+
+	if err = d.initFirewall(); err != nil {
+		return fmt.Errorf("failed to configure firewall: %v", err)
+	}
+	if err = d.initRouting(); err != nil {
+		return fmt.Errorf("failed to configure routing: %v", err)
+	}
+	if err = d.initStore(option); err != nil {
+		return fmt.Errorf("failed to initialize datastore: %v", err)
+	}
+
+	return nil
+}
+
+func (d *driver) getNetwork(id string) (*bridgeNetwork, error) {
+	d.Lock()
+	defer d.Unlock()
+
+	if id == "" {
+		return nil, types.BadRequestErrorf("invalid network id: %s", id)
+	}
+
+	if nw, ok := d.networks[id]; ok {
+		return nw, nil
+	}
+
+	return nil, types.NotFoundErrorf("network not found: %s", id)
+}
+
+// Return a slice of networks over which caller can iterate safely
+func (d *driver) getNetworks() []*bridgeNetwork {
+	d.Lock()
+	defer d.Unlock()
+
+	ls := make([]*bridgeNetwork, 0, len(d.networks))
+	for _, nw := range d.networks {
+		ls = append(ls, nw)
+	}
+	return ls
+}
+
+func bridgeSetup(config *networkConfiguration) error {
+	var err error
+	var bindingIntf string
+
+	bridgeName := config.BridgeName
+	gwName := fmt.Sprintf("%s_gw0", bridgeName)
+	gwIP := config.AddressIPv4.String()
+
+	if config.DefaultBindingIP == nil {
+		// Default to net0 if bindingIP is not provided.
+		bindingIntf = "net0"
+	} else {
+		ipadmCmd := "/usr/sbin/ipadm show-addr -p -o addrobj,addr |" +
+			"/usr/bin/grep " + config.DefaultBindingIP.String()
+		out, err := exec.Command("/usr/bin/bash", "-c", ipadmCmd).Output()
+		if err != nil {
+			logrus.Warnf("cannot find binding interface")
+			return err
+		}
+		bindingIntf = strings.SplitN(string(out), "/", 2)[0]
+		if bindingIntf == "" {
+			logrus.Warnf("cannot parse binding interface %s", string(out))
+			return &ErrNoIPAddr{}
+		}
+	}
+	config.DefaultBindingIntf = bindingIntf
+
+	err = exec.Command("/usr/sbin/dladm", "create-etherstub",
+		"-t", config.BridgeNameInternal).Run()
+	if err != nil {
+		logrus.Warnf("cannot create etherstub %s: %+v", config.BridgeNameInternal, err)
+		return err
+	}
+	err = exec.Command("/usr/sbin/dladm", "create-vnic",
+		"-t", "-l", config.BridgeNameInternal, gwName).Run()
+	if err != nil {
+		logrus.Warnf("cannot create vnic %s", gwName)
+		return err
+	}
+	err = exec.Command("/usr/sbin/ifconfig", gwName,
+		"plumb", gwIP, "up").Run()
+	if err != nil {
+		logrus.Warnf("cannot create gateway interface %s on %s",
+			gwIP, gwName)
+		return err
+	}
+
+	tableName := "bridge_nw_subnets"
+	pfAnchor := fmt.Sprintf("_auto/docker/%s", tableName)
+	err = exec.Command("/usr/sbin/pfctl", "-a", pfAnchor, "-t", tableName, "-T", "add", gwIP).Run()
+	if err != nil {
+		logrus.Warnf("cannot add bridge network '%s' to PF table", bridgeName)
+	}
+
+	pfCmd := fmt.Sprintf(
+		"/usr/bin/echo \"pass out on %s from %s:network to any nat-to (%s)\n"+
+			"block in quick from { <%s>, ! %s } to %s\" |"+
+			"/usr/sbin/pfctl -a _auto/docker/%s -f -",
+		bindingIntf, gwName, bindingIntf,
+		tableName, gwIP, gwIP,
+		bridgeName)
+	err = exec.Command("/usr/bin/bash", "-c", pfCmd).Run()
+	if err != nil {
+		logrus.Warnf("cannot add pf rule using: %s", pfCmd)
+		return err
+	}
+
+	return nil
+}
+
+func bridgeCleanup(config *networkConfiguration, logErr bool) {
+	var err error
+
+	bridgeName := config.BridgeName
+	tableName := "bridge_nw_subnets"
+	gwName := fmt.Sprintf("%s_gw0", bridgeName)
+	gwIP := config.AddressIPv4.String()
+	pfAnchor := fmt.Sprintf("_auto/docker/%s", bridgeName)
+	tableAnchor := fmt.Sprintf("_auto/docker/%s", tableName)
+
+	err = exec.Command("/usr/sbin/pfctl", "-a", pfAnchor, "-F", "all").Run()
+	if err != nil && logErr {
+		logrus.Warnf("cannot flush firewall rules")
+	}
+	err = exec.Command("/usr/sbin/ifconfig", gwName, "unplumb").Run()
+	if err != nil && logErr {
+		logrus.Warnf("cannot remove gateway interface")
+	}
+	err = exec.Command("/usr/sbin/dladm", "delete-vnic",
+		"-t", gwName).Run()
+	if err != nil && logErr {
+		logrus.Warnf("cannot delete vnic")
+	}
+	err = exec.Command("/usr/sbin/dladm", "delete-etherstub",
+		"-t", config.BridgeNameInternal).Run()
+	if err != nil && logErr {
+		logrus.Warnf("cannot delete etherstub")
+	}
+	err = exec.Command("/usr/sbin/pfctl", "-a", tableAnchor, "-t", tableName, "-T", "delete", gwIP).Run()
+	if err != nil && logErr {
+		logrus.Warnf("cannot remove bridge network '%s' from PF table", bridgeName)
+	}
+}
+
+func (d *driver) createNetwork(config *networkConfiguration) error {
+	var err error
+
+	logrus.Infof("Creating bridge network: %s %s %s", config.ID,
+		config.BridgeName, config.AddressIPv4)
+
+	networkList := d.getNetworks()
+	for i, nw := range networkList {
+		nw.Lock()
+		nwConfig := nw.config
+		nw.Unlock()
+		if err := nwConfig.Conflicts(config); err != nil {
+			if config.DefaultBridge {
+				// We encountered and identified a stale default network
+				// We must delete it as libnetwork is the source of thruth
+				// The default network being created must be the only one
+				// This can happen only from docker 1.12 on ward
+				logrus.Infof("Removing stale default bridge network %s (%s)", nwConfig.ID, nwConfig.BridgeName)
+				if err := d.DeleteNetwork(nwConfig.ID); err != nil {
+					logrus.Warnf("Failed to remove stale default network: %s (%s): %v. Will remove from store.", nwConfig.ID, nwConfig.BridgeName, err)
+					d.storeDelete(nwConfig)
+				}
+				networkList = append(networkList[:i], networkList[i+1:]...)
+			} else {
+				return types.ForbiddenErrorf(
+					"cannot create network %s (%s): "+
+						"conflicts with network %s (%s): %s",
+					nwConfig.BridgeName, config.ID, nw.id,
+					nw.config.BridgeName, err.Error())
+			}
+		}
+	}
+	if config.DefaultBindingIP == nil ||
+		config.DefaultBindingIP.IsUnspecified() {
+		config.DefaultBindingIP = d.defrouteIP
+	}
+
+	// Create and set network handler in driver
+	network := &bridgeNetwork{
+		id:         config.ID,
+		endpoints:  make(map[string]*bridgeEndpoint),
+		config:     config,
+		portMapper: portmapper.New(""),
+		driver:     d,
+	}
+
+	d.Lock()
+	d.networks[config.ID] = network
+	d.Unlock()
+
+	// On failure make sure to reset driver network handler to nil
+	defer func() {
+		if err != nil {
+			d.Lock()
+			delete(d.networks, config.ID)
+			d.Unlock()
+		}
+	}()
+
+	// Create or retrieve the bridge L3 interface
+	bridgeIface := newInterface(config)
+	network.bridge = bridgeIface
+
+	// Verify the network configuration does not conflict with previously installed
+	// networks. This step is needed now because driver might have now set the bridge
+	// name on this config struct. And because we need to check for possible address
+	// conflicts, so we need to check against operational networks.
+	if err = config.conflictsWithNetworks(config.ID, networkList); err != nil {
+		return err
+	}
+
+	// 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)
+	}
+
+	bridgeCleanup(config, false)
+	err = bridgeSetup(config)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func (d *driver) DeleteNetwork(nid string) error {
+	var err error
+	// Get network handler and remove it from driver
+	d.Lock()
+	n, ok := d.networks[nid]
+	d.Unlock()
+
+	if !ok {
+		return types.InternalMaskableErrorf("network %s does not exist", nid)
+	}
+	d.Lock()
+	delete(d.networks, nid)
+	d.Unlock()
+
+	// On failure set network handler back in driver, but
+	// only if is not already taken over by some other thread
+	defer func() {
+		if err != nil {
+			d.Lock()
+			if _, ok := d.networks[nid]; !ok {
+				d.networks[nid] = n
+			}
+			d.Unlock()
+		}
+	}()
+
+	// Sanity check
+	if n == nil {
+		err = driverapi.ErrNoNetwork(nid)
+		return err
+	}
+
+	// Cannot remove network if endpoints are still present
+	if len(n.endpoints) != 0 {
+		err = ActiveEndpointsError(n.id)
+		return err
+	}
+	bridgeCleanup(n.config, true)
+	logrus.Infof("Deleting bridge network: %s", nid[:12])
+	return d.storeDelete(n.config)
+}
+
+func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo, epOptions map[string]interface{}) error {
+	if ifInfo == nil {
+		return errors.New("invalid interface passed")
+	}
+
+	// Get the network handler and make sure it exists
+	d.Lock()
+	n, ok := d.networks[nid]
+	d.Unlock()
+
+	if !ok {
+		return types.NotFoundErrorf("network %s does not exist", nid)
+	}
+	if n == nil {
+		return driverapi.ErrNoNetwork(nid)
+	}
+
+	// Sanity check
+	n.Lock()
+	if n.id != nid {
+		n.Unlock()
+		return InvalidNetworkIDError(nid)
+	}
+	n.Unlock()
+
+	// Check if endpoint id is good and retrieve correspondent endpoint
+	ep, err := n.getEndpoint(eid)
+	if err != nil {
+		return err
+	}
+
+	// Endpoint with that id exists either on desired or other sandbox
+	if ep != nil {
+		return driverapi.ErrEndpointExists(eid)
+	}
+
+	// Try to convert the options to endpoint configuration
+	epConfig, err := parseEndpointOptions(epOptions)
+	if err != nil {
+		return err
+	}
+
+	// Create and add the endpoint
+	n.Lock()
+	endpoint := &bridgeEndpoint{id: eid, config: epConfig}
+	n.endpoints[eid] = endpoint
+	n.Unlock()
+
+	// On failure make sure to remove the endpoint
+	defer func() {
+		if err != nil {
+			n.Lock()
+			delete(n.endpoints, eid)
+			n.Unlock()
+		}
+	}()
+
+	// Create the sandbox side pipe interface
+	if ifInfo.MacAddress() == nil {
+		// No MAC address assigned to interface. Generate a random MAC to assign
+		endpoint.macAddress = netutils.GenerateRandomMAC()
+		if err := ifInfo.SetMacAddress(endpoint.macAddress); err != nil {
+			logrus.Warnf("Unable to set mac address: %s to endpoint: %s",
+				endpoint.macAddress.String(), endpoint.id)
+			return err
+		}
+	} else {
+		endpoint.macAddress = ifInfo.MacAddress()
+	}
+	endpoint.addr = ifInfo.Address()
+	endpoint.addrv6 = ifInfo.AddressIPv6()
+	c := n.config
+
+	// Program any required port mapping and store them in the endpoint
+	endpoint.portMapping, err = n.allocatePorts(endpoint, c.DefaultBindingIntf, c.DefaultBindingIP, true)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (d *driver) DeleteEndpoint(nid, eid string) error {
+	var err error
+
+	// Get the network handler and make sure it exists
+	d.Lock()
+	n, ok := d.networks[nid]
+	d.Unlock()
+
+	if !ok {
+		return types.InternalMaskableErrorf("network %s does not exist", nid)
+	}
+	if n == nil {
+		return driverapi.ErrNoNetwork(nid)
+	}
+
+	// Sanity Check
+	n.Lock()
+	if n.id != nid {
+		n.Unlock()
+		return InvalidNetworkIDError(nid)
+	}
+	n.Unlock()
+
+	// Check endpoint id and if an endpoint is actually there
+	ep, err := n.getEndpoint(eid)
+	if err != nil {
+		return err
+	}
+	if ep == nil {
+		return EndpointNotFoundError(eid)
+	}
+
+	// Remove it
+	n.Lock()
+	delete(n.endpoints, eid)
+	n.Unlock()
+
+	// On failure make sure to set back ep in n.endpoints, but only
+	// if it hasn't been taken over already by some other thread.
+	defer func() {
+		if err != nil {
+			n.Lock()
+			if _, ok := n.endpoints[eid]; !ok {
+				n.endpoints[eid] = ep
+			}
+			n.Unlock()
+		}
+	}()
+
+	err = n.releasePorts(ep)
+	if err != nil {
+		logrus.Warn(err)
+	}
+
+	return nil
+}
+
+func (d *driver) EndpointOperInfo(nid, eid string) (map[string]interface{}, error) {
+	// Get the network handler and make sure it exists
+	d.Lock()
+	n, ok := d.networks[nid]
+	d.Unlock()
+	if !ok {
+		return nil, types.NotFoundErrorf("network %s does not exist", nid)
+	}
+	if n == nil {
+		return nil, driverapi.ErrNoNetwork(nid)
+	}
+
+	// Sanity check
+	n.Lock()
+	if n.id != nid {
+		n.Unlock()
+		return nil, InvalidNetworkIDError(nid)
+	}
+	n.Unlock()
+
+	// Check if endpoint id is good and retrieve correspondent endpoint
+	ep, err := n.getEndpoint(eid)
+	if err != nil {
+		return nil, err
+	}
+	if ep == nil {
+		return nil, driverapi.ErrNoEndpoint(eid)
+	}
+
+	m := make(map[string]interface{})
+
+	if ep.extConnConfig != nil && ep.extConnConfig.ExposedPorts != nil {
+		// Return a copy of the config data
+		epc := make([]types.TransportPort, 0, len(ep.extConnConfig.ExposedPorts))
+		for _, tp := range ep.extConnConfig.ExposedPorts {
+			epc = append(epc, tp.GetCopy())
+		}
+		m[netlabel.ExposedPorts] = epc
+	}
+
+	if ep.portMapping != nil {
+		// Return a copy of the operational data
+		pmc := make([]types.PortBinding, 0, len(ep.portMapping))
+		for _, pm := range ep.portMapping {
+			pmc = append(pmc, pm.GetCopy())
+		}
+		m[netlabel.PortMap] = pmc
+	}
+
+	if len(ep.macAddress) != 0 {
+		m[netlabel.MacAddress] = ep.macAddress
+	}
+	return m, 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 {
+	network, err := d.getNetwork(nid)
+	if err != nil {
+		return err
+	}
+
+	endpoint, err := network.getEndpoint(eid)
+	if err != nil {
+		return err
+	}
+
+	if endpoint == nil {
+		return EndpointNotFoundError(eid)
+	}
+
+	endpoint.containerConfig, err = parseContainerOptions(options)
+	if err != nil {
+		return err
+	}
+
+	err = jinfo.SetGateway(network.bridge.gatewayIPv4)
+	if err != nil {
+		return err
+	}
+
+	err = jinfo.SetGatewayIPv6(network.bridge.gatewayIPv6)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (d *driver) link(network *bridgeNetwork, endpoint *bridgeEndpoint, enable bool) error {
+	return nil
+}
+
+// Leave method is invoked when a Sandbox detaches from an endpoint.
+func (d *driver) Leave(nid, eid string) error {
+	network, err := d.getNetwork(nid)
+	if err != nil {
+		return types.InternalMaskableErrorf("%s", err)
+	}
+
+	endpoint, err := network.getEndpoint(eid)
+	if err != nil {
+		return err
+	}
+
+	if endpoint == nil {
+		return EndpointNotFoundError(eid)
+	}
+
+	return nil
+}
+
+func (d *driver) ProgramExternalConnectivity(nid, eid string, options map[string]interface{}) error {
+	network, err := d.getNetwork(nid)
+	if err != nil {
+		return err
+	}
+
+	endpoint, err := network.getEndpoint(eid)
+	if err != nil {
+		return err
+	}
+
+	if endpoint == nil {
+		return EndpointNotFoundError(eid)
+	}
+
+	endpoint.extConnConfig, err = parseConnectivityOptions(options)
+	if err != nil {
+		return err
+	}
+
+	// Program any required port mapping and store them in the endpoint
+	endpoint.portMapping, err = network.allocatePorts(endpoint, network.config.DefaultBindingIntf, network.config.DefaultBindingIP, true)
+	if err != nil {
+		return err
+	}
+
+	if !network.config.EnableICC {
+		return d.link(network, endpoint, true)
+	}
+
+	return nil
+}
+
+func (d *driver) RevokeExternalConnectivity(nid, eid string) error {
+	network, err := d.getNetwork(nid)
+	if err != nil {
+		return err
+	}
+
+	endpoint, err := network.getEndpoint(eid)
+	if err != nil {
+		return err
+	}
+
+	if endpoint == nil {
+		return EndpointNotFoundError(eid)
+	}
+
+	err = network.releasePorts(endpoint)
+	if err != nil {
+		logrus.Warn(err)
+	}
+
+	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 discoverapi.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 discoverapi.DiscoveryType, data interface{}) error {
+	return nil
+}
+
+// Validate performs a static validation on the network configuration parameters.
+// Whatever can be assessed a priori before attempting any programming.
+func (c *networkConfiguration) Validate() error {
+	if c.Mtu < 0 {
+		return ErrInvalidMtu(c.Mtu)
+	}
+
+	// If bridge v4 subnet is specified
+	if c.AddressIPv4 != nil {
+		// If default gw is specified, it must be part of bridge subnet
+		if c.DefaultGatewayIPv4 != nil {
+			if !c.AddressIPv4.Contains(c.DefaultGatewayIPv4) {
+				return &ErrInvalidGateway{}
+			}
+		}
+	}
+
+	// If default v6 gw is specified, AddressIPv6 must be specified and gw must belong to AddressIPv6 subnet
+	if c.EnableIPv6 && c.DefaultGatewayIPv6 != nil {
+		if c.AddressIPv6 == nil || !c.AddressIPv6.Contains(c.DefaultGatewayIPv6) {
+			return &ErrInvalidGateway{}
+		}
+	}
+	return nil
+}
+
+// Checks whether this network's configuration for the network with this id conflicts with any of the passed networks
+func (c *networkConfiguration) conflictsWithNetworks(id string, others []*bridgeNetwork) error {
+	for _, nw := range others {
+
+		nw.Lock()
+		nwID := nw.id
+		nwConfig := nw.config
+		nwBridge := nw.bridge
+		nw.Unlock()
+
+		if nwID == id {
+			continue
+		}
+		// Verify the name (which may have been set by newInterface()) does not conflict with
+		// existing bridge interfaces. Ironically the system chosen name gets stored in the config...
+		// Basically we are checking if the two original configs were both empty.
+		if nwConfig.BridgeName == c.BridgeName {
+			return types.ForbiddenErrorf("conflicts with network %s (%s) by bridge name", nwID, nwConfig.BridgeName)
+		}
+		// If this network config specifies the AddressIPv4, we need
+		// to make sure it does not conflict with any previously allocated
+		// bridges. This could not be completely caught by the config conflict
+		// check, because networks which config does not specify the AddressIPv4
+		// get their address and subnet selected by the driver (see electBridgeIPv4())
+		if c.AddressIPv4 != nil {
+			if nwBridge.bridgeIPv4.Contains(c.AddressIPv4.IP) ||
+				c.AddressIPv4.Contains(nwBridge.bridgeIPv4.IP) {
+				return types.ForbiddenErrorf("conflicts with network %s (%s) by ip network", nwID, nwConfig.BridgeName)
+			}
+		}
+	}
+
+	return nil
+}
+
+// Conflicts check if two NetworkConfiguration objects overlap
+func (c *networkConfiguration) Conflicts(o *networkConfiguration) error {
+	if o == nil {
+		return fmt.Errorf("same configuration")
+	}
+
+	// Also empty, becasue only one network with empty name is allowed
+	if c.BridgeName == o.BridgeName {
+		return fmt.Errorf("networks have same bridge name")
+	}
+
+	// They must be in different subnets
+	if (c.AddressIPv4 != nil && o.AddressIPv4 != nil) &&
+		(c.AddressIPv4.Contains(o.AddressIPv4.IP) || o.AddressIPv4.Contains(c.AddressIPv4.IP)) {
+		return fmt.Errorf("networks have overlapping IPv4")
+	}
+
+	// They must be in different v6 subnets
+	if (c.AddressIPv6 != nil && o.AddressIPv6 != nil) &&
+		(c.AddressIPv6.Contains(o.AddressIPv6.IP) || o.AddressIPv6.Contains(c.AddressIPv6.IP)) {
+		return fmt.Errorf("networks have overlapping IPv6")
+	}
+
+	return nil
+}
+
+func (c *networkConfiguration) fromLabels(labels map[string]string) error {
+	var err error
+	for label, value := range labels {
+		switch label {
+		case BridgeName:
+			c.BridgeName = value
+		case netlabel.DriverMTU:
+			if c.Mtu, err = strconv.Atoi(value); err != nil {
+				return parseErr(label, value, err.Error())
+			}
+		case netlabel.EnableIPv6:
+			if c.EnableIPv6, err = strconv.ParseBool(value); err != nil {
+				return parseErr(label, value, err.Error())
+			}
+		case EnableIPMasquerade:
+			if c.EnableIPMasquerade, err = strconv.ParseBool(value); err != nil {
+				return parseErr(label, value, err.Error())
+			}
+		case EnableICC:
+			if c.EnableICC, err = strconv.ParseBool(value); err != nil {
+				return parseErr(label, value, err.Error())
+			}
+		case DefaultBridge:
+			if c.DefaultBridge, err = strconv.ParseBool(value); err != nil {
+				return parseErr(label, value, err.Error())
+			}
+		case DefaultBindingIP:
+			if c.DefaultBindingIP = net.ParseIP(value); c.DefaultBindingIP == nil {
+				return parseErr(label, value, "nil ip")
+			}
+		}
+	}
+
+	return nil
+}
+
+func parseErr(label, value, errString string) error {
+	return types.BadRequestErrorf("failed to parse %s value: %v (%s)", label, value, errString)
+}
+
+func parseNetworkGenericOptions(data interface{}) (*networkConfiguration, error) {
+	var (
+		err    error
+		config *networkConfiguration
+	)
+
+	switch opt := data.(type) {
+	case *networkConfiguration:
+		config = opt
+	case map[string]string:
+		config = &networkConfiguration{
+			EnableICC:          true,
+			EnableIPMasquerade: true,
+		}
+		err = config.fromLabels(opt)
+	case options.Generic:
+		var opaqueConfig interface{}
+		if opaqueConfig, err = options.GenerateFromModel(opt, config); err == nil {
+			config = opaqueConfig.(*networkConfiguration)
+		}
+	default:
+		err = types.BadRequestErrorf("do not recognize network configuration format: %T", opt)
+	}
+
+	return config, err
+}
+
+func parseNetworkOptions(d *driver, id string, option options.Generic) (*networkConfiguration, error) {
+	var (
+		err    error
+		config = &networkConfiguration{}
+	)
+
+	// Parse generic label first, config will be re-assigned
+	if genData, ok := option[netlabel.GenericData]; ok && genData != nil {
+		if config, err = parseNetworkGenericOptions(genData); err != nil {
+			return nil, err
+		}
+	}
+
+	// Process well-known labels next
+	if val, ok := option[netlabel.EnableIPv6]; ok {
+		config.EnableIPv6 = val.(bool)
+	}
+
+	if val, ok := option[netlabel.Internal]; ok {
+		if internal, ok := val.(bool); ok && internal {
+			config.Internal = true
+		}
+	}
+
+	// Finally validate the configuration
+	if err = config.Validate(); err != nil {
+		return nil, err
+	}
+
+	if config.BridgeName == "" && config.DefaultBridge == false {
+		config.BridgeName = "br_" + id[:12] + "_0"
+	}
+
+	lastChar := config.BridgeName[len(config.BridgeName)-1:]
+	if _, err = strconv.Atoi(lastChar); err != nil {
+		config.BridgeNameInternal = config.BridgeName + "_0"
+	} else {
+		config.BridgeNameInternal = config.BridgeName
+	}
+
+	config.ID = id
+	return config, nil
+}
+
+func (c *networkConfiguration) processIPAM(id string, ipamV4Data, ipamV6Data []driverapi.IPAMData) error {
+	if len(ipamV4Data) > 1 || len(ipamV6Data) > 1 {
+		return types.ForbiddenErrorf("bridge driver doesnt support multiple subnets")
+	}
+
+	if len(ipamV4Data) == 0 {
+		return types.BadRequestErrorf("bridge network %s requires ipv4 configuration", id)
+	}
+
+	if ipamV4Data[0].Gateway != nil {
+		c.AddressIPv4 = types.GetIPNetCopy(ipamV4Data[0].Gateway)
+	}
+
+	if gw, ok := ipamV4Data[0].AuxAddresses[DefaultGatewayV4AuxKey]; ok {
+		c.DefaultGatewayIPv4 = gw.IP
+	}
+
+	if len(ipamV6Data) > 0 {
+		c.AddressIPv6 = ipamV6Data[0].Pool
+
+		if ipamV6Data[0].Gateway != nil {
+			c.AddressIPv6 = types.GetIPNetCopy(ipamV6Data[0].Gateway)
+		}
+
+		if gw, ok := ipamV6Data[0].AuxAddresses[DefaultGatewayV6AuxKey]; ok {
+			c.DefaultGatewayIPv6 = gw.IP
+		}
+	}
+
+	return nil
+}
+
+func (n *bridgeNetwork) getEndpoint(eid string) (*bridgeEndpoint, error) {
+	n.Lock()
+	defer n.Unlock()
+
+	if eid == "" {
+		return nil, InvalidEndpointIDError(eid)
+	}
+
+	if ep, ok := n.endpoints[eid]; ok {
+		return ep, nil
+	}
+
+	return nil, nil
+}
+
+func parseEndpointOptions(epOptions map[string]interface{}) (*endpointConfiguration, error) {
+	if epOptions == nil {
+		return nil, nil
+	}
+
+	ec := &endpointConfiguration{}
+
+	if opt, ok := epOptions[netlabel.MacAddress]; ok {
+		if mac, ok := opt.(net.HardwareAddr); ok {
+			ec.MacAddress = mac
+		} else {
+			return nil, &ErrInvalidEndpointConfig{}
+		}
+	}
+
+	if opt, ok := epOptions[netlabel.PortMap]; ok {
+		if bs, ok := opt.([]types.PortBinding); ok {
+			ec.PortBindings = bs
+		} else {
+			return nil, &ErrInvalidEndpointConfig{}
+		}
+	}
+
+	if opt, ok := epOptions[netlabel.ExposedPorts]; ok {
+		if ports, ok := opt.([]types.TransportPort); ok {
+			ec.ExposedPorts = ports
+		} else {
+			return nil, &ErrInvalidEndpointConfig{}
+		}
+	}
+
+	return ec, nil
+}
+
+func parseContainerOptions(cOptions map[string]interface{}) (*containerConfiguration, error) {
+	if cOptions == nil {
+		return nil, nil
+	}
+	genericData := cOptions[netlabel.GenericData]
+	if genericData == nil {
+		return nil, nil
+	}
+	switch opt := genericData.(type) {
+	case options.Generic:
+		opaqueConfig, err := options.GenerateFromModel(opt, &containerConfiguration{})
+		if err != nil {
+			return nil, err
+		}
+		return opaqueConfig.(*containerConfiguration), nil
+	case *containerConfiguration:
+		return opt, nil
+	default:
+		return nil, nil
+	}
+}
+
+func parseConnectivityOptions(cOptions map[string]interface{}) (*connectivityConfiguration, error) {
+	if cOptions == nil {
+		return nil, nil
+	}
+
+	cc := &connectivityConfiguration{}
+
+	if opt, ok := cOptions[netlabel.PortMap]; ok {
+		if pb, ok := opt.([]types.PortBinding); ok {
+			cc.PortBindings = pb
+		} else {
+			return nil, types.BadRequestErrorf("Invalid port mapping data in connectivity configuration: %v", opt)
+		}
+	}
+
+	if opt, ok := cOptions[netlabel.ExposedPorts]; ok {
+		if ports, ok := opt.([]types.TransportPort); ok {
+			cc.ExposedPorts = ports
+		} else {
+			return nil, types.BadRequestErrorf("Invalid exposed ports data in connectivity configuration: %v", opt)
+		}
+	}
+
+	return cc, nil
+}

+ 384 - 0
libnetwork/drivers/solaris/bridge/bridge_store.go

@@ -0,0 +1,384 @@
+// +build solaris
+
+package bridge
+
+import (
+	"encoding/json"
+	"fmt"
+	"net"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/docker/libnetwork/datastore"
+	"github.com/docker/libnetwork/discoverapi"
+	"github.com/docker/libnetwork/netlabel"
+	"github.com/docker/libnetwork/types"
+)
+
+const (
+	// network config prefix was not specific enough.
+	// To be backward compatible, need custom endpoint
+	// prefix with different root
+	bridgePrefix         = "bridge"
+	bridgeEndpointPrefix = "bridge-endpoint"
+)
+
+func (d *driver) initStore(option map[string]interface{}) error {
+	if data, ok := option[netlabel.LocalKVClient]; ok {
+		var err error
+		dsc, ok := data.(discoverapi.DatastoreConfigData)
+		if !ok {
+			return types.InternalErrorf("incorrect data in datastore configuration: %v", data)
+		}
+		d.store, err = datastore.NewDataStoreFromConfig(dsc)
+		if err != nil {
+			return types.InternalErrorf("bridge driver failed to initialize data store: %v", err)
+		}
+
+		err = d.populateNetworks()
+		if err != nil {
+			return err
+		}
+
+		err = d.populateEndpoints()
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (d *driver) populateNetworks() error {
+	kvol, err := d.store.List(datastore.Key(bridgePrefix), &networkConfiguration{})
+	if err != nil && err != datastore.ErrKeyNotFound {
+		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: %v", ncfg.ID, ncfg.BridgeName, err)
+		}
+		logrus.Debugf("Network (%s) restored", ncfg.ID[0:7])
+	}
+
+	return nil
+}
+
+func (d *driver) populateEndpoints() error {
+	kvol, err := d.store.List(datastore.Key(bridgeEndpointPrefix), &bridgeEndpoint{})
+	if err != nil && err != datastore.ErrKeyNotFound {
+		return fmt.Errorf("failed to get bridge endpoints from store: %v", err)
+	}
+
+	if err == datastore.ErrKeyNotFound {
+		return nil
+	}
+
+	for _, kvo := range kvol {
+		ep := kvo.(*bridgeEndpoint)
+		n, ok := d.networks[ep.nid]
+		if !ok {
+			logrus.Debugf("Network (%s) not found for restored bridge endpoint (%s)", ep.nid[0:7], ep.id[0:7])
+			logrus.Debugf("Deleting stale bridge endpoint (%s) from store", ep.nid[0:7])
+			if err := d.storeDelete(ep); err != nil {
+				logrus.Debugf("Failed to delete stale bridge endpoint (%s) from store", ep.nid[0:7])
+			}
+			continue
+		}
+		n.endpoints[ep.id] = ep
+		n.restorePortAllocations(ep)
+		logrus.Debugf("Endpoint (%s) restored to network (%s)", ep.id[0:7], ep.nid[0:7])
+	}
+
+	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["BridgeNameInternal"] = ncfg.BridgeNameInternal
+	nMap["EnableIPv6"] = ncfg.EnableIPv6
+	nMap["EnableIPMasquerade"] = ncfg.EnableIPMasquerade
+	nMap["EnableICC"] = ncfg.EnableICC
+	nMap["Mtu"] = ncfg.Mtu
+	nMap["Internal"] = ncfg.Internal
+	nMap["DefaultBridge"] = ncfg.DefaultBridge
+	nMap["DefaultBindingIP"] = ncfg.DefaultBindingIP.String()
+	nMap["DefaultBindingIntf"] = ncfg.DefaultBindingIntf
+	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.DefaultBindingIntf = nMap["DefaultBindingIntf"].(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.BridgeNameInternal = nMap["BridgeNameInternal"].(string)
+	ncfg.EnableIPv6 = nMap["EnableIPv6"].(bool)
+	ncfg.EnableIPMasquerade = nMap["EnableIPMasquerade"].(bool)
+	ncfg.EnableICC = nMap["EnableICC"].(bool)
+	ncfg.Mtu = int(nMap["Mtu"].(float64))
+	if v, ok := nMap["Internal"]; ok {
+		ncfg.Internal = v.(bool)
+	}
+
+	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 false
+}
+
+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
+}
+
+func (ep *bridgeEndpoint) MarshalJSON() ([]byte, error) {
+	epMap := make(map[string]interface{})
+	epMap["id"] = ep.id
+	epMap["nid"] = ep.nid
+	epMap["SrcName"] = ep.srcName
+	epMap["MacAddress"] = ep.macAddress.String()
+	epMap["Addr"] = ep.addr.String()
+	if ep.addrv6 != nil {
+		epMap["Addrv6"] = ep.addrv6.String()
+	}
+	epMap["Config"] = ep.config
+	epMap["ContainerConfig"] = ep.containerConfig
+	epMap["ExternalConnConfig"] = ep.extConnConfig
+	epMap["PortMapping"] = ep.portMapping
+
+	return json.Marshal(epMap)
+}
+
+func (ep *bridgeEndpoint) UnmarshalJSON(b []byte) error {
+	var (
+		err   error
+		epMap map[string]interface{}
+	)
+
+	if err = json.Unmarshal(b, &epMap); err != nil {
+		return fmt.Errorf("Failed to unmarshal to bridge endpoint: %v", err)
+	}
+
+	if v, ok := epMap["MacAddress"]; ok {
+		if ep.macAddress, err = net.ParseMAC(v.(string)); err != nil {
+			return types.InternalErrorf("failed to decode bridge endpoint MAC address (%s) after json unmarshal: %v", v.(string), err)
+		}
+	}
+	if v, ok := epMap["Addr"]; ok {
+		if ep.addr, err = types.ParseCIDR(v.(string)); err != nil {
+			return types.InternalErrorf("failed to decode bridge endpoint IPv4 address (%s) after json unmarshal: %v", v.(string), err)
+		}
+	}
+	if v, ok := epMap["Addrv6"]; ok {
+		if ep.addrv6, err = types.ParseCIDR(v.(string)); err != nil {
+			return types.InternalErrorf("failed to decode bridge endpoint IPv6 address (%s) after json unmarshal: %v", v.(string), err)
+		}
+	}
+	ep.id = epMap["id"].(string)
+	ep.nid = epMap["nid"].(string)
+	ep.srcName = epMap["SrcName"].(string)
+	d, _ := json.Marshal(epMap["Config"])
+	if err := json.Unmarshal(d, &ep.config); err != nil {
+		logrus.Warnf("Failed to decode endpoint config %v", err)
+	}
+	d, _ = json.Marshal(epMap["ContainerConfig"])
+	if err := json.Unmarshal(d, &ep.containerConfig); err != nil {
+		logrus.Warnf("Failed to decode endpoint container config %v", err)
+	}
+	d, _ = json.Marshal(epMap["ExternalConnConfig"])
+	if err := json.Unmarshal(d, &ep.extConnConfig); err != nil {
+		logrus.Warnf("Failed to decode endpoint external connectivity configuration %v", err)
+	}
+	d, _ = json.Marshal(epMap["PortMapping"])
+	if err := json.Unmarshal(d, &ep.portMapping); err != nil {
+		logrus.Warnf("Failed to decode endpoint port mapping %v", err)
+	}
+
+	return nil
+}
+
+func (ep *bridgeEndpoint) Key() []string {
+	return []string{bridgeEndpointPrefix, ep.id}
+}
+
+func (ep *bridgeEndpoint) KeyPrefix() []string {
+	return []string{bridgeEndpointPrefix}
+}
+
+func (ep *bridgeEndpoint) Value() []byte {
+	b, err := json.Marshal(ep)
+	if err != nil {
+		return nil
+	}
+	return b
+}
+
+func (ep *bridgeEndpoint) SetValue(value []byte) error {
+	return json.Unmarshal(value, ep)
+}
+
+func (ep *bridgeEndpoint) Index() uint64 {
+	return ep.dbIndex
+}
+
+func (ep *bridgeEndpoint) SetIndex(index uint64) {
+	ep.dbIndex = index
+	ep.dbExists = true
+}
+
+func (ep *bridgeEndpoint) Exists() bool {
+	return ep.dbExists
+}
+
+func (ep *bridgeEndpoint) Skip() bool {
+	return false
+}
+
+func (ep *bridgeEndpoint) New() datastore.KVObject {
+	return &bridgeEndpoint{}
+}
+
+func (ep *bridgeEndpoint) CopyTo(o datastore.KVObject) error {
+	dstEp := o.(*bridgeEndpoint)
+	*dstEp = *ep
+	return nil
+}
+
+func (ep *bridgeEndpoint) DataScope() string {
+	return datastore.LocalScope
+}
+
+func (n *bridgeNetwork) restorePortAllocations(ep *bridgeEndpoint) {
+	if ep.extConnConfig == nil ||
+		ep.extConnConfig.ExposedPorts == nil ||
+		ep.extConnConfig.PortBindings == nil {
+		return
+	}
+	tmp := ep.extConnConfig.PortBindings
+	ep.extConnConfig.PortBindings = ep.portMapping
+	_, err := n.allocatePorts(ep, n.config.DefaultBindingIntf, n.config.DefaultBindingIP, n.driver.config.EnableUserlandProxy)
+	if err != nil {
+		logrus.Warnf("Failed to reserve existing port mapping for endpoint %s:%v", ep.id[0:7], err)
+	}
+	ep.extConnConfig.PortBindings = tmp
+}

+ 675 - 0
libnetwork/drivers/solaris/bridge/bridge_test.go

@@ -0,0 +1,675 @@
+// +build solaris
+
+package bridge
+
+import (
+	"bytes"
+	"encoding/json"
+	"net"
+	"testing"
+
+	"github.com/docker/libnetwork/driverapi"
+	"github.com/docker/libnetwork/ipamutils"
+	"github.com/docker/libnetwork/netlabel"
+	"github.com/docker/libnetwork/netutils"
+	"github.com/docker/libnetwork/testutils"
+	"github.com/docker/libnetwork/types"
+)
+
+func init() {
+	ipamutils.InitNetworks()
+}
+
+func TestEndpointMarshalling(t *testing.T) {
+	ip1, _ := types.ParseCIDR("172.22.0.9/16")
+	ip2, _ := types.ParseCIDR("2001:db8::9")
+	mac, _ := net.ParseMAC("ac:bd:24:57:66:77")
+	e := &bridgeEndpoint{
+		id:         "d2c015a1fe5930650cbcd50493efba0500bcebd8ee1f4401a16319f8a567de33",
+		nid:        "ee33fbb43c323f1920b6b35a0101552ac22ede960d0e5245e9738bccc68b2415",
+		addr:       ip1,
+		addrv6:     ip2,
+		macAddress: mac,
+		srcName:    "veth123456",
+		config:     &endpointConfiguration{MacAddress: mac},
+		containerConfig: &containerConfiguration{
+			ParentEndpoints: []string{"one", "due", "three"},
+			ChildEndpoints:  []string{"four", "five", "six"},
+		},
+		extConnConfig: &connectivityConfiguration{
+			ExposedPorts: []types.TransportPort{
+				{
+					Proto: 6,
+					Port:  uint16(18),
+				},
+			},
+			PortBindings: []types.PortBinding{
+				{
+					Proto:       6,
+					IP:          net.ParseIP("17210.33.9.56"),
+					Port:        uint16(18),
+					HostPort:    uint16(3000),
+					HostPortEnd: uint16(14000),
+				},
+			},
+		},
+		portMapping: []types.PortBinding{
+			{
+				Proto:       17,
+				IP:          net.ParseIP("172.33.9.56"),
+				Port:        uint16(99),
+				HostIP:      net.ParseIP("10.10.100.2"),
+				HostPort:    uint16(9900),
+				HostPortEnd: uint16(10000),
+			},
+			{
+				Proto:       6,
+				IP:          net.ParseIP("171.33.9.56"),
+				Port:        uint16(55),
+				HostIP:      net.ParseIP("10.11.100.2"),
+				HostPort:    uint16(5500),
+				HostPortEnd: uint16(55000),
+			},
+		},
+	}
+
+	b, err := json.Marshal(e)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	ee := &bridgeEndpoint{}
+	err = json.Unmarshal(b, ee)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if e.id != ee.id || e.nid != ee.nid || e.srcName != ee.srcName || !bytes.Equal(e.macAddress, ee.macAddress) ||
+		!types.CompareIPNet(e.addr, ee.addr) || !types.CompareIPNet(e.addrv6, ee.addrv6) ||
+		!compareEpConfig(e.config, ee.config) ||
+		!compareContainerConfig(e.containerConfig, ee.containerConfig) ||
+		!compareConnConfig(e.extConnConfig, ee.extConnConfig) ||
+		!compareBindings(e.portMapping, ee.portMapping) {
+		t.Fatalf("JSON marsh/unmarsh failed.\nOriginal:\n%#v\nDecoded:\n%#v", e, ee)
+	}
+}
+
+func compareEpConfig(a, b *endpointConfiguration) bool {
+	if a == b {
+		return true
+	}
+	if a == nil || b == nil {
+		return false
+	}
+	return bytes.Equal(a.MacAddress, b.MacAddress)
+}
+
+func compareContainerConfig(a, b *containerConfiguration) bool {
+	if a == b {
+		return true
+	}
+	if a == nil || b == nil {
+		return false
+	}
+	if len(a.ParentEndpoints) != len(b.ParentEndpoints) ||
+		len(a.ChildEndpoints) != len(b.ChildEndpoints) {
+		return false
+	}
+	for i := 0; i < len(a.ParentEndpoints); i++ {
+		if a.ParentEndpoints[i] != b.ParentEndpoints[i] {
+			return false
+		}
+	}
+	for i := 0; i < len(a.ChildEndpoints); i++ {
+		if a.ChildEndpoints[i] != b.ChildEndpoints[i] {
+			return false
+		}
+	}
+	return true
+}
+
+func compareConnConfig(a, b *connectivityConfiguration) bool {
+	if a == b {
+		return true
+	}
+	if a == nil || b == nil {
+		return false
+	}
+	if len(a.ExposedPorts) != len(b.ExposedPorts) ||
+		len(a.PortBindings) != len(b.PortBindings) {
+		return false
+	}
+	for i := 0; i < len(a.ExposedPorts); i++ {
+		if !a.ExposedPorts[i].Equal(&b.ExposedPorts[i]) {
+			return false
+		}
+	}
+	for i := 0; i < len(a.PortBindings); i++ {
+		if !a.PortBindings[i].Equal(&b.PortBindings[i]) {
+			return false
+		}
+	}
+	return true
+}
+
+func compareBindings(a, b []types.PortBinding) bool {
+	if len(a) != len(b) {
+		return false
+	}
+	for i := 0; i < len(a); i++ {
+		if !a[i].Equal(&b[i]) {
+			return false
+		}
+	}
+	return true
+}
+
+func getIPv4Data(t *testing.T) []driverapi.IPAMData {
+	ipd := driverapi.IPAMData{AddressSpace: "full"}
+	nw, _, err := netutils.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")
+
+	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.EnableIPv6] = true
+	netOption[netlabel.GenericData] = &networkConfiguration{
+		BridgeName: DefaultBridgeName,
+	}
+
+	ipdList := []driverapi.IPAMData{
+		{
+			Pool:         bnw,
+			Gateway:      br,
+			AuxAddresses: map[string]*net.IPNet{DefaultGatewayV4AuxKey: defgw},
+		},
+	}
+	err := d.CreateNetwork("dummy", netOption, nil, 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) {
+	if !testutils.IsRunningInContainer() {
+		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, nil, getIPv4Data(t), nil); err != nil {
+		t.Fatalf("Failed to create bridge: %v", err)
+	}
+}
+
+func TestCreateFullOptionsLabels(t *testing.T) {
+	if !testutils.IsRunningInContainer() {
+		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 := "2001:db8:2600:2700:2800::/80"
+	gwV6s := "2001:db8:2600:2700:2800::25/80"
+	nwV6, _ := types.ParseCIDR(nwV6s)
+	gwV6, _ := types.ParseCIDR(gwV6s)
+
+	labels := map[string]string{
+		BridgeName:         DefaultBridgeName,
+		DefaultBridge:      "true",
+		EnableICC:          "true",
+		EnableIPMasquerade: "true",
+		DefaultBindingIP:   bndIPs,
+	}
+
+	netOption := make(map[string]interface{})
+	netOption[netlabel.EnableIPv6] = true
+	netOption[netlabel.GenericData] = labels
+
+	ipdList := getIPv4Data(t)
+	ipd6List := []driverapi.IPAMData{
+		{
+			Pool: nwV6,
+			AuxAddresses: map[string]*net.IPNet{
+				DefaultGatewayV6AuxKey: gwV6,
+			},
+		},
+	}
+
+	err := d.CreateNetwork("dummy", netOption, nil, 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)
+	}
+}
+
+func TestCreate(t *testing.T) {
+	if !testutils.IsRunningInContainer() {
+		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, nil, getIPv4Data(t), nil); err != nil {
+		t.Fatalf("Failed to create bridge: %v", err)
+	}
+
+	err := d.CreateNetwork("dummy", genericOption, nil, 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) {
+	if !testutils.IsRunningInContainer() {
+		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, nil, getIPv4Data(t), nil); err == nil {
+		t.Fatal("Bridge creation was expected to fail")
+	}
+}
+
+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) AddTableEntry(tableName string, key string, value []byte) error {
+	return nil
+}
+
+func (te *testEndpoint) DisableGatewayService() {}
+
+func TestQueryEndpointInfo(t *testing.T) {
+	testQueryEndpointInfo(t, true)
+}
+
+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, nil, ipdList, nil)
+	if err != nil {
+		t.Fatalf("Failed to create bridge: %v", err)
+	}
+
+	sbOptions := make(map[string]interface{})
+	sbOptions[netlabel.PortMap] = getPortMapping()
+
+	te := newTestEndpoint(ipdList[0].Pool, 11)
+	err = d.CreateEndpoint("net1", "ep1", te.Interface(), nil)
+	if err != nil {
+		t.Fatalf("Failed to create an endpoint : %s", err.Error())
+	}
+
+	err = d.Join("net1", "ep1", "sbox", te, sbOptions)
+	if err != nil {
+		t.Fatalf("Failed to join the endpoint: %v", err)
+	}
+
+	err = d.ProgramExternalConnectivity("net1", "ep1", sbOptions)
+	if err != nil {
+		t.Fatalf("Failed to program external connectivity: %v", err)
+	}
+
+	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")
+		}
+	}
+
+	err = d.RevokeExternalConnectivity("net1", "ep1")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// release host mapped ports
+	err = d.Leave("net1", "ep1")
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+func getExposedPorts() []types.TransportPort {
+	return []types.TransportPort{
+		{Proto: types.TCP, Port: uint16(5000)},
+		{Proto: types.UDP, Port: uint16(400)},
+		{Proto: types.TCP, Port: uint16(600)},
+	}
+}
+
+func getPortMapping() []types.PortBinding {
+	return []types.PortBinding{
+		{Proto: types.TCP, Port: uint16(230), HostPort: uint16(23000)},
+		{Proto: types.UDP, Port: uint16(200), HostPort: uint16(22000)},
+		{Proto: types.TCP, Port: uint16(120), HostPort: uint16(12000)},
+	}
+}
+
+func TestValidateConfig(t *testing.T) {
+	if !testutils.IsRunningInContainer() {
+		defer testutils.SetupTestOSContext(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:db8:ae:b004::/64")
+	c = networkConfiguration{
+		EnableIPv6:         true,
+		AddressIPv6:        v6nw,
+		DefaultGatewayIPv6: net.ParseIP("2001:db8:ac:b004::bad:a55"),
+	}
+	err = c.Validate()
+	if err == nil {
+		t.Fatalf("Failed to detect invalid v6 default gateway")
+	}
+
+	c.DefaultGatewayIPv6 = net.ParseIP("2001:db8: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")
+	}
+}

+ 119 - 0
libnetwork/drivers/solaris/bridge/errors.go

@@ -0,0 +1,119 @@
+package bridge
+
+import "fmt"
+
+// ErrInvalidEndpointConfig error is returned when an 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() {}
+
+// 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() {}
+
+// 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() {}
+
+// 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() {}
+
+// 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() {}
+
+// 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() {}

+ 225 - 0
libnetwork/drivers/solaris/bridge/port_mapping.go

@@ -0,0 +1,225 @@
+// +build solaris
+
+package bridge
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"net"
+	"os"
+	"os/exec"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/docker/libnetwork/types"
+)
+
+var (
+	defaultBindingIP = net.IPv4(0, 0, 0, 0)
+)
+
+const (
+	maxAllocatePortAttempts = 10
+)
+
+func addPFRules(epid, bindIntf string, bs []types.PortBinding) {
+	var id string
+
+	if len(epid) > 12 {
+		id = epid[:12]
+	} else {
+		id = epid
+	}
+
+	fname := "/var/lib/docker/network/files/pf." + id
+
+	f, err := os.OpenFile(fname,
+		os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
+	if err != nil {
+		logrus.Warnf("cannot open temp pf file")
+		return
+	}
+	for _, b := range bs {
+		r := fmt.Sprintf(
+			"pass in on %s proto %s from any to (%s) "+
+				"port %d rdr-to %s port %d\n", bindIntf,
+			b.Proto.String(), bindIntf, b.HostPort,
+			b.IP.String(), b.Port)
+		_, err = f.WriteString(r)
+		if err != nil {
+			logrus.Warnf("cannot write firewall rules to %s: %v", fname, err)
+		}
+	}
+	f.Close()
+
+	anchor := fmt.Sprintf("_auto/docker/ep%s", id)
+	err = exec.Command("/usr/sbin/pfctl", "-a", anchor, "-f", fname).Run()
+	if err != nil {
+		logrus.Warnf("failed to add firewall rules: %v", err)
+	}
+	os.Remove(fname)
+}
+
+func removePFRules(epid string) {
+	var id string
+
+	if len(epid) > 12 {
+		id = epid[:12]
+	} else {
+		id = epid
+	}
+
+	anchor := fmt.Sprintf("_auto/docker/ep%s", id)
+	err := exec.Command("/usr/sbin/pfctl", "-a", anchor, "-F", "all").Run()
+	if err != nil {
+		logrus.Warnf("failed to remove firewall rules: %v", err)
+	}
+}
+
+func (n *bridgeNetwork) allocatePorts(ep *bridgeEndpoint, bindIntf string, reqDefBindIP net.IP, ulPxyEnabled bool) ([]types.PortBinding, error) {
+	if ep.extConnConfig == nil || ep.extConnConfig.PortBindings == nil {
+		return nil, nil
+	}
+
+	defHostIP := defaultBindingIP
+	if reqDefBindIP != nil {
+		defHostIP = reqDefBindIP
+	}
+
+	bs, err := n.allocatePortsInternal(ep.extConnConfig.PortBindings, bindIntf, ep.addr.IP, defHostIP, ulPxyEnabled)
+	if err != nil {
+		return nil, err
+	}
+
+	// Add PF rules for port bindings, if any
+	if len(bs) > 0 {
+		addPFRules(ep.id, bindIntf, bs)
+	}
+
+	return bs, err
+}
+
+func (n *bridgeNetwork) allocatePortsInternal(bindings []types.PortBinding, bindIntf string, 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); 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) 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), false); 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 {
+	err := n.releasePortsInternal(ep.portMapping)
+	if err != nil {
+		return nil
+	}
+
+	// remove rules if there are any port mappings
+	if len(ep.portMapping) > 0 {
+		removePFRules(ep.id)
+	}
+
+	return nil
+
+}
+
+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)
+}

+ 9 - 1
libnetwork/drivers_solaris.go

@@ -1,5 +1,13 @@
 package libnetwork
 
+import (
+	"github.com/docker/libnetwork/drivers/null"
+	"github.com/docker/libnetwork/drivers/solaris/bridge"
+)
+
 func getInitializers() []initializer {
-	return []initializer{}
+	return []initializer{
+		{bridge.Init, "bridge"},
+		{null.Init, "null"},
+	}
 }

+ 995 - 0
libnetwork/libnetwork_linux_test.go

@@ -0,0 +1,995 @@
+package libnetwork_test
+
+import (
+	"bytes"
+	"encoding/json"
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"net"
+	"os"
+	"os/exec"
+	"runtime"
+	"strconv"
+	"strings"
+	"testing"
+
+	log "github.com/Sirupsen/logrus"
+	"github.com/docker/docker/pkg/reexec"
+	"github.com/docker/libnetwork"
+	"github.com/docker/libnetwork/ipamapi"
+	"github.com/docker/libnetwork/netlabel"
+	"github.com/docker/libnetwork/options"
+	"github.com/docker/libnetwork/osl"
+	"github.com/docker/libnetwork/testutils"
+	"github.com/docker/libnetwork/types"
+	"github.com/opencontainers/runc/libcontainer"
+	"github.com/opencontainers/runc/libcontainer/configs"
+	"github.com/vishvananda/netlink"
+	"github.com/vishvananda/netns"
+)
+
+func TestHost(t *testing.T) {
+	sbx1, err := controller.NewSandbox("host_c1",
+		libnetwork.OptionHostname("test1"),
+		libnetwork.OptionDomainname("docker.io"),
+		libnetwork.OptionExtraHost("web", "192.168.0.1"),
+		libnetwork.OptionUseDefaultSandbox())
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		if err := sbx1.Delete(); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	sbx2, err := controller.NewSandbox("host_c2",
+		libnetwork.OptionHostname("test2"),
+		libnetwork.OptionDomainname("docker.io"),
+		libnetwork.OptionExtraHost("web", "192.168.0.1"),
+		libnetwork.OptionUseDefaultSandbox())
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		if err := sbx2.Delete(); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	network, err := createTestNetwork("host", "testhost", options.Generic{}, nil, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	ep1, err := network.CreateEndpoint("testep1")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if err := ep1.Join(sbx1); err != nil {
+		t.Fatal(err)
+	}
+
+	ep2, err := network.CreateEndpoint("testep2")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if err := ep2.Join(sbx2); err != nil {
+		t.Fatal(err)
+	}
+
+	if err := ep1.Leave(sbx1); err != nil {
+		t.Fatal(err)
+	}
+
+	if err := ep2.Leave(sbx2); err != nil {
+		t.Fatal(err)
+	}
+
+	if err := ep1.Delete(false); err != nil {
+		t.Fatal(err)
+	}
+
+	if err := ep2.Delete(false); err != nil {
+		t.Fatal(err)
+	}
+
+	// Try to create another host endpoint and join/leave that.
+	cnt3, err := controller.NewSandbox("host_c3",
+		libnetwork.OptionHostname("test3"),
+		libnetwork.OptionDomainname("docker.io"),
+		libnetwork.OptionExtraHost("web", "192.168.0.1"),
+		libnetwork.OptionUseDefaultSandbox())
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		if err := cnt3.Delete(); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	ep3, err := network.CreateEndpoint("testep3")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if err := ep3.Join(sbx2); err != nil {
+		t.Fatal(err)
+	}
+
+	if err := ep3.Leave(sbx2); err != nil {
+		t.Fatal(err)
+	}
+
+	if err := ep3.Delete(false); err != nil {
+		t.Fatal(err)
+	}
+}
+
+// Testing IPV6 from MAC address
+func TestBridgeIpv6FromMac(t *testing.T) {
+	if !testutils.IsRunningInContainer() {
+		defer testutils.SetupTestOSContext(t)()
+	}
+
+	netOption := options.Generic{
+		netlabel.GenericData: options.Generic{
+			"BridgeName":         "testipv6mac",
+			"EnableICC":          true,
+			"EnableIPMasquerade": true,
+		},
+	}
+	ipamV4ConfList := []*libnetwork.IpamConf{{PreferredPool: "192.168.100.0/24", Gateway: "192.168.100.1"}}
+	ipamV6ConfList := []*libnetwork.IpamConf{{PreferredPool: "fe90::/64", Gateway: "fe90::22"}}
+
+	network, err := controller.NewNetwork(bridgeNetType, "testipv6mac", "",
+		libnetwork.NetworkOptionGeneric(netOption),
+		libnetwork.NetworkOptionEnableIPv6(true),
+		libnetwork.NetworkOptionIpam(ipamapi.DefaultIPAM, "", ipamV4ConfList, ipamV6ConfList, nil),
+		libnetwork.NetworkOptionDeferIPv6Alloc(true))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	mac := net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}
+	epOption := options.Generic{netlabel.MacAddress: mac}
+
+	ep, err := network.CreateEndpoint("testep", libnetwork.EndpointOptionGeneric(epOption))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	iface := ep.Info().Iface()
+	if !bytes.Equal(iface.MacAddress(), mac) {
+		t.Fatalf("Unexpected mac address: %v", iface.MacAddress())
+	}
+
+	ip, expIP, _ := net.ParseCIDR("fe90::aabb:ccdd:eeff/64")
+	expIP.IP = ip
+	if !types.CompareIPNet(expIP, iface.AddressIPv6()) {
+		t.Fatalf("Expected %v. Got: %v", expIP, iface.AddressIPv6())
+	}
+
+	if err := ep.Delete(false); err != nil {
+		t.Fatal(err)
+	}
+
+	if err := network.Delete(); err != nil {
+		t.Fatal(err)
+	}
+}
+
+func checkSandbox(t *testing.T, info libnetwork.EndpointInfo) {
+	key := info.Sandbox().Key()
+	sbNs, err := netns.GetFromPath(key)
+	if err != nil {
+		t.Fatalf("Failed to get network namespace path %q: %v", key, err)
+	}
+	defer sbNs.Close()
+
+	nh, err := netlink.NewHandleAt(sbNs)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	_, err = nh.LinkByName("eth0")
+	if err != nil {
+		t.Fatalf("Could not find the interface eth0 inside the sandbox: %v", err)
+	}
+
+	_, err = nh.LinkByName("eth1")
+	if err != nil {
+		t.Fatalf("Could not find the interface eth1 inside the sandbox: %v", err)
+	}
+}
+
+func TestEndpointJoin(t *testing.T) {
+	if !testutils.IsRunningInContainer() {
+		defer testutils.SetupTestOSContext(t)()
+	}
+
+	// Create network 1 and add 2 endpoint: ep11, ep12
+	netOption := options.Generic{
+		netlabel.GenericData: options.Generic{
+			"BridgeName":         "testnetwork1",
+			"EnableICC":          true,
+			"EnableIPMasquerade": true,
+		},
+	}
+	ipamV6ConfList := []*libnetwork.IpamConf{{PreferredPool: "fe90::/64", Gateway: "fe90::22"}}
+	n1, err := controller.NewNetwork(bridgeNetType, "testnetwork1", "",
+		libnetwork.NetworkOptionGeneric(netOption),
+		libnetwork.NetworkOptionEnableIPv6(true),
+		libnetwork.NetworkOptionIpam(ipamapi.DefaultIPAM, "", nil, ipamV6ConfList, nil),
+		libnetwork.NetworkOptionDeferIPv6Alloc(true))
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		if err := n1.Delete(); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	ep1, err := n1.CreateEndpoint("ep1")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		if err := ep1.Delete(false); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	// Validate if ep.Info() only gives me IP address info and not names and gateway during CreateEndpoint()
+	info := ep1.Info()
+	iface := info.Iface()
+	if iface.Address() != nil && iface.Address().IP.To4() == nil {
+		t.Fatalf("Invalid IP address returned: %v", iface.Address())
+	}
+	if iface.AddressIPv6() != nil && iface.AddressIPv6().IP == nil {
+		t.Fatalf("Invalid IPv6 address returned: %v", iface.Address())
+	}
+
+	if len(info.Gateway()) != 0 {
+		t.Fatalf("Expected empty gateway for an empty endpoint. Instead found a gateway: %v", info.Gateway())
+	}
+	if len(info.GatewayIPv6()) != 0 {
+		t.Fatalf("Expected empty gateway for an empty ipv6 endpoint. Instead found a gateway: %v", info.GatewayIPv6())
+	}
+
+	if info.Sandbox() != nil {
+		t.Fatalf("Expected an empty sandbox key for an empty endpoint. Instead found a non-empty sandbox key: %s", info.Sandbox().Key())
+	}
+
+	// test invalid joins
+	err = ep1.Join(nil)
+	if err == nil {
+		t.Fatalf("Expected to fail join with nil Sandbox")
+	}
+	if _, ok := err.(types.BadRequestError); !ok {
+		t.Fatalf("Unexpected error type returned: %T", err)
+	}
+
+	fsbx := &fakeSandbox{}
+	if err = ep1.Join(fsbx); err == nil {
+		t.Fatalf("Expected to fail join with invalid Sandbox")
+	}
+	if _, ok := err.(types.BadRequestError); !ok {
+		t.Fatalf("Unexpected error type returned: %T", err)
+	}
+
+	sb, err := controller.NewSandbox(containerID,
+		libnetwork.OptionHostname("test"),
+		libnetwork.OptionDomainname("docker.io"),
+		libnetwork.OptionExtraHost("web", "192.168.0.1"))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	defer func() {
+		if err := sb.Delete(); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	err = ep1.Join(sb)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		err = ep1.Leave(sb)
+		if err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	// Validate if ep.Info() only gives valid gateway and sandbox key after has container has joined.
+	info = ep1.Info()
+	if len(info.Gateway()) == 0 {
+		t.Fatalf("Expected a valid gateway for a joined endpoint. Instead found an invalid gateway: %v", info.Gateway())
+	}
+	if len(info.GatewayIPv6()) == 0 {
+		t.Fatalf("Expected a valid ipv6 gateway for a joined endpoint. Instead found an invalid gateway: %v", info.GatewayIPv6())
+	}
+
+	if info.Sandbox() == nil {
+		t.Fatalf("Expected an non-empty sandbox key for a joined endpoint. Instead found a empty sandbox key")
+	}
+
+	// Check endpoint provided container information
+	if ep1.Info().Sandbox().Key() != sb.Key() {
+		t.Fatalf("Endpoint Info returned unexpected sandbox key: %s", sb.Key())
+	}
+
+	// Attempt retrieval of endpoint interfaces statistics
+	stats, err := sb.Statistics()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if _, ok := stats["eth0"]; !ok {
+		t.Fatalf("Did not find eth0 statistics")
+	}
+
+	// Now test the container joining another network
+	n2, err := createTestNetwork(bridgeNetType, "testnetwork2",
+		options.Generic{
+			netlabel.GenericData: options.Generic{
+				"BridgeName": "testnetwork2",
+			},
+		}, nil, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		if err := n2.Delete(); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	ep2, err := n2.CreateEndpoint("ep2")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		if err := ep2.Delete(false); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	err = ep2.Join(sb)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		err = ep2.Leave(sb)
+		if err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	if ep1.Info().Sandbox().Key() != ep2.Info().Sandbox().Key() {
+		t.Fatalf("ep1 and ep2 returned different container sandbox key")
+	}
+
+	checkSandbox(t, info)
+}
+
+func TestExternalKey(t *testing.T) {
+	externalKeyTest(t, false)
+}
+
+func externalKeyTest(t *testing.T, reexec bool) {
+	if !testutils.IsRunningInContainer() {
+		defer testutils.SetupTestOSContext(t)()
+	}
+
+	n, err := createTestNetwork(bridgeNetType, "testnetwork", options.Generic{
+		netlabel.GenericData: options.Generic{
+			"BridgeName": "testnetwork",
+		},
+	}, nil, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		if err := n.Delete(); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	ep, err := n.CreateEndpoint("ep1")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		err = ep.Delete(false)
+		if err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	ep2, err := n.CreateEndpoint("ep2")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		err = ep2.Delete(false)
+		if err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	cnt, err := controller.NewSandbox(containerID,
+		libnetwork.OptionHostname("test"),
+		libnetwork.OptionDomainname("docker.io"),
+		libnetwork.OptionUseExternalKey(),
+		libnetwork.OptionExtraHost("web", "192.168.0.1"))
+	defer func() {
+		if err := cnt.Delete(); err != nil {
+			t.Fatal(err)
+		}
+		osl.GC()
+	}()
+
+	// Join endpoint to sandbox before SetKey
+	err = ep.Join(cnt)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		err = ep.Leave(cnt)
+		if err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	sbox := ep.Info().Sandbox()
+	if sbox == nil {
+		t.Fatalf("Expected to have a valid Sandbox")
+	}
+
+	if reexec {
+		err := reexecSetKey("this-must-fail", containerID, controller.ID())
+		if err == nil {
+			t.Fatalf("SetExternalKey must fail if the corresponding namespace is not created")
+		}
+	} else {
+		// Setting an non-existing key (namespace) must fail
+		if err := sbox.SetKey("this-must-fail"); err == nil {
+			t.Fatalf("Setkey must fail if the corresponding namespace is not created")
+		}
+	}
+
+	// Create a new OS sandbox using the osl API before using it in SetKey
+	if extOsBox, err := osl.NewSandbox("ValidKey", true, false); err != nil {
+		t.Fatalf("Failed to create new osl sandbox")
+	} else {
+		defer func() {
+			if err := extOsBox.Destroy(); err != nil {
+				log.Warnf("Failed to remove os sandbox: %v", err)
+			}
+		}()
+	}
+
+	if reexec {
+		err := reexecSetKey("ValidKey", containerID, controller.ID())
+		if err != nil {
+			t.Fatalf("SetExternalKey failed with %v", err)
+		}
+	} else {
+		if err := sbox.SetKey("ValidKey"); err != nil {
+			t.Fatalf("Setkey failed with %v", err)
+		}
+	}
+
+	// Join endpoint to sandbox after SetKey
+	err = ep2.Join(sbox)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		err = ep2.Leave(sbox)
+		if err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	if ep.Info().Sandbox().Key() != ep2.Info().Sandbox().Key() {
+		t.Fatalf("ep1 and ep2 returned different container sandbox key")
+	}
+
+	checkSandbox(t, ep.Info())
+}
+
+func reexecSetKey(key string, containerID string, controllerID string) error {
+	var (
+		state libcontainer.State
+		b     []byte
+		err   error
+	)
+
+	state.NamespacePaths = make(map[configs.NamespaceType]string)
+	state.NamespacePaths[configs.NamespaceType("NEWNET")] = key
+	if b, err = json.Marshal(state); err != nil {
+		return err
+	}
+	cmd := &exec.Cmd{
+		Path:   reexec.Self(),
+		Args:   append([]string{"libnetwork-setkey"}, containerID, controllerID),
+		Stdin:  strings.NewReader(string(b)),
+		Stdout: os.Stdout,
+		Stderr: os.Stderr,
+	}
+	return cmd.Run()
+}
+
+func TestEnableIPv6(t *testing.T) {
+	if !testutils.IsRunningInContainer() {
+		defer testutils.SetupTestOSContext(t)()
+	}
+
+	tmpResolvConf := []byte("search pommesfrites.fr\nnameserver 12.34.56.78\nnameserver 2001:4860:4860::8888\n")
+	expectedResolvConf := []byte("search pommesfrites.fr\nnameserver 127.0.0.11\nnameserver 2001:4860:4860::8888\noptions ndots:0\n")
+	//take a copy of resolv.conf for restoring after test completes
+	resolvConfSystem, err := ioutil.ReadFile("/etc/resolv.conf")
+	if err != nil {
+		t.Fatal(err)
+	}
+	//cleanup
+	defer func() {
+		if err := ioutil.WriteFile("/etc/resolv.conf", resolvConfSystem, 0644); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	netOption := options.Generic{
+		netlabel.EnableIPv6: true,
+		netlabel.GenericData: options.Generic{
+			"BridgeName": "testnetwork",
+		},
+	}
+	ipamV6ConfList := []*libnetwork.IpamConf{{PreferredPool: "fe99::/64", Gateway: "fe99::9"}}
+
+	n, err := createTestNetwork("bridge", "testnetwork", netOption, nil, ipamV6ConfList)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		if err := n.Delete(); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	ep1, err := n.CreateEndpoint("ep1")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if err := ioutil.WriteFile("/etc/resolv.conf", tmpResolvConf, 0644); err != nil {
+		t.Fatal(err)
+	}
+
+	resolvConfPath := "/tmp/libnetwork_test/resolv.conf"
+	defer os.Remove(resolvConfPath)
+
+	sb, err := controller.NewSandbox(containerID, libnetwork.OptionResolvConfPath(resolvConfPath))
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		if err := sb.Delete(); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	err = ep1.Join(sb)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	content, err := ioutil.ReadFile(resolvConfPath)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if !bytes.Equal(content, expectedResolvConf) {
+		t.Fatalf("Expected:\n%s\nGot:\n%s", string(expectedResolvConf), string(content))
+	}
+
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+func TestResolvConfHost(t *testing.T) {
+	if !testutils.IsRunningInContainer() {
+		defer testutils.SetupTestOSContext(t)()
+	}
+
+	tmpResolvConf := []byte("search localhost.net\nnameserver 127.0.0.1\nnameserver 2001:4860:4860::8888\n")
+
+	//take a copy of resolv.conf for restoring after test completes
+	resolvConfSystem, err := ioutil.ReadFile("/etc/resolv.conf")
+	if err != nil {
+		t.Fatal(err)
+	}
+	//cleanup
+	defer func() {
+		if err := ioutil.WriteFile("/etc/resolv.conf", resolvConfSystem, 0644); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	n, err := controller.NetworkByName("testhost")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	ep1, err := n.CreateEndpoint("ep1", libnetwork.CreateOptionDisableResolution())
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if err := ioutil.WriteFile("/etc/resolv.conf", tmpResolvConf, 0644); err != nil {
+		t.Fatal(err)
+	}
+
+	resolvConfPath := "/tmp/libnetwork_test/resolv.conf"
+	defer os.Remove(resolvConfPath)
+
+	sb, err := controller.NewSandbox(containerID,
+		libnetwork.OptionResolvConfPath(resolvConfPath),
+		libnetwork.OptionOriginResolvConfPath("/etc/resolv.conf"))
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		if err := sb.Delete(); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	err = ep1.Join(sb)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		err = ep1.Leave(sb)
+		if err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	finfo, err := os.Stat(resolvConfPath)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	fmode := (os.FileMode)(0644)
+	if finfo.Mode() != fmode {
+		t.Fatalf("Expected file mode %s, got %s", fmode.String(), finfo.Mode().String())
+	}
+
+	content, err := ioutil.ReadFile(resolvConfPath)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if !bytes.Equal(content, tmpResolvConf) {
+		t.Fatalf("Expected:\n%s\nGot:\n%s", string(tmpResolvConf), string(content))
+	}
+}
+
+func TestResolvConf(t *testing.T) {
+	if !testutils.IsRunningInContainer() {
+		defer testutils.SetupTestOSContext(t)()
+	}
+
+	tmpResolvConf1 := []byte("search pommesfrites.fr\nnameserver 12.34.56.78\nnameserver 2001:4860:4860::8888\n")
+	tmpResolvConf2 := []byte("search pommesfrites.fr\nnameserver 112.34.56.78\nnameserver 2001:4860:4860::8888\n")
+	expectedResolvConf1 := []byte("search pommesfrites.fr\nnameserver 127.0.0.11\noptions ndots:0\n")
+	tmpResolvConf3 := []byte("search pommesfrites.fr\nnameserver 113.34.56.78\n")
+
+	//take a copy of resolv.conf for restoring after test completes
+	resolvConfSystem, err := ioutil.ReadFile("/etc/resolv.conf")
+	if err != nil {
+		t.Fatal(err)
+	}
+	//cleanup
+	defer func() {
+		if err := ioutil.WriteFile("/etc/resolv.conf", resolvConfSystem, 0644); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	netOption := options.Generic{
+		netlabel.GenericData: options.Generic{
+			"BridgeName": "testnetwork",
+		},
+	}
+	n, err := createTestNetwork("bridge", "testnetwork", netOption, nil, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		if err := n.Delete(); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	ep, err := n.CreateEndpoint("ep")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if err := ioutil.WriteFile("/etc/resolv.conf", tmpResolvConf1, 0644); err != nil {
+		t.Fatal(err)
+	}
+
+	resolvConfPath := "/tmp/libnetwork_test/resolv.conf"
+	defer os.Remove(resolvConfPath)
+
+	sb1, err := controller.NewSandbox(containerID, libnetwork.OptionResolvConfPath(resolvConfPath))
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		if err := sb1.Delete(); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	err = ep.Join(sb1)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	finfo, err := os.Stat(resolvConfPath)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	fmode := (os.FileMode)(0644)
+	if finfo.Mode() != fmode {
+		t.Fatalf("Expected file mode %s, got %s", fmode.String(), finfo.Mode().String())
+	}
+
+	content, err := ioutil.ReadFile(resolvConfPath)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if !bytes.Equal(content, expectedResolvConf1) {
+		fmt.Printf("\n%v\n%v\n", expectedResolvConf1, content)
+		t.Fatalf("Expected:\n%s\nGot:\n%s", string(expectedResolvConf1), string(content))
+	}
+
+	err = ep.Leave(sb1)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if err := ioutil.WriteFile("/etc/resolv.conf", tmpResolvConf2, 0644); err != nil {
+		t.Fatal(err)
+	}
+
+	sb2, err := controller.NewSandbox(containerID+"_2", libnetwork.OptionResolvConfPath(resolvConfPath))
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		if err := sb2.Delete(); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	err = ep.Join(sb2)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	content, err = ioutil.ReadFile(resolvConfPath)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if !bytes.Equal(content, expectedResolvConf1) {
+		t.Fatalf("Expected:\n%s\nGot:\n%s", string(expectedResolvConf1), string(content))
+	}
+
+	if err := ioutil.WriteFile(resolvConfPath, tmpResolvConf3, 0644); err != nil {
+		t.Fatal(err)
+	}
+
+	err = ep.Leave(sb2)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	err = ep.Join(sb2)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	content, err = ioutil.ReadFile(resolvConfPath)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if !bytes.Equal(content, tmpResolvConf3) {
+		t.Fatalf("Expected:\n%s\nGot:\n%s", string(tmpResolvConf3), string(content))
+	}
+}
+
+func parallelJoin(t *testing.T, rc libnetwork.Sandbox, ep libnetwork.Endpoint, thrNumber int) {
+	debugf("J%d.", thrNumber)
+	var err error
+
+	sb := sboxes[thrNumber-1]
+	err = ep.Join(sb)
+
+	runtime.LockOSThread()
+	if err != nil {
+		if _, ok := err.(types.ForbiddenError); !ok {
+			t.Fatalf("thread %d: %v", thrNumber, err)
+		}
+		debugf("JE%d(%v).", thrNumber, err)
+	}
+	debugf("JD%d.", thrNumber)
+}
+
+func parallelLeave(t *testing.T, rc libnetwork.Sandbox, ep libnetwork.Endpoint, thrNumber int) {
+	debugf("L%d.", thrNumber)
+	var err error
+
+	sb := sboxes[thrNumber-1]
+
+	err = ep.Leave(sb)
+	runtime.LockOSThread()
+	if err != nil {
+		if _, ok := err.(types.ForbiddenError); !ok {
+			t.Fatalf("thread %d: %v", thrNumber, err)
+		}
+		debugf("LE%d(%v).", thrNumber, err)
+	}
+	debugf("LD%d.", thrNumber)
+}
+
+func runParallelTests(t *testing.T, thrNumber int) {
+	var (
+		ep  libnetwork.Endpoint
+		sb  libnetwork.Sandbox
+		err error
+	)
+
+	t.Parallel()
+
+	pTest := flag.Lookup("test.parallel")
+	if pTest == nil {
+		t.Skip("Skipped because test.parallel flag not set;")
+	}
+	numParallel, err := strconv.Atoi(pTest.Value.String())
+	if err != nil {
+		t.Fatal(err)
+	}
+	if numParallel < numThreads {
+		t.Skip("Skipped because t.parallel was less than ", numThreads)
+	}
+
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	if thrNumber == first {
+		createGlobalInstance(t)
+	}
+
+	if thrNumber != first {
+		select {
+		case <-start:
+		}
+
+		thrdone := make(chan struct{})
+		done <- thrdone
+		defer close(thrdone)
+
+		if thrNumber == last {
+			defer close(done)
+		}
+
+		err = netns.Set(testns)
+		if err != nil {
+			t.Fatal(err)
+		}
+	}
+	defer netns.Set(origns)
+
+	net1, err := controller.NetworkByName("testhost")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if net1 == nil {
+		t.Fatal("Could not find testhost")
+	}
+
+	net2, err := controller.NetworkByName("network2")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if net2 == nil {
+		t.Fatal("Could not find network2")
+	}
+
+	epName := fmt.Sprintf("pep%d", thrNumber)
+
+	if thrNumber == first {
+		ep, err = net1.EndpointByName(epName)
+	} else {
+		ep, err = net2.EndpointByName(epName)
+	}
+
+	if err != nil {
+		t.Fatal(err)
+	}
+	if ep == nil {
+		t.Fatal("Got nil ep with no error")
+	}
+
+	cid := fmt.Sprintf("%drace", thrNumber)
+	controller.WalkSandboxes(libnetwork.SandboxContainerWalker(&sb, cid))
+	if sb == nil {
+		t.Fatalf("Got nil sandbox for container: %s", cid)
+	}
+
+	for i := 0; i < iterCnt; i++ {
+		parallelJoin(t, sb, ep, thrNumber)
+		parallelLeave(t, sb, ep, thrNumber)
+	}
+
+	debugf("\n")
+
+	err = sb.Delete()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if thrNumber == first {
+		for thrdone := range done {
+			select {
+			case <-thrdone:
+			}
+		}
+
+		testns.Close()
+		if err := net2.Delete(); err != nil {
+			t.Fatal(err)
+		}
+	} else {
+		err = ep.Delete(false)
+		if err != nil {
+			t.Fatal(err)
+		}
+	}
+}
+
+func TestParallel1(t *testing.T) {
+	runParallelTests(t, 1)
+}
+
+func TestParallel2(t *testing.T) {
+	runParallelTests(t, 2)
+}
+
+func TestParallel3(t *testing.T) {
+	runParallelTests(t, 3)
+}
+
+func TestNullIpam(t *testing.T) {
+	_, err := controller.NewNetwork(bridgeNetType, "testnetworkinternal", "", libnetwork.NetworkOptionIpam(ipamapi.NullIPAM, "", nil, nil, nil))
+	if err == nil || err.Error() != "ipv4 pool is empty" {
+		t.Fatal("bridge network should complain empty pool")
+	}
+}

+ 7 - 983
libnetwork/libnetwork_test.go

@@ -1,19 +1,12 @@
 package libnetwork_test
 
 import (
-	"bytes"
-	"encoding/json"
-	"flag"
 	"fmt"
 	"io/ioutil"
 	"net"
 	"net/http"
 	"net/http/httptest"
 	"os"
-	"os/exec"
-	"runtime"
-	"strconv"
-	"strings"
 	"sync"
 	"testing"
 
@@ -27,12 +20,8 @@ import (
 	"github.com/docker/libnetwork/ipamapi"
 	"github.com/docker/libnetwork/netlabel"
 	"github.com/docker/libnetwork/options"
-	"github.com/docker/libnetwork/osl"
 	"github.com/docker/libnetwork/testutils"
 	"github.com/docker/libnetwork/types"
-	"github.com/opencontainers/runc/libcontainer"
-	"github.com/opencontainers/runc/libcontainer/configs"
-	"github.com/vishvananda/netlink"
 	"github.com/vishvananda/netns"
 )
 
@@ -151,107 +140,6 @@ func TestNull(t *testing.T) {
 	}
 }
 
-func TestHost(t *testing.T) {
-	sbx1, err := controller.NewSandbox("host_c1",
-		libnetwork.OptionHostname("test1"),
-		libnetwork.OptionDomainname("docker.io"),
-		libnetwork.OptionExtraHost("web", "192.168.0.1"),
-		libnetwork.OptionUseDefaultSandbox())
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer func() {
-		if err := sbx1.Delete(); err != nil {
-			t.Fatal(err)
-		}
-	}()
-
-	sbx2, err := controller.NewSandbox("host_c2",
-		libnetwork.OptionHostname("test2"),
-		libnetwork.OptionDomainname("docker.io"),
-		libnetwork.OptionExtraHost("web", "192.168.0.1"),
-		libnetwork.OptionUseDefaultSandbox())
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer func() {
-		if err := sbx2.Delete(); err != nil {
-			t.Fatal(err)
-		}
-	}()
-
-	network, err := createTestNetwork("host", "testhost", options.Generic{}, nil, nil)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	ep1, err := network.CreateEndpoint("testep1")
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	if err := ep1.Join(sbx1); err != nil {
-		t.Fatal(err)
-	}
-
-	ep2, err := network.CreateEndpoint("testep2")
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	if err := ep2.Join(sbx2); err != nil {
-		t.Fatal(err)
-	}
-
-	if err := ep1.Leave(sbx1); err != nil {
-		t.Fatal(err)
-	}
-
-	if err := ep2.Leave(sbx2); err != nil {
-		t.Fatal(err)
-	}
-
-	if err := ep1.Delete(false); err != nil {
-		t.Fatal(err)
-	}
-
-	if err := ep2.Delete(false); err != nil {
-		t.Fatal(err)
-	}
-
-	// Try to create another host endpoint and join/leave that.
-	cnt3, err := controller.NewSandbox("host_c3",
-		libnetwork.OptionHostname("test3"),
-		libnetwork.OptionDomainname("docker.io"),
-		libnetwork.OptionExtraHost("web", "192.168.0.1"),
-		libnetwork.OptionUseDefaultSandbox())
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer func() {
-		if err := cnt3.Delete(); err != nil {
-			t.Fatal(err)
-		}
-	}()
-
-	ep3, err := network.CreateEndpoint("testep3")
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	if err := ep3.Join(sbx2); err != nil {
-		t.Fatal(err)
-	}
-
-	if err := ep3.Leave(sbx2); err != nil {
-		t.Fatal(err)
-	}
-
-	if err := ep3.Delete(false); err != nil {
-		t.Fatal(err)
-	}
-}
-
 func TestBridge(t *testing.T) {
 	if !testutils.IsRunningInContainer() {
 		defer testutils.SetupTestOSContext(t)()
@@ -315,59 +203,6 @@ func TestBridge(t *testing.T) {
 	}
 }
 
-// Testing IPV6 from MAC address
-func TestBridgeIpv6FromMac(t *testing.T) {
-	if !testutils.IsRunningInContainer() {
-		defer testutils.SetupTestOSContext(t)()
-	}
-
-	netOption := options.Generic{
-		netlabel.GenericData: options.Generic{
-			"BridgeName":         "testipv6mac",
-			"EnableICC":          true,
-			"EnableIPMasquerade": true,
-		},
-	}
-	ipamV4ConfList := []*libnetwork.IpamConf{{PreferredPool: "192.168.100.0/24", Gateway: "192.168.100.1"}}
-	ipamV6ConfList := []*libnetwork.IpamConf{{PreferredPool: "fe90::/64", Gateway: "fe90::22"}}
-
-	network, err := controller.NewNetwork(bridgeNetType, "testipv6mac", "",
-		libnetwork.NetworkOptionGeneric(netOption),
-		libnetwork.NetworkOptionEnableIPv6(true),
-		libnetwork.NetworkOptionIpam(ipamapi.DefaultIPAM, "", ipamV4ConfList, ipamV6ConfList, nil),
-		libnetwork.NetworkOptionDeferIPv6Alloc(true))
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	mac := net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}
-	epOption := options.Generic{netlabel.MacAddress: mac}
-
-	ep, err := network.CreateEndpoint("testep", libnetwork.EndpointOptionGeneric(epOption))
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	iface := ep.Info().Iface()
-	if !bytes.Equal(iface.MacAddress(), mac) {
-		t.Fatalf("Unexpected mac address: %v", iface.MacAddress())
-	}
-
-	ip, expIP, _ := net.ParseCIDR("fe90::aabb:ccdd:eeff/64")
-	expIP.IP = ip
-	if !types.CompareIPNet(expIP, iface.AddressIPv6()) {
-		t.Fatalf("Expected %v. Got: %v", expIP, iface.AddressIPv6())
-	}
-
-	if err := ep.Delete(false); err != nil {
-		t.Fatal(err)
-	}
-
-	if err := network.Delete(); err != nil {
-		t.Fatal(err)
-	}
-}
-
 func TestUnknownDriver(t *testing.T) {
 	if !testutils.IsRunningInContainer() {
 		defer testutils.SetupTestOSContext(t)()
@@ -966,202 +801,6 @@ func TestNetworkQuery(t *testing.T) {
 
 const containerID = "valid_c"
 
-func checkSandbox(t *testing.T, info libnetwork.EndpointInfo) {
-	key := info.Sandbox().Key()
-	sbNs, err := netns.GetFromPath(key)
-	if err != nil {
-		t.Fatalf("Failed to get network namespace path %q: %v", key, err)
-	}
-	defer sbNs.Close()
-
-	nh, err := netlink.NewHandleAt(sbNs)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	_, err = nh.LinkByName("eth0")
-	if err != nil {
-		t.Fatalf("Could not find the interface eth0 inside the sandbox: %v", err)
-	}
-
-	_, err = nh.LinkByName("eth1")
-	if err != nil {
-		t.Fatalf("Could not find the interface eth1 inside the sandbox: %v", err)
-	}
-}
-
-func TestEndpointJoin(t *testing.T) {
-	if !testutils.IsRunningInContainer() {
-		defer testutils.SetupTestOSContext(t)()
-	}
-
-	// Create network 1 and add 2 endpoint: ep11, ep12
-	netOption := options.Generic{
-		netlabel.GenericData: options.Generic{
-			"BridgeName":         "testnetwork1",
-			"EnableICC":          true,
-			"EnableIPMasquerade": true,
-		},
-	}
-	ipamV6ConfList := []*libnetwork.IpamConf{{PreferredPool: "fe90::/64", Gateway: "fe90::22"}}
-	n1, err := controller.NewNetwork(bridgeNetType, "testnetwork1", "",
-		libnetwork.NetworkOptionGeneric(netOption),
-		libnetwork.NetworkOptionEnableIPv6(true),
-		libnetwork.NetworkOptionIpam(ipamapi.DefaultIPAM, "", nil, ipamV6ConfList, nil),
-		libnetwork.NetworkOptionDeferIPv6Alloc(true))
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer func() {
-		if err := n1.Delete(); err != nil {
-			t.Fatal(err)
-		}
-	}()
-
-	ep1, err := n1.CreateEndpoint("ep1")
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer func() {
-		if err := ep1.Delete(false); err != nil {
-			t.Fatal(err)
-		}
-	}()
-
-	// Validate if ep.Info() only gives me IP address info and not names and gateway during CreateEndpoint()
-	info := ep1.Info()
-	iface := info.Iface()
-	if iface.Address() != nil && iface.Address().IP.To4() == nil {
-		t.Fatalf("Invalid IP address returned: %v", iface.Address())
-	}
-	if iface.AddressIPv6() != nil && iface.AddressIPv6().IP == nil {
-		t.Fatalf("Invalid IPv6 address returned: %v", iface.Address())
-	}
-
-	if len(info.Gateway()) != 0 {
-		t.Fatalf("Expected empty gateway for an empty endpoint. Instead found a gateway: %v", info.Gateway())
-	}
-	if len(info.GatewayIPv6()) != 0 {
-		t.Fatalf("Expected empty gateway for an empty ipv6 endpoint. Instead found a gateway: %v", info.GatewayIPv6())
-	}
-
-	if info.Sandbox() != nil {
-		t.Fatalf("Expected an empty sandbox key for an empty endpoint. Instead found a non-empty sandbox key: %s", info.Sandbox().Key())
-	}
-
-	// test invalid joins
-	err = ep1.Join(nil)
-	if err == nil {
-		t.Fatalf("Expected to fail join with nil Sandbox")
-	}
-	if _, ok := err.(types.BadRequestError); !ok {
-		t.Fatalf("Unexpected error type returned: %T", err)
-	}
-
-	fsbx := &fakeSandbox{}
-	if err = ep1.Join(fsbx); err == nil {
-		t.Fatalf("Expected to fail join with invalid Sandbox")
-	}
-	if _, ok := err.(types.BadRequestError); !ok {
-		t.Fatalf("Unexpected error type returned: %T", err)
-	}
-
-	sb, err := controller.NewSandbox(containerID,
-		libnetwork.OptionHostname("test"),
-		libnetwork.OptionDomainname("docker.io"),
-		libnetwork.OptionExtraHost("web", "192.168.0.1"))
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	defer func() {
-		if err := sb.Delete(); err != nil {
-			t.Fatal(err)
-		}
-	}()
-
-	err = ep1.Join(sb)
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer func() {
-		err = ep1.Leave(sb)
-		if err != nil {
-			t.Fatal(err)
-		}
-	}()
-
-	// Validate if ep.Info() only gives valid gateway and sandbox key after has container has joined.
-	info = ep1.Info()
-	if len(info.Gateway()) == 0 {
-		t.Fatalf("Expected a valid gateway for a joined endpoint. Instead found an invalid gateway: %v", info.Gateway())
-	}
-	if len(info.GatewayIPv6()) == 0 {
-		t.Fatalf("Expected a valid ipv6 gateway for a joined endpoint. Instead found an invalid gateway: %v", info.GatewayIPv6())
-	}
-
-	if info.Sandbox() == nil {
-		t.Fatalf("Expected a non-empty sandbox key for a joined endpoint. Instead found an empty sandbox key")
-	}
-
-	// Check endpoint provided container information
-	if ep1.Info().Sandbox().Key() != sb.Key() {
-		t.Fatalf("Endpoint Info returned unexpected sandbox key: %s", sb.Key())
-	}
-
-	// Attempt retrieval of endpoint interfaces statistics
-	stats, err := sb.Statistics()
-	if err != nil {
-		t.Fatal(err)
-	}
-	if _, ok := stats["eth0"]; !ok {
-		t.Fatalf("Did not find eth0 statistics")
-	}
-
-	// Now test the container joining another network
-	n2, err := createTestNetwork(bridgeNetType, "testnetwork2",
-		options.Generic{
-			netlabel.GenericData: options.Generic{
-				"BridgeName": "testnetwork2",
-			},
-		}, nil, nil)
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer func() {
-		if err := n2.Delete(); err != nil {
-			t.Fatal(err)
-		}
-	}()
-
-	ep2, err := n2.CreateEndpoint("ep2")
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer func() {
-		if err := ep2.Delete(false); err != nil {
-			t.Fatal(err)
-		}
-	}()
-
-	err = ep2.Join(sb)
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer func() {
-		err = ep2.Leave(sb)
-		if err != nil {
-			t.Fatal(err)
-		}
-	}()
-
-	if ep1.Info().Sandbox().Key() != ep2.Info().Sandbox().Key() {
-		t.Fatalf("ep1 and ep2 returned different container sandbox key")
-	}
-
-	checkSandbox(t, info)
-}
-
 type fakeSandbox struct{}
 
 func (f *fakeSandbox) ID() string {
@@ -1216,11 +855,7 @@ func (f *fakeSandbox) Endpoints() []libnetwork.Endpoint {
 	return nil
 }
 
-func TestExternalKey(t *testing.T) {
-	externalKeyTest(t, false)
-}
-
-func externalKeyTest(t *testing.T, reexec bool) {
+func TestEndpointDeleteWithActiveContainer(t *testing.T) {
 	if !testutils.IsRunningInContainer() {
 		defer testutils.SetupTestOSContext(t)()
 	}
@@ -1250,30 +885,16 @@ func externalKeyTest(t *testing.T, reexec bool) {
 		}
 	}()
 
-	ep2, err := n.CreateEndpoint("ep2")
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer func() {
-		err = ep2.Delete(false)
-		if err != nil {
-			t.Fatal(err)
-		}
-	}()
-
 	cnt, err := controller.NewSandbox(containerID,
 		libnetwork.OptionHostname("test"),
 		libnetwork.OptionDomainname("docker.io"),
-		libnetwork.OptionUseExternalKey(),
 		libnetwork.OptionExtraHost("web", "192.168.0.1"))
 	defer func() {
 		if err := cnt.Delete(); err != nil {
 			t.Fatal(err)
 		}
-		osl.GC()
 	}()
 
-	// Join endpoint to sandbox before SetKey
 	err = ep.Join(cnt)
 	if err != nil {
 		t.Fatal(err)
@@ -1285,146 +906,15 @@ func externalKeyTest(t *testing.T, reexec bool) {
 		}
 	}()
 
-	sbox := ep.Info().Sandbox()
-	if sbox == nil {
-		t.Fatalf("Expected to have a valid Sandbox")
+	err = ep.Delete(false)
+	if err == nil {
+		t.Fatal("Expected to fail. But instead succeeded")
 	}
 
-	if reexec {
-		err := reexecSetKey("this-must-fail", containerID, controller.ID())
-		if err == nil {
-			t.Fatalf("SetExternalKey must fail if the corresponding namespace is not created")
-		}
-	} else {
-		// Setting a non-existing key (namespace) must fail
-		if err := sbox.SetKey("this-must-fail"); err == nil {
-			t.Fatalf("Setkey must fail if the corresponding namespace is not created")
-		}
+	if _, ok := err.(*libnetwork.ActiveContainerError); !ok {
+		t.Fatalf("Did not fail with expected error. Actual error: %v", err)
 	}
-
-	// Create a new OS sandbox using the osl API before using it in SetKey
-	if extOsBox, err := osl.NewSandbox("ValidKey", true, false); err != nil {
-		t.Fatalf("Failed to create new osl sandbox")
-	} else {
-		defer func() {
-			if err := extOsBox.Destroy(); err != nil {
-				log.Warnf("Failed to remove os sandbox: %v", err)
-			}
-		}()
-	}
-
-	if reexec {
-		err := reexecSetKey("ValidKey", containerID, controller.ID())
-		if err != nil {
-			t.Fatalf("SetExternalKey failed with %v", err)
-		}
-	} else {
-		if err := sbox.SetKey("ValidKey"); err != nil {
-			t.Fatalf("Setkey failed with %v", err)
-		}
-	}
-
-	// Join endpoint to sandbox after SetKey
-	err = ep2.Join(sbox)
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer func() {
-		err = ep2.Leave(sbox)
-		if err != nil {
-			t.Fatal(err)
-		}
-	}()
-
-	if ep.Info().Sandbox().Key() != ep2.Info().Sandbox().Key() {
-		t.Fatalf("ep1 and ep2 returned different container sandbox key")
-	}
-
-	checkSandbox(t, ep.Info())
-}
-
-func reexecSetKey(key string, containerID string, controllerID string) error {
-	var (
-		state libcontainer.State
-		b     []byte
-		err   error
-	)
-
-	state.NamespacePaths = make(map[configs.NamespaceType]string)
-	state.NamespacePaths[configs.NamespaceType("NEWNET")] = key
-	if b, err = json.Marshal(state); err != nil {
-		return err
-	}
-	cmd := &exec.Cmd{
-		Path:   reexec.Self(),
-		Args:   append([]string{"libnetwork-setkey"}, containerID, controllerID),
-		Stdin:  strings.NewReader(string(b)),
-		Stdout: os.Stdout,
-		Stderr: os.Stderr,
-	}
-	return cmd.Run()
-}
-
-func TestEndpointDeleteWithActiveContainer(t *testing.T) {
-	if !testutils.IsRunningInContainer() {
-		defer testutils.SetupTestOSContext(t)()
-	}
-
-	n, err := createTestNetwork(bridgeNetType, "testnetwork", options.Generic{
-		netlabel.GenericData: options.Generic{
-			"BridgeName": "testnetwork",
-		},
-	}, nil, nil)
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer func() {
-		if err := n.Delete(); err != nil {
-			t.Fatal(err)
-		}
-	}()
-
-	ep, err := n.CreateEndpoint("ep1")
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer func() {
-		err = ep.Delete(false)
-		if err != nil {
-			t.Fatal(err)
-		}
-	}()
-
-	cnt, err := controller.NewSandbox(containerID,
-		libnetwork.OptionHostname("test"),
-		libnetwork.OptionDomainname("docker.io"),
-		libnetwork.OptionExtraHost("web", "192.168.0.1"))
-	defer func() {
-		if err := cnt.Delete(); err != nil {
-			t.Fatal(err)
-		}
-	}()
-
-	err = ep.Join(cnt)
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer func() {
-		err = ep.Leave(cnt)
-		if err != nil {
-			t.Fatal(err)
-		}
-	}()
-
-	err = ep.Delete(false)
-	if err == nil {
-		t.Fatal("Expected to fail. But instead succeeded")
-	}
-
-	if _, ok := err.(*libnetwork.ActiveContainerError); !ok {
-		t.Fatalf("Did not fail with expected error. Actual error: %v", err)
-	}
-}
+}
 
 func TestEndpointMultipleJoins(t *testing.T) {
 	if !testutils.IsRunningInContainer() {
@@ -1678,304 +1168,6 @@ func TestEndpointUpdateParent(t *testing.T) {
 	}
 }
 
-func TestEnableIPv6(t *testing.T) {
-	if !testutils.IsRunningInContainer() {
-		defer testutils.SetupTestOSContext(t)()
-	}
-
-	tmpResolvConf := []byte("search pommesfrites.fr\nnameserver 12.34.56.78\nnameserver 2001:4860:4860::8888\n")
-	expectedResolvConf := []byte("search pommesfrites.fr\nnameserver 127.0.0.11\nnameserver 2001:4860:4860::8888\noptions ndots:0\n")
-	//take a copy of resolv.conf for restoring after test completes
-	resolvConfSystem, err := ioutil.ReadFile("/etc/resolv.conf")
-	if err != nil {
-		t.Fatal(err)
-	}
-	//cleanup
-	defer func() {
-		if err := ioutil.WriteFile("/etc/resolv.conf", resolvConfSystem, 0644); err != nil {
-			t.Fatal(err)
-		}
-	}()
-
-	netOption := options.Generic{
-		netlabel.EnableIPv6: true,
-		netlabel.GenericData: options.Generic{
-			"BridgeName": "testnetwork",
-		},
-	}
-	ipamV6ConfList := []*libnetwork.IpamConf{{PreferredPool: "fe99::/64", Gateway: "fe99::9"}}
-
-	n, err := createTestNetwork("bridge", "testnetwork", netOption, nil, ipamV6ConfList)
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer func() {
-		if err := n.Delete(); err != nil {
-			t.Fatal(err)
-		}
-	}()
-
-	ep1, err := n.CreateEndpoint("ep1")
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	if err := ioutil.WriteFile("/etc/resolv.conf", tmpResolvConf, 0644); err != nil {
-		t.Fatal(err)
-	}
-
-	resolvConfPath := "/tmp/libnetwork_test/resolv.conf"
-	defer os.Remove(resolvConfPath)
-
-	sb, err := controller.NewSandbox(containerID, libnetwork.OptionResolvConfPath(resolvConfPath))
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer func() {
-		if err := sb.Delete(); err != nil {
-			t.Fatal(err)
-		}
-	}()
-
-	err = ep1.Join(sb)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	content, err := ioutil.ReadFile(resolvConfPath)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	if !bytes.Equal(content, expectedResolvConf) {
-		t.Fatalf("Expected:\n%s\nGot:\n%s", string(expectedResolvConf), string(content))
-	}
-
-	if err != nil {
-		t.Fatal(err)
-	}
-}
-
-func TestResolvConfHost(t *testing.T) {
-	if !testutils.IsRunningInContainer() {
-		defer testutils.SetupTestOSContext(t)()
-	}
-
-	tmpResolvConf := []byte("search localhost.net\nnameserver 127.0.0.1\nnameserver 2001:4860:4860::8888\n")
-
-	//take a copy of resolv.conf for restoring after test completes
-	resolvConfSystem, err := ioutil.ReadFile("/etc/resolv.conf")
-	if err != nil {
-		t.Fatal(err)
-	}
-	//cleanup
-	defer func() {
-		if err := ioutil.WriteFile("/etc/resolv.conf", resolvConfSystem, 0644); err != nil {
-			t.Fatal(err)
-		}
-	}()
-
-	n, err := controller.NetworkByName("testhost")
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	ep1, err := n.CreateEndpoint("ep1", libnetwork.CreateOptionDisableResolution())
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	if err := ioutil.WriteFile("/etc/resolv.conf", tmpResolvConf, 0644); err != nil {
-		t.Fatal(err)
-	}
-
-	resolvConfPath := "/tmp/libnetwork_test/resolv.conf"
-	defer os.Remove(resolvConfPath)
-
-	sb, err := controller.NewSandbox(containerID,
-		libnetwork.OptionResolvConfPath(resolvConfPath),
-		libnetwork.OptionOriginResolvConfPath("/etc/resolv.conf"))
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer func() {
-		if err := sb.Delete(); err != nil {
-			t.Fatal(err)
-		}
-	}()
-
-	err = ep1.Join(sb)
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer func() {
-		err = ep1.Leave(sb)
-		if err != nil {
-			t.Fatal(err)
-		}
-	}()
-
-	finfo, err := os.Stat(resolvConfPath)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	fmode := (os.FileMode)(0644)
-	if finfo.Mode() != fmode {
-		t.Fatalf("Expected file mode %s, got %s", fmode.String(), finfo.Mode().String())
-	}
-
-	content, err := ioutil.ReadFile(resolvConfPath)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	if !bytes.Equal(content, tmpResolvConf) {
-		t.Fatalf("Expected:\n%s\nGot:\n%s", string(tmpResolvConf), string(content))
-	}
-}
-
-func TestResolvConf(t *testing.T) {
-	if !testutils.IsRunningInContainer() {
-		defer testutils.SetupTestOSContext(t)()
-	}
-
-	tmpResolvConf1 := []byte("search pommesfrites.fr\nnameserver 12.34.56.78\nnameserver 2001:4860:4860::8888\n")
-	tmpResolvConf2 := []byte("search pommesfrites.fr\nnameserver 112.34.56.78\nnameserver 2001:4860:4860::8888\n")
-	expectedResolvConf1 := []byte("search pommesfrites.fr\nnameserver 127.0.0.11\noptions ndots:0\n")
-	tmpResolvConf3 := []byte("search pommesfrites.fr\nnameserver 113.34.56.78\n")
-
-	//take a copy of resolv.conf for restoring after test completes
-	resolvConfSystem, err := ioutil.ReadFile("/etc/resolv.conf")
-	if err != nil {
-		t.Fatal(err)
-	}
-	//cleanup
-	defer func() {
-		if err := ioutil.WriteFile("/etc/resolv.conf", resolvConfSystem, 0644); err != nil {
-			t.Fatal(err)
-		}
-	}()
-
-	netOption := options.Generic{
-		netlabel.GenericData: options.Generic{
-			"BridgeName": "testnetwork",
-		},
-	}
-	n, err := createTestNetwork("bridge", "testnetwork", netOption, nil, nil)
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer func() {
-		if err := n.Delete(); err != nil {
-			t.Fatal(err)
-		}
-	}()
-
-	ep, err := n.CreateEndpoint("ep")
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	if err := ioutil.WriteFile("/etc/resolv.conf", tmpResolvConf1, 0644); err != nil {
-		t.Fatal(err)
-	}
-
-	resolvConfPath := "/tmp/libnetwork_test/resolv.conf"
-	defer os.Remove(resolvConfPath)
-
-	sb1, err := controller.NewSandbox(containerID, libnetwork.OptionResolvConfPath(resolvConfPath))
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer func() {
-		if err := sb1.Delete(); err != nil {
-			t.Fatal(err)
-		}
-	}()
-
-	err = ep.Join(sb1)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	finfo, err := os.Stat(resolvConfPath)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	fmode := (os.FileMode)(0644)
-	if finfo.Mode() != fmode {
-		t.Fatalf("Expected file mode %s, got %s", fmode.String(), finfo.Mode().String())
-	}
-
-	content, err := ioutil.ReadFile(resolvConfPath)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	if !bytes.Equal(content, expectedResolvConf1) {
-		fmt.Printf("\n%v\n%v\n", expectedResolvConf1, content)
-		t.Fatalf("Expected:\n%s\nGot:\n%s", string(expectedResolvConf1), string(content))
-	}
-
-	err = ep.Leave(sb1)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	if err := ioutil.WriteFile("/etc/resolv.conf", tmpResolvConf2, 0644); err != nil {
-		t.Fatal(err)
-	}
-
-	sb2, err := controller.NewSandbox(containerID+"_2", libnetwork.OptionResolvConfPath(resolvConfPath))
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer func() {
-		if err := sb2.Delete(); err != nil {
-			t.Fatal(err)
-		}
-	}()
-
-	err = ep.Join(sb2)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	content, err = ioutil.ReadFile(resolvConfPath)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	if !bytes.Equal(content, expectedResolvConf1) {
-		t.Fatalf("Expected:\n%s\nGot:\n%s", string(expectedResolvConf1), string(content))
-	}
-
-	if err := ioutil.WriteFile(resolvConfPath, tmpResolvConf3, 0644); err != nil {
-		t.Fatal(err)
-	}
-
-	err = ep.Leave(sb2)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	err = ep.Join(sb2)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	content, err = ioutil.ReadFile(resolvConfPath)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	if !bytes.Equal(content, tmpResolvConf3) {
-		t.Fatalf("Expected:\n%s\nGot:\n%s", string(tmpResolvConf3), string(content))
-	}
-}
-
 func TestInvalidRemoteDriver(t *testing.T) {
 	if !testutils.IsRunningInContainer() {
 		t.Skip("Skipping test when not running inside a Container")
@@ -2164,171 +1356,3 @@ func debugf(format string, a ...interface{}) (int, error) {
 
 	return 0, nil
 }
-
-func parallelJoin(t *testing.T, rc libnetwork.Sandbox, ep libnetwork.Endpoint, thrNumber int) {
-	debugf("J%d.", thrNumber)
-	var err error
-
-	sb := sboxes[thrNumber-1]
-	err = ep.Join(sb)
-
-	runtime.LockOSThread()
-	if err != nil {
-		if _, ok := err.(types.ForbiddenError); !ok {
-			t.Fatalf("thread %d: %v", thrNumber, err)
-		}
-		debugf("JE%d(%v).", thrNumber, err)
-	}
-	debugf("JD%d.", thrNumber)
-}
-
-func parallelLeave(t *testing.T, rc libnetwork.Sandbox, ep libnetwork.Endpoint, thrNumber int) {
-	debugf("L%d.", thrNumber)
-	var err error
-
-	sb := sboxes[thrNumber-1]
-
-	err = ep.Leave(sb)
-	runtime.LockOSThread()
-	if err != nil {
-		if _, ok := err.(types.ForbiddenError); !ok {
-			t.Fatalf("thread %d: %v", thrNumber, err)
-		}
-		debugf("LE%d(%v).", thrNumber, err)
-	}
-	debugf("LD%d.", thrNumber)
-}
-
-func runParallelTests(t *testing.T, thrNumber int) {
-	var (
-		ep  libnetwork.Endpoint
-		sb  libnetwork.Sandbox
-		err error
-	)
-
-	t.Parallel()
-
-	pTest := flag.Lookup("test.parallel")
-	if pTest == nil {
-		t.Skip("Skipped because test.parallel flag not set;")
-	}
-	numParallel, err := strconv.Atoi(pTest.Value.String())
-	if err != nil {
-		t.Fatal(err)
-	}
-	if numParallel < numThreads {
-		t.Skip("Skipped because t.parallel was less than ", numThreads)
-	}
-
-	runtime.LockOSThread()
-	defer runtime.UnlockOSThread()
-
-	if thrNumber == first {
-		createGlobalInstance(t)
-	}
-
-	if thrNumber != first {
-		select {
-		case <-start:
-		}
-
-		thrdone := make(chan struct{})
-		done <- thrdone
-		defer close(thrdone)
-
-		if thrNumber == last {
-			defer close(done)
-		}
-
-		err = netns.Set(testns)
-		if err != nil {
-			t.Fatal(err)
-		}
-	}
-	defer netns.Set(origns)
-
-	net1, err := controller.NetworkByName("testhost")
-	if err != nil {
-		t.Fatal(err)
-	}
-	if net1 == nil {
-		t.Fatal("Could not find testhost")
-	}
-
-	net2, err := controller.NetworkByName("network2")
-	if err != nil {
-		t.Fatal(err)
-	}
-	if net2 == nil {
-		t.Fatal("Could not find network2")
-	}
-
-	epName := fmt.Sprintf("pep%d", thrNumber)
-
-	if thrNumber == first {
-		ep, err = net1.EndpointByName(epName)
-	} else {
-		ep, err = net2.EndpointByName(epName)
-	}
-
-	if err != nil {
-		t.Fatal(err)
-	}
-	if ep == nil {
-		t.Fatal("Got nil ep with no error")
-	}
-
-	cid := fmt.Sprintf("%drace", thrNumber)
-	controller.WalkSandboxes(libnetwork.SandboxContainerWalker(&sb, cid))
-	if sb == nil {
-		t.Fatalf("Got nil sandbox for container: %s", cid)
-	}
-
-	for i := 0; i < iterCnt; i++ {
-		parallelJoin(t, sb, ep, thrNumber)
-		parallelLeave(t, sb, ep, thrNumber)
-	}
-
-	debugf("\n")
-
-	err = sb.Delete()
-	if err != nil {
-		t.Fatal(err)
-	}
-	if thrNumber == first {
-		for thrdone := range done {
-			select {
-			case <-thrdone:
-			}
-		}
-
-		testns.Close()
-		if err := net2.Delete(); err != nil {
-			t.Fatal(err)
-		}
-	} else {
-		err = ep.Delete(false)
-		if err != nil {
-			t.Fatal(err)
-		}
-	}
-}
-
-func TestParallel1(t *testing.T) {
-	runParallelTests(t, 1)
-}
-
-func TestParallel2(t *testing.T) {
-	runParallelTests(t, 2)
-}
-
-func TestParallel3(t *testing.T) {
-	runParallelTests(t, 3)
-}
-
-func TestNullIpam(t *testing.T) {
-	_, err := controller.NewNetwork(bridgeNetType, "testnetworkinternal", "", libnetwork.NetworkOptionIpam(ipamapi.NullIPAM, "", nil, nil, nil))
-	if err == nil || err.Error() != "ipv4 pool is empty" {
-		t.Fatal("bridge network should complain empty pool")
-	}
-}

+ 75 - 5
libnetwork/netutils/utils_solaris.go

@@ -1,13 +1,26 @@
-package netutils
+// +build solaris
 
-// Solaris: TODO
+package netutils
 
 import (
+	"fmt"
 	"net"
+	"os/exec"
+	"strings"
 
 	"github.com/docker/libnetwork/ipamutils"
+	"github.com/vishvananda/netlink"
+)
+
+var (
+	networkGetRoutesFct func(netlink.Link, int) ([]netlink.Route, error)
 )
 
+// CheckRouteOverlaps checks whether the passed network overlaps with any existing routes
+func CheckRouteOverlaps(toCheck *net.IPNet) error {
+	return nil
+}
+
 // ElectInterfaceAddresses looks for an interface on the OS with the specified name
 // and returns its IPv4 and IPv6 addresses in CIDR form. If the interface does not exist,
 // it chooses from a predifined list the first IPv4 address which does not conflict
@@ -15,18 +28,75 @@ import (
 func ElectInterfaceAddresses(name string) (*net.IPNet, []*net.IPNet, error) {
 	var (
 		v4Net *net.IPNet
-		err   error
 	)
 
-	v4Net, err = FindAvailableNetwork(ipamutils.PredefinedBroadNetworks)
+	out, err := exec.Command("/usr/sbin/ipadm", "show-addr",
+		"-p", "-o", "addrobj,addr").Output()
 	if err != nil {
+		fmt.Println("failed to list interfaces on system")
 		return nil, nil, err
 	}
+	alist := strings.Fields(string(out))
+	for _, a := range alist {
+		linkandaddr := strings.SplitN(a, ":", 2)
+		if len(linkandaddr) != 2 {
+			fmt.Println("failed to check interfaces on system: ", a)
+			continue
+		}
+		gw := fmt.Sprintf("%s_gw0", name)
+		link := strings.Split(linkandaddr[0], "/")[0]
+		addr := linkandaddr[1]
+		if gw != link {
+			continue
+		}
+		_, ipnet, err := net.ParseCIDR(addr)
+		if err != nil {
+			fmt.Println("failed to parse address: ", addr)
+			continue
+		}
+		v4Net = ipnet
+		break
+	}
+	if v4Net == nil {
+		v4Net, err = FindAvailableNetwork(ipamutils.PredefinedBroadNetworks)
+		if err != nil {
+			return nil, nil, err
+		}
+	}
 	return v4Net, nil, nil
 }
 
 // FindAvailableNetwork returns a network from the passed list which does not
 // overlap with existing interfaces in the system
 func FindAvailableNetwork(list []*net.IPNet) (*net.IPNet, error) {
-	return list[0], nil
+	out, err := exec.Command("/usr/sbin/ipadm", "show-addr",
+		"-p", "-o", "addr").Output()
+
+	if err != nil {
+		fmt.Println("failed to list interfaces on system")
+		return nil, err
+	}
+	ipaddrs := strings.Fields(string(out))
+	inuse := []*net.IPNet{}
+	for _, ip := range ipaddrs {
+		_, ipnet, err := net.ParseCIDR(ip)
+		if err != nil {
+			fmt.Println("failed to check interfaces on system: ", ip)
+			continue
+		}
+		inuse = append(inuse, ipnet)
+	}
+	for _, avail := range list {
+		is_avail := true
+		for _, ipnet := range inuse {
+			if NetworkOverlaps(avail, ipnet) {
+				is_avail = false
+				break
+			}
+		}
+		if is_avail {
+			return avail, nil
+		}
+	}
+	return nil, fmt.Errorf("no available network")
 }

+ 2 - 0
libnetwork/netutils/utils_test.go

@@ -1,3 +1,5 @@
+// +build !solaris
+
 package netutils
 
 import (

+ 24 - 0
libnetwork/osl/sandbox_solaris.go

@@ -0,0 +1,24 @@
+package osl
+
+// NewSandbox provides a new sandbox instance created in an os specific way
+// provided a key which uniquely identifies the sandbox
+func NewSandbox(key string, osCreate, isRestore bool) (Sandbox, error) {
+	return nil, nil
+}
+
+// GenerateKey generates a sandbox key based on the passed
+// container id.
+func GenerateKey(containerID string) string {
+	maxLen := 12
+
+	if len(containerID) < maxLen {
+		maxLen = len(containerID)
+	}
+
+	return containerID[:maxLen]
+}
+
+// InitOSContext initializes OS context while configuring network resources
+func InitOSContext() func() {
+	return func() {}
+}

+ 2 - 0
libnetwork/osl/sandbox_test.go

@@ -1,3 +1,5 @@
+// +build !solaris
+
 package osl
 
 import (

+ 1 - 1
libnetwork/osl/sandbox_unsupported.go

@@ -1,4 +1,4 @@
-// +build !linux,!windows,!freebsd
+// +build !linux,!windows,!freebsd,!solaris
 
 package osl
 

+ 1 - 1
libnetwork/osl/sandbox_unsupported_test.go

@@ -1,4 +1,4 @@
-// +build !linux
+// +build !linux,!solaris
 
 package osl
 

+ 0 - 22
libnetwork/portallocator/portallocator.go

@@ -1,11 +1,9 @@
 package portallocator
 
 import (
-	"bufio"
 	"errors"
 	"fmt"
 	"net"
-	"os"
 	"sync"
 )
 
@@ -106,26 +104,6 @@ func newInstance() *PortAllocator {
 	}
 }
 
-func getDynamicPortRange() (start int, end int, err error) {
-	const portRangeKernelParam = "/proc/sys/net/ipv4/ip_local_port_range"
-	portRangeFallback := fmt.Sprintf("using fallback port range %d-%d", DefaultPortRangeStart, DefaultPortRangeEnd)
-	file, err := os.Open(portRangeKernelParam)
-	if err != nil {
-		return 0, 0, fmt.Errorf("port allocator - %s due to error: %v", portRangeFallback, err)
-	}
-
-	defer file.Close()
-
-	n, err := fmt.Fscanf(bufio.NewReader(file), "%d\t%d", &start, &end)
-	if n != 2 || err != nil {
-		if err == nil {
-			err = fmt.Errorf("unexpected count of parsed numbers (%d)", n)
-		}
-		return 0, 0, fmt.Errorf("port allocator - failed to parse system ephemeral port range from %s - %s: %v", portRangeKernelParam, portRangeFallback, err)
-	}
-	return start, end, nil
-}
-
 // RequestPort requests new port from global ports pool for specified ip and proto.
 // If port is 0 it returns first free port. Otherwise it checks port availability
 // in proto's pool and returns that port or error if port is already busy.

+ 27 - 0
libnetwork/portallocator/portallocator_linux.go

@@ -0,0 +1,27 @@
+package portallocator
+
+import (
+	"bufio"
+	"fmt"
+	"os"
+)
+
+func getDynamicPortRange() (start int, end int, err error) {
+	const portRangeKernelParam = "/proc/sys/net/ipv4/ip_local_port_range"
+	portRangeFallback := fmt.Sprintf("using fallback port range %d-%d", DefaultPortRangeStart, DefaultPortRangeEnd)
+	file, err := os.Open(portRangeKernelParam)
+	if err != nil {
+		return 0, 0, fmt.Errorf("port allocator - %s due to error: %v", portRangeFallback, err)
+	}
+
+	defer file.Close()
+
+	n, err := fmt.Fscanf(bufio.NewReader(file), "%d\t%d", &start, &end)
+	if n != 2 || err != nil {
+		if err == nil {
+			err = fmt.Errorf("unexpected count of parsed numbers (%d)", n)
+		}
+		return 0, 0, fmt.Errorf("port allocator - failed to parse system ephemeral port range from %s - %s: %v", portRangeKernelParam, portRangeFallback, err)
+	}
+	return start, end, nil
+}

+ 5 - 0
libnetwork/portallocator/portallocator_solaris.go

@@ -0,0 +1,5 @@
+package portallocator
+
+func getDynamicPortRange() (start int, end int, err error) {
+	return 32768, 65535, nil
+}

+ 0 - 32
libnetwork/portmapper/proxy.go

@@ -7,8 +7,6 @@ import (
 	"net"
 	"os"
 	"os/exec"
-	"strconv"
-	"syscall"
 	"time"
 )
 
@@ -25,36 +23,6 @@ type proxyCommand struct {
 	cmd *exec.Cmd
 }
 
-func newProxyCommand(proto string, hostIP net.IP, hostPort int, containerIP net.IP, containerPort int, proxyPath string) (userlandProxy, error) {
-	path := proxyPath
-	if proxyPath == "" {
-		cmd, err := exec.LookPath(userlandProxyCommandName)
-		if err != nil {
-			return nil, err
-		}
-		path = cmd
-	}
-
-	args := []string{
-		path,
-		"-proto", proto,
-		"-host-ip", hostIP.String(),
-		"-host-port", strconv.Itoa(hostPort),
-		"-container-ip", containerIP.String(),
-		"-container-port", strconv.Itoa(containerPort),
-	}
-
-	return &proxyCommand{
-		cmd: &exec.Cmd{
-			Path: path,
-			Args: args,
-			SysProcAttr: &syscall.SysProcAttr{
-				Pdeathsig: syscall.SIGTERM, // send a sigterm to the proxy if the daemon process dies
-			},
-		},
-	}, nil
-}
-
 func (p *proxyCommand) Start() error {
 	r, w, err := os.Pipe()
 	if err != nil {

+ 38 - 0
libnetwork/portmapper/proxy_linux.go

@@ -0,0 +1,38 @@
+package portmapper
+
+import (
+	"net"
+	"os/exec"
+	"strconv"
+	"syscall"
+)
+
+func newProxyCommand(proto string, hostIP net.IP, hostPort int, containerIP net.IP, containerPort int, proxyPath string) (userlandProxy, error) {
+	path := proxyPath
+	if proxyPath == "" {
+		cmd, err := exec.LookPath(userlandProxyCommandName)
+		if err != nil {
+			return nil, err
+		}
+		path = cmd
+	}
+
+	args := []string{
+		path,
+		"-proto", proto,
+		"-host-ip", hostIP.String(),
+		"-host-port", strconv.Itoa(hostPort),
+		"-container-ip", containerIP.String(),
+		"-container-port", strconv.Itoa(containerPort),
+	}
+
+	return &proxyCommand{
+		cmd: &exec.Cmd{
+			Path: path,
+			Args: args,
+			SysProcAttr: &syscall.SysProcAttr{
+				Pdeathsig: syscall.SIGTERM, // send a sigterm to the proxy if the daemon process dies
+			},
+		},
+	}, nil
+}

+ 34 - 0
libnetwork/portmapper/proxy_solaris.go

@@ -0,0 +1,34 @@
+package portmapper
+
+import (
+	"net"
+	"os/exec"
+	"strconv"
+)
+
+func newProxyCommand(proto string, hostIP net.IP, hostPort int, containerIP net.IP, containerPort int, proxyPath string) (userlandProxy, error) {
+	path := proxyPath
+	if proxyPath == "" {
+		cmd, err := exec.LookPath(userlandProxyCommandName)
+		if err != nil {
+			return nil, err
+		}
+		path = cmd
+	}
+
+	args := []string{
+		path,
+		"-proto", proto,
+		"-host-ip", hostIP.String(),
+		"-host-port", strconv.Itoa(hostPort),
+		"-container-ip", containerIP.String(),
+		"-container-port", strconv.Itoa(containerPort),
+	}
+
+	return &proxyCommand{
+		cmd: &exec.Cmd{
+			Path: path,
+			Args: args,
+		},
+	}, nil
+}

+ 45 - 0
libnetwork/store_linux_test.go

@@ -0,0 +1,45 @@
+package libnetwork
+
+import (
+	"os"
+	"testing"
+
+	"github.com/docker/libkv/store"
+	"github.com/docker/libnetwork/datastore"
+)
+
+func TestBoltdbBackend(t *testing.T) {
+	defer os.Remove(datastore.DefaultScopes("")[datastore.LocalScope].Client.Address)
+	testLocalBackend(t, "", "", nil)
+	defer os.Remove("/tmp/boltdb.db")
+	config := &store.Config{Bucket: "testBackend"}
+	testLocalBackend(t, "boltdb", "/tmp/boltdb.db", config)
+
+}
+
+func TestNoPersist(t *testing.T) {
+	cfgOptions, err := OptionBoltdbWithRandomDBFile()
+	if err != nil {
+		t.Fatalf("Error creating random boltdb file : %v", err)
+	}
+	ctrl, err := New(cfgOptions...)
+	if err != nil {
+		t.Fatalf("Error new controller: %v", err)
+	}
+	nw, err := ctrl.NewNetwork("host", "host", "", NetworkOptionPersist(false))
+	if err != nil {
+		t.Fatalf("Error creating default \"host\" network: %v", err)
+	}
+	ep, err := nw.CreateEndpoint("newendpoint", []EndpointOption{}...)
+	if err != nil {
+		t.Fatalf("Error creating endpoint: %v", err)
+	}
+	store := ctrl.(*controller).getStore(datastore.LocalScope).KVStore()
+	if exists, _ := store.Exists(datastore.Key(datastore.NetworkKeyPrefix, string(nw.ID()))); exists {
+		t.Fatalf("Network with persist=false should not be stored in KV Store")
+	}
+	if exists, _ := store.Exists(datastore.Key([]string{datastore.EndpointKeyPrefix, string(nw.ID()), string(ep.ID())}...)); exists {
+		t.Fatalf("Endpoint in Network with persist=false should not be stored in KV Store")
+	}
+	store.Close()
+}

+ 0 - 37
libnetwork/store_test.go

@@ -3,7 +3,6 @@ package libnetwork
 import (
 	"fmt"
 	"io/ioutil"
-	"os"
 	"testing"
 
 	"github.com/docker/libkv/store"
@@ -31,15 +30,6 @@ func testNewController(t *testing.T, provider, url string) (NetworkController, e
 	return New(cfgOptions...)
 }
 
-func TestBoltdbBackend(t *testing.T) {
-	defer os.Remove(datastore.DefaultScopes("")[datastore.LocalScope].Client.Address)
-	testLocalBackend(t, "", "", nil)
-	defer os.Remove("/tmp/boltdb.db")
-	config := &store.Config{Bucket: "testBackend"}
-	testLocalBackend(t, "boltdb", "/tmp/boltdb.db", config)
-
-}
-
 func testLocalBackend(t *testing.T, provider, url string, storeConfig *store.Config) {
 	cfgOptions := []config.Option{}
 	cfgOptions = append(cfgOptions, config.OptionLocalKVProvider(provider))
@@ -82,33 +72,6 @@ func testLocalBackend(t *testing.T, provider, url string, storeConfig *store.Con
 	}
 }
 
-func TestNoPersist(t *testing.T) {
-	cfgOptions, err := OptionBoltdbWithRandomDBFile()
-	if err != nil {
-		t.Fatalf("Error creating random boltdb file : %v", err)
-	}
-	ctrl, err := New(cfgOptions...)
-	if err != nil {
-		t.Fatalf("Error new controller: %v", err)
-	}
-	nw, err := ctrl.NewNetwork("host", "host", "", NetworkOptionPersist(false))
-	if err != nil {
-		t.Fatalf("Error creating default \"host\" network: %v", err)
-	}
-	ep, err := nw.CreateEndpoint("newendpoint", []EndpointOption{}...)
-	if err != nil {
-		t.Fatalf("Error creating endpoint: %v", err)
-	}
-	store := ctrl.(*controller).getStore(datastore.LocalScope).KVStore()
-	if exists, _ := store.Exists(datastore.Key(datastore.NetworkKeyPrefix, string(nw.ID()))); exists {
-		t.Fatalf("Network with persist=false should not be stored in KV Store")
-	}
-	if exists, _ := store.Exists(datastore.Key([]string{datastore.EndpointKeyPrefix, string(nw.ID()), string(ep.ID())}...)); exists {
-		t.Fatalf("Endpoint in Network with persist=false should not be stored in KV Store")
-	}
-	store.Close()
-}
-
 // OptionBoltdbWithRandomDBFile function returns a random dir for local store backend
 func OptionBoltdbWithRandomDBFile() ([]config.Option, error) {
 	tmp, err := ioutil.TempFile("", "libnetwork-")

+ 25 - 0
libnetwork/testutils/context_solaris.go

@@ -0,0 +1,25 @@
+// +build solaris
+
+package testutils
+
+import (
+	"os"
+	"testing"
+)
+
+// SetupTestOSContext joins a new network namespace, and returns its associated
+// teardown function.
+//
+// Example usage:
+//
+//     defer SetupTestOSContext(t)()
+//
+func SetupTestOSContext(t *testing.T) func() {
+	return func() {
+	}
+}
+
+// RunningOnCircleCI returns true if being executed on libnetwork Circle CI setup
+func RunningOnCircleCI() bool {
+	return os.Getenv("CIRCLECI") != ""
+}