Pārlūkot izejas kodu

Merge pull request #810 from aboch/se

Move exposed ports and port bindings from Endpoint to Sandbox
Santhosh Manohar 9 gadi atpakaļ
vecāks
revīzija
bb3c060954

+ 6 - 14
libnetwork/api/api.go

@@ -251,6 +251,12 @@ func (sc *sandboxCreate) parseOptions() []libnetwork.SandboxOption {
 			setFctList = append(setFctList, libnetwork.OptionExtraHost(e.Name, e.Address))
 		}
 	}
+	if sc.ExposedPorts != nil {
+		setFctList = append(setFctList, libnetwork.OptionExposedPorts(sc.ExposedPorts))
+	}
+	if sc.PortMapping != nil {
+		setFctList = append(setFctList, libnetwork.OptionPortMapping(sc.PortMapping))
+	}
 	return setFctList
 }
 
@@ -384,13 +390,6 @@ func procCreateEndpoint(c libnetwork.NetworkController, vars map[string]string,
 	}
 
 	var setFctList []libnetwork.EndpointOption
-	if ec.ExposedPorts != nil {
-		setFctList = append(setFctList, libnetwork.CreateOptionExposedPorts(ec.ExposedPorts))
-	}
-	if ec.PortMapping != nil {
-		setFctList = append(setFctList, libnetwork.CreateOptionPortMapping(ec.PortMapping))
-	}
-
 	for _, str := range ec.MyAliases {
 		setFctList = append(setFctList, libnetwork.CreateOptionMyAlias(str))
 	}
@@ -633,13 +632,6 @@ func procPublishService(c libnetwork.NetworkController, vars map[string]string,
 	}
 
 	var setFctList []libnetwork.EndpointOption
-	if sp.ExposedPorts != nil {
-		setFctList = append(setFctList, libnetwork.CreateOptionExposedPorts(sp.ExposedPorts))
-	}
-	if sp.PortMapping != nil {
-		setFctList = append(setFctList, libnetwork.CreateOptionPortMapping(sp.PortMapping))
-	}
-
 	for _, str := range sp.MyAliases {
 		setFctList = append(setFctList, libnetwork.CreateOptionMyAlias(str))
 	}

+ 11 - 9
libnetwork/api/api_test.go

@@ -293,9 +293,7 @@ func TestGetNetworksAndEndpoints(t *testing.T) {
 	}
 
 	ec1 := endpointCreate{
-		Name:         "ep1",
-		ExposedPorts: getExposedPorts(),
-		PortMapping:  getPortMapping(),
+		Name: "ep1",
 	}
 	b1, err := json.Marshal(ec1)
 	if err != nil {
@@ -823,10 +821,8 @@ func TestProcPublishUnpublishService(t *testing.T) {
 	}
 
 	sp := servicePublish{
-		Name:         "web",
-		Network:      "network",
-		ExposedPorts: getExposedPorts(),
-		PortMapping:  getPortMapping(),
+		Name:    "web",
+		Network: "network",
 	}
 	b, err = json.Marshal(sp)
 	if err != nil {
@@ -2087,7 +2083,10 @@ func TestEndToEnd(t *testing.T) {
 	cpid1 := string(chars[0 : len(chars)/2])
 
 	// Create sandboxes
-	sb1, err := json.Marshal(sandboxCreate{ContainerID: cid1})
+	sb1, err := json.Marshal(sandboxCreate{
+		ContainerID: cid1,
+		PortMapping: getPortMapping(),
+	})
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -2111,7 +2110,10 @@ func TestEndToEnd(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	sb2, err := json.Marshal(sandboxCreate{ContainerID: cid2})
+	sb2, err := json.Marshal(sandboxCreate{
+		ContainerID:  cid2,
+		ExposedPorts: getExposedPorts(),
+	})
 	if err != nil {
 		t.Fatal(err)
 	}

+ 16 - 18
libnetwork/api/types.go

@@ -42,23 +42,23 @@ type networkCreate struct {
 
 // endpointCreate represents the body of the "create endpoint" http request message
 type endpointCreate struct {
-	Name         string                `json:"name"`
-	MyAliases    []string              `json:"my_aliases"`
-	ExposedPorts []types.TransportPort `json:"exposed_ports"`
-	PortMapping  []types.PortBinding   `json:"port_mapping"`
+	Name      string   `json:"name"`
+	MyAliases []string `json:"my_aliases"`
 }
 
 // sandboxCreate is the expected body of the "create sandbox" http request message
 type sandboxCreate struct {
-	ContainerID       string      `json:"container_id"`
-	HostName          string      `json:"host_name"`
-	DomainName        string      `json:"domain_name"`
-	HostsPath         string      `json:"hosts_path"`
-	ResolvConfPath    string      `json:"resolv_conf_path"`
-	DNS               []string    `json:"dns"`
-	ExtraHosts        []extraHost `json:"extra_hosts"`
-	UseDefaultSandbox bool        `json:"use_default_sandbox"`
-	UseExternalKey    bool        `json:"use_external_key"`
+	ContainerID       string                `json:"container_id"`
+	HostName          string                `json:"host_name"`
+	DomainName        string                `json:"domain_name"`
+	HostsPath         string                `json:"hosts_path"`
+	ResolvConfPath    string                `json:"resolv_conf_path"`
+	DNS               []string              `json:"dns"`
+	ExtraHosts        []extraHost           `json:"extra_hosts"`
+	UseDefaultSandbox bool                  `json:"use_default_sandbox"`
+	UseExternalKey    bool                  `json:"use_external_key"`
+	ExposedPorts      []types.TransportPort `json:"exposed_ports"`
+	PortMapping       []types.PortBinding   `json:"port_mapping"`
 }
 
 // endpointJoin represents the expected body of the "join endpoint" or "leave endpoint" http request messages
@@ -69,11 +69,9 @@ type endpointJoin struct {
 
 // servicePublish represents the body of the "publish service" http request message
 type servicePublish struct {
-	Name         string                `json:"name"`
-	MyAliases    []string              `json:"my_aliases"`
-	Network      string                `json:"network_name"`
-	ExposedPorts []types.TransportPort `json:"exposed_ports"`
-	PortMapping  []types.PortBinding   `json:"port_mapping"`
+	Name      string   `json:"name"`
+	MyAliases []string `json:"my_aliases"`
+	Network   string   `json:"network_name"`
 }
 
 // serviceDelete represents the body of the "unpublish service" http request message

+ 13 - 13
libnetwork/client/types.go

@@ -42,11 +42,9 @@ type networkCreate struct {
 
 // serviceCreate represents the body of the "publish service" http request message
 type serviceCreate struct {
-	Name         string                `json:"name"`
-	MyAliases    []string              `json:"my_aliases"`
-	Network      string                `json:"network_name"`
-	ExposedPorts []types.TransportPort `json:"exposed_ports"`
-	PortMapping  []types.PortBinding   `json:"port_mapping"`
+	Name      string   `json:"name"`
+	MyAliases []string `json:"my_aliases"`
+	Network   string   `json:"network_name"`
 }
 
 // serviceDelete represents the body of the "unpublish service" http request message
@@ -63,14 +61,16 @@ type serviceAttach struct {
 
 // SandboxCreate is the body of the "post /sandboxes" http request message
 type SandboxCreate struct {
-	ContainerID       string      `json:"container_id"`
-	HostName          string      `json:"host_name"`
-	DomainName        string      `json:"domain_name"`
-	HostsPath         string      `json:"hosts_path"`
-	ResolvConfPath    string      `json:"resolv_conf_path"`
-	DNS               []string    `json:"dns"`
-	ExtraHosts        []extraHost `json:"extra_hosts"`
-	UseDefaultSandbox bool        `json:"use_default_sandbox"`
+	ContainerID       string                `json:"container_id"`
+	HostName          string                `json:"host_name"`
+	DomainName        string                `json:"domain_name"`
+	HostsPath         string                `json:"hosts_path"`
+	ResolvConfPath    string                `json:"resolv_conf_path"`
+	DNS               []string              `json:"dns"`
+	ExtraHosts        []extraHost           `json:"extra_hosts"`
+	UseDefaultSandbox bool                  `json:"use_default_sandbox"`
+	ExposedPorts      []types.TransportPort `json:"exposed_ports"`
+	PortMapping       []types.PortBinding   `json:"port_mapping"`
 }
 
 // extraHost represents the extra host object

+ 26 - 18
libnetwork/default_gateway.go

@@ -3,7 +3,6 @@ package libnetwork
 import (
 	"fmt"
 
-	"github.com/docker/libnetwork/netlabel"
 	"github.com/docker/libnetwork/types"
 )
 
@@ -28,15 +27,15 @@ var procGwNetwork = make(chan (bool), 1)
    - its deleted when an endpoint with GW joins the container
 */
 
-func (sb *sandbox) setupDefaultGW(srcEp *endpoint) error {
-	var createOptions []EndpointOption
-	c := srcEp.getNetwork().getController()
+func (sb *sandbox) setupDefaultGW() error {
 
 	// check if the conitainer already has a GW endpoint
 	if ep := sb.getEndpointInGWNetwork(); ep != nil {
 		return nil
 	}
 
+	c := sb.controller
+
 	// Look for default gw network. In case of error (includes not found),
 	// retry and create it if needed in a serialized execution.
 	n, err := c.NetworkByName(libnGWNetwork)
@@ -46,19 +45,7 @@ func (sb *sandbox) setupDefaultGW(srcEp *endpoint) error {
 		}
 	}
 
-	if opt, ok := srcEp.generic[netlabel.PortMap]; ok {
-		if pb, ok := opt.([]types.PortBinding); ok {
-			createOptions = append(createOptions, CreateOptionPortMapping(pb))
-		}
-	}
-
-	if opt, ok := srcEp.generic[netlabel.ExposedPorts]; ok {
-		if exp, ok := opt.([]types.TransportPort); ok {
-			createOptions = append(createOptions, CreateOptionExposedPorts(exp))
-		}
-	}
-
-	createOptions = append(createOptions, CreateOptionAnonymous())
+	createOptions := []EndpointOption{CreateOptionAnonymous()}
 
 	eplen := gwEPlen
 	if len(sb.containerID) < gwEPlen {
@@ -74,9 +61,13 @@ func (sb *sandbox) setupDefaultGW(srcEp *endpoint) error {
 	if err := epLocal.sbJoin(sb); err != nil {
 		return fmt.Errorf("container %s: endpoint join on GW Network failed: %v", sb.containerID, err)
 	}
+
 	return nil
 }
 
+// If present, removes the endpoint connecting the sandbox to the default gw network.
+// Unless it is the endpoint designated to provide the external connectivity.
+// If the sandbox is being deleted, removes the endpoint unconditionally.
 func (sb *sandbox) clearDefaultGW() error {
 	var ep *endpoint
 
@@ -84,6 +75,10 @@ func (sb *sandbox) clearDefaultGW() error {
 		return nil
 	}
 
+	if ep == sb.getGatewayEndpoint() && !sb.inDelete {
+		return nil
+	}
+
 	if err := ep.sbLeave(sb, false); err != nil {
 		return fmt.Errorf("container %s: endpoint leaving GW Network failed: %v", sb.containerID, err)
 	}
@@ -98,7 +93,7 @@ func (sb *sandbox) needDefaultGW() bool {
 
 	for _, ep := range sb.getConnectedEndpoints() {
 		if ep.endpointInGWNetwork() {
-			continue
+			return false
 		}
 		if ep.getNetwork().Type() == "null" || ep.getNetwork().Type() == "host" {
 			continue
@@ -165,3 +160,16 @@ func (c *controller) defaultGwNetwork() (Network, error) {
 	}
 	return n, err
 }
+
+// Returns the endpoint which is providing external connectivity to the sandbox
+func (sb *sandbox) getGatewayEndpoint() *endpoint {
+	for _, ep := range sb.getConnectedEndpoints() {
+		if ep.getNetwork().Type() == "null" || ep.getNetwork().Type() == "host" {
+			continue
+		}
+		if len(ep.Gateway()) != 0 {
+			return ep
+		}
+	}
+	return nil
+}

+ 8 - 0
libnetwork/driverapi/driverapi.go

@@ -42,6 +42,14 @@ type Driver interface {
 	// Leave method is invoked when a Sandbox detaches from an endpoint.
 	Leave(nid, eid string) error
 
+	// ProgramExternalConnectivity invokes the driver method which does the necessary
+	// programming to allow the external connectivity dictated by the passed options
+	ProgramExternalConnectivity(nid, eid string, options map[string]interface{}) error
+
+	// RevokeExternalConnectivity aks the driver to remove any external connectivity
+	// programming that was done so far
+	RevokeExternalConnectivity(nid, eid string) error
+
 	// Type returns the the type of this driver, the network type this driver manages
 	Type() string
 }

+ 125 - 63
libnetwork/drivers/bridge/bridge.go

@@ -74,9 +74,7 @@ type networkConfiguration struct {
 
 // endpointConfiguration represents the user specified configuration for the sandbox endpoint
 type endpointConfiguration struct {
-	MacAddress   net.HardwareAddr
-	PortBindings []types.PortBinding
-	ExposedPorts []types.TransportPort
+	MacAddress net.HardwareAddr
 }
 
 // containerConfiguration represents the user specified configuration for a container
@@ -85,6 +83,12 @@ type containerConfiguration struct {
 	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
 	srcName         string
@@ -93,6 +97,7 @@ type bridgeEndpoint struct {
 	macAddress      net.HardwareAddr
 	config          *endpointConfiguration // User specified parameters
 	containerConfig *containerConfiguration
+	extConnConfig   *connectivityConfiguration
 	portMapping     []types.PortBinding // Operation port bindings
 }
 
@@ -995,12 +1000,6 @@ func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo,
 		}
 	}
 
-	// Program any required port mapping and store them in the endpoint
-	endpoint.portMapping, err = n.allocatePorts(epConfig, endpoint, config.DefaultBindingIP, d.config.EnableUserlandProxy)
-	if err != nil {
-		return err
-	}
-
 	return nil
 }
 
@@ -1055,9 +1054,6 @@ func (d *driver) DeleteEndpoint(nid, eid string) error {
 		}
 	}()
 
-	// Remove port mappings. Do not stop endpoint delete on unmap failure
-	n.releasePorts(ep)
-
 	// Try removal of link. Discard error: it is a best effort.
 	// Also make sure defer does not see this error either.
 	if link, err := netlink.LinkByName(ep.srcName); err == nil {
@@ -1098,10 +1094,10 @@ func (d *driver) EndpointOperInfo(nid, eid string) (map[string]interface{}, erro
 
 	m := make(map[string]interface{})
 
-	if ep.config.ExposedPorts != nil {
+	if ep.extConnConfig != nil && ep.extConnConfig.ExposedPorts != nil {
 		// Return a copy of the config data
-		epc := make([]types.TransportPort, 0, len(ep.config.ExposedPorts))
-		for _, tp := range ep.config.ExposedPorts {
+		epc := make([]types.TransportPort, 0, len(ep.extConnConfig.ExposedPorts))
+		for _, tp := range ep.extConnConfig.ExposedPorts {
 			epc = append(epc, tp.GetCopy())
 		}
 		m[netlabel.ExposedPorts] = epc
@@ -1141,6 +1137,11 @@ func (d *driver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo,
 		return EndpointNotFoundError(eid)
 	}
 
+	endpoint.containerConfig, err = parseContainerOptions(options)
+	if err != nil {
+		return err
+	}
+
 	iNames := jinfo.InterfaceName()
 	err = iNames.SetNames(endpoint.srcName, containerVethPrefix)
 	if err != nil {
@@ -1157,10 +1158,6 @@ func (d *driver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo,
 		return err
 	}
 
-	if !network.config.EnableICC {
-		return d.link(network, endpoint, options, true)
-	}
-
 	return nil
 }
 
@@ -1183,32 +1180,87 @@ func (d *driver) Leave(nid, eid string) error {
 	}
 
 	if !network.config.EnableICC {
-		return d.link(network, endpoint, nil, false)
+		if err = d.link(network, endpoint, false); err != nil {
+			return err
+		}
 	}
 
 	return nil
 }
 
-func (d *driver) link(network *bridgeNetwork, endpoint *bridgeEndpoint, options map[string]interface{}, enable bool) error {
-	var (
-		cc  *containerConfiguration
-		err error
-	)
+func (d *driver) ProgramExternalConnectivity(nid, eid string, options map[string]interface{}) error {
+	defer osl.InitOSContext()()
 
-	if enable {
-		cc, err = parseContainerOptions(options)
-		if err != nil {
-			return err
-		}
-	} else {
-		cc = endpoint.containerConfig
+	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.DefaultBindingIP, d.config.EnableUserlandProxy)
+	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 {
+	defer osl.InitOSContext()()
+
+	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) link(network *bridgeNetwork, endpoint *bridgeEndpoint, enable bool) error {
+	var err error
+
+	cc := endpoint.containerConfig
 	if cc == nil {
 		return nil
 	}
+	ec := endpoint.extConnConfig
+	if ec == nil {
+		return nil
+	}
 
-	if endpoint.config != nil && endpoint.config.ExposedPorts != nil {
+	if ec.ExposedPorts != nil {
 		for _, p := range cc.ParentEndpoints {
 			var parentEndpoint *bridgeEndpoint
 			parentEndpoint, err = network.getEndpoint(p)
@@ -1222,7 +1274,7 @@ func (d *driver) link(network *bridgeNetwork, endpoint *bridgeEndpoint, options
 
 			l := newLink(parentEndpoint.addr.IP.String(),
 				endpoint.addr.IP.String(),
-				endpoint.config.ExposedPorts, network.config.BridgeName)
+				ec.ExposedPorts, network.config.BridgeName)
 			if enable {
 				err = l.Enable()
 				if err != nil {
@@ -1249,13 +1301,13 @@ func (d *driver) link(network *bridgeNetwork, endpoint *bridgeEndpoint, options
 			err = InvalidEndpointIDError(c)
 			return err
 		}
-		if childEndpoint.config == nil || childEndpoint.config.ExposedPorts == nil {
+		if childEndpoint.extConnConfig == nil || childEndpoint.extConnConfig.ExposedPorts == nil {
 			continue
 		}
 
 		l := newLink(endpoint.addr.IP.String(),
 			childEndpoint.addr.IP.String(),
-			childEndpoint.config.ExposedPorts, network.config.BridgeName)
+			childEndpoint.extConnConfig.ExposedPorts, network.config.BridgeName)
 		if enable {
 			err = l.Enable()
 			if err != nil {
@@ -1271,10 +1323,6 @@ func (d *driver) link(network *bridgeNetwork, endpoint *bridgeEndpoint, options
 		}
 	}
 
-	if enable {
-		endpoint.containerConfig = cc
-	}
-
 	return nil
 }
 
@@ -1307,45 +1355,59 @@ func parseEndpointOptions(epOptions map[string]interface{}) (*endpointConfigurat
 		}
 	}
 
-	if opt, ok := epOptions[netlabel.PortMap]; ok {
-		if bs, ok := opt.([]types.PortBinding); ok {
-			ec.PortBindings = bs
+	return ec, nil
+}
+
+func parseContainerOptions(cOptions map[string]interface{}) (*containerConfiguration, error) {
+	if cOptions == nil {
+		return nil, nil
+	}
+
+	cc := &containerConfiguration{}
+
+	if opt, ok := cOptions[ParentEndpoints]; ok {
+		if pe, ok := opt.([]string); ok {
+			cc.ParentEndpoints = pe
 		} else {
-			return nil, &ErrInvalidEndpointConfig{}
+			return nil, types.BadRequestErrorf("Invalid parent endpoints data in sandbox configuration: %v", opt)
 		}
 	}
 
-	if opt, ok := epOptions[netlabel.ExposedPorts]; ok {
-		if ports, ok := opt.([]types.TransportPort); ok {
-			ec.ExposedPorts = ports
+	if opt, ok := cOptions[ChildEndpoints]; ok {
+		if ce, ok := opt.([]string); ok {
+			cc.ChildEndpoints = ce
 		} else {
-			return nil, &ErrInvalidEndpointConfig{}
+			return nil, types.BadRequestErrorf("Invalid child endpoints data in sandbox configuration: %v", opt)
 		}
 	}
 
-	return ec, nil
+	return cc, nil
 }
 
-func parseContainerOptions(cOptions map[string]interface{}) (*containerConfiguration, error) {
+func parseConnectivityOptions(cOptions map[string]interface{}) (*connectivityConfiguration, error) {
 	if cOptions == nil {
 		return nil, nil
 	}
-	genericData := cOptions[netlabel.GenericData]
-	if genericData == 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)
+		}
 	}
-	switch opt := genericData.(type) {
-	case options.Generic:
-		opaqueConfig, err := options.GenerateFromModel(opt, &containerConfiguration{})
-		if err != nil {
-			return nil, err
+
+	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 opaqueConfig.(*containerConfiguration), nil
-	case *containerConfiguration:
-		return opt, nil
-	default:
-		return nil, nil
 	}
+
+	return cc, nil
 }
 
 func electMacAddress(epConfig *endpointConfiguration, ip net.IP) net.HardwareAddr {

+ 56 - 22
libnetwork/drivers/bridge/bridge_test.go

@@ -461,16 +461,25 @@ func testQueryEndpointInfo(t *testing.T, ulPxyEnabled bool) {
 		t.Fatalf("Failed to create bridge: %v", err)
 	}
 
-	portMappings := getPortMapping()
-	epOptions := make(map[string]interface{})
-	epOptions[netlabel.PortMap] = portMappings
+	sbOptions := make(map[string]interface{})
+	sbOptions[netlabel.PortMap] = getPortMapping()
 
 	te := newTestEndpoint(ipdList[0].Pool, 11)
-	err = d.CreateEndpoint("net1", "ep1", te.Interface(), epOptions)
+	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")
@@ -497,10 +506,15 @@ func testQueryEndpointInfo(t *testing.T, ulPxyEnabled bool) {
 		}
 	}
 
-	// Cleanup as host ports are there
-	err = network.releasePorts(ep)
+	err = d.RevokeExternalConnectivity("net1", "ep1")
 	if err != nil {
-		t.Fatalf("Failed to release mapped ports: %v", err)
+		t.Fatal(err)
+	}
+
+	// release host mapped ports
+	err = d.Leave("net1", "ep1")
+	if err != nil {
+		t.Fatal(err)
 	}
 }
 
@@ -548,16 +562,26 @@ func TestLinkContainers(t *testing.T) {
 		t.Fatalf("Failed to create bridge: %v", err)
 	}
 
-	exposedPorts := getExposedPorts()
-	epOptions := make(map[string]interface{})
-	epOptions[netlabel.ExposedPorts] = exposedPorts
-
 	te1 := newTestEndpoint(ipdList[0].Pool, 11)
-	err = d.CreateEndpoint("net1", "ep1", te1.Interface(), epOptions)
+	err = d.CreateEndpoint("net1", "ep1", te1.Interface(), nil)
 	if err != nil {
 		t.Fatalf("Failed to create an endpoint : %s", err.Error())
 	}
 
+	exposedPorts := getExposedPorts()
+	sbOptions := make(map[string]interface{})
+	sbOptions[netlabel.ExposedPorts] = exposedPorts
+
+	err = d.Join("net1", "ep1", "sbox", te1, 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)
+	}
+
 	addr1 := te1.iface.addr
 	if addr1.IP.To4() == nil {
 		t.Fatalf("No Ipv4 address assigned to the endpoint:  ep1")
@@ -574,16 +598,19 @@ func TestLinkContainers(t *testing.T) {
 		t.Fatalf("No Ipv4 address assigned to the endpoint:  ep2")
 	}
 
-	ce := []string{"ep1"}
-	cConfig := &containerConfiguration{ChildEndpoints: ce}
-	genericOption = make(map[string]interface{})
-	genericOption[netlabel.GenericData] = cConfig
+	sbOptions = make(map[string]interface{})
+	sbOptions[ChildEndpoints] = []string{"ep1"}
 
-	err = d.Join("net1", "ep2", "", te2, genericOption)
+	err = d.Join("net1", "ep2", "", te2, sbOptions)
 	if err != nil {
 		t.Fatalf("Failed to link ep1 and ep2")
 	}
 
+	err = d.ProgramExternalConnectivity("net1", "ep2", sbOptions)
+	if err != nil {
+		t.Fatalf("Failed to program external connectivity: %v", err)
+	}
+
 	out, err := iptables.Raw("-L", DockerChain)
 	for _, pm := range exposedPorts {
 		regex := fmt.Sprintf("%s dpt:%d", pm.Proto.String(), pm.Port)
@@ -600,6 +627,11 @@ func TestLinkContainers(t *testing.T) {
 		}
 	}
 
+	err = d.RevokeExternalConnectivity("net1", "ep2")
+	if err != nil {
+		t.Fatalf("Failed to revoke external connectivity: %v", err)
+	}
+
 	err = d.Leave("net1", "ep2")
 	if err != nil {
 		t.Fatalf("Failed to unlink ep1 and ep2")
@@ -622,12 +654,14 @@ func TestLinkContainers(t *testing.T) {
 	}
 
 	// Error condition test with an invalid endpoint-id "ep4"
-	ce = []string{"ep1", "ep4"}
-	cConfig = &containerConfiguration{ChildEndpoints: ce}
-	genericOption = make(map[string]interface{})
-	genericOption[netlabel.GenericData] = cConfig
+	sbOptions = make(map[string]interface{})
+	sbOptions[ChildEndpoints] = []string{"ep1", "ep4"}
 
-	err = d.Join("net1", "ep2", "", te2, genericOption)
+	err = d.Join("net1", "ep2", "", te2, sbOptions)
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = d.ProgramExternalConnectivity("net1", "ep2", sbOptions)
 	if err != nil {
 		out, err = iptables.Raw("-L", DockerChain)
 		for _, pm := range exposedPorts {

+ 6 - 0
libnetwork/drivers/bridge/labels.go

@@ -15,4 +15,10 @@ const (
 
 	// DefaultBridge label
 	DefaultBridge = "com.docker.network.bridge.default_bridge"
+
+	// ChildEndpoints for links
+	ChildEndpoints = "com.docker.network.bridge.child_endpoints"
+
+	// ParentEndpoints for links
+	ParentEndpoints = "com.docker.network.bridge.parent_endpoints"
 )

+ 3 - 3
libnetwork/drivers/bridge/port_mapping.go

@@ -14,8 +14,8 @@ var (
 	defaultBindingIP = net.IPv4(0, 0, 0, 0)
 )
 
-func (n *bridgeNetwork) allocatePorts(epConfig *endpointConfiguration, ep *bridgeEndpoint, reqDefBindIP net.IP, ulPxyEnabled bool) ([]types.PortBinding, error) {
-	if epConfig == nil || epConfig.PortBindings == nil {
+func (n *bridgeNetwork) allocatePorts(ep *bridgeEndpoint, reqDefBindIP net.IP, ulPxyEnabled bool) ([]types.PortBinding, error) {
+	if ep.extConnConfig == nil || ep.extConnConfig.PortBindings == nil {
 		return nil, nil
 	}
 
@@ -24,7 +24,7 @@ func (n *bridgeNetwork) allocatePorts(epConfig *endpointConfiguration, ep *bridg
 		defHostIP = reqDefBindIP
 	}
 
-	return n.allocatePortsInternal(epConfig.PortBindings, ep.addr.IP, defHostIP, ulPxyEnabled)
+	return n.allocatePortsInternal(ep.extConnConfig.PortBindings, ep.addr.IP, defHostIP, ulPxyEnabled)
 }
 
 func (n *bridgeNetwork) allocatePortsInternal(bindings []types.PortBinding, containerIP, defHostIP net.IP, ulPxyEnabled bool) ([]types.PortBinding, error) {

+ 19 - 5
libnetwork/drivers/bridge/port_mapping_test.go

@@ -35,8 +35,8 @@ func TestPortMappingConfig(t *testing.T) {
 	binding2 := types.PortBinding{Proto: types.TCP, Port: uint16(500), HostPort: uint16(65000)}
 	portBindings := []types.PortBinding{binding1, binding2}
 
-	epOptions := make(map[string]interface{})
-	epOptions[netlabel.PortMap] = portBindings
+	sbOptions := make(map[string]interface{})
+	sbOptions[netlabel.PortMap] = portBindings
 
 	netConfig := &networkConfiguration{
 		BridgeName: DefaultBridgeName,
@@ -51,11 +51,19 @@ func TestPortMappingConfig(t *testing.T) {
 	}
 
 	te := newTestEndpoint(ipdList[0].Pool, 11)
-	err = d.CreateEndpoint("dummy", "ep1", te.Interface(), epOptions)
+	err = d.CreateEndpoint("dummy", "ep1", te.Interface(), nil)
 	if err != nil {
 		t.Fatalf("Failed to create the endpoint: %s", err.Error())
 	}
 
+	if err = d.Join("dummy", "ep1", "sbox", te, sbOptions); err != nil {
+		t.Fatalf("Failed to join the endpoint: %v", err)
+	}
+
+	if err = d.ProgramExternalConnectivity("dummy", "ep1", sbOptions); err != nil {
+		t.Fatalf("Failed to program external connectivity: %v", err)
+	}
+
 	network, ok := d.networks["dummy"]
 	if !ok {
 		t.Fatalf("Cannot find network %s inside driver", "dummy")
@@ -73,8 +81,14 @@ func TestPortMappingConfig(t *testing.T) {
 		t.Fatalf("operational port mapping data not found on bridgeEndpoint")
 	}
 
-	err = network.releasePorts(ep)
+	// release host mapped ports
+	err = d.Leave("dummy", "ep1")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	err = d.RevokeExternalConnectivity("dummy", "ep1")
 	if err != nil {
-		t.Fatalf("Failed to release mapped ports: %v", err)
+		t.Fatal(err)
 	}
 }

+ 8 - 0
libnetwork/drivers/host/host.go

@@ -63,6 +63,14 @@ func (d *driver) Leave(nid, eid string) error {
 	return nil
 }
 
+func (d *driver) ProgramExternalConnectivity(nid, eid string, options map[string]interface{}) error {
+	return nil
+}
+
+func (d *driver) RevokeExternalConnectivity(nid, eid string) error {
+	return nil
+}
+
 func (d *driver) Type() string {
 	return networkType
 }

+ 8 - 0
libnetwork/drivers/null/null.go

@@ -63,6 +63,14 @@ func (d *driver) Leave(nid, eid string) error {
 	return nil
 }
 
+func (d *driver) ProgramExternalConnectivity(nid, eid string, options map[string]interface{}) error {
+	return nil
+}
+
+func (d *driver) RevokeExternalConnectivity(nid, eid string) error {
+	return nil
+}
+
 func (d *driver) Type() string {
 	return networkType
 }

+ 8 - 0
libnetwork/drivers/overlay/ov_network.go

@@ -114,6 +114,14 @@ func (d *driver) DeleteNetwork(nid string) error {
 	return n.releaseVxlanID()
 }
 
+func (d *driver) ProgramExternalConnectivity(nid, eid string, options map[string]interface{}) error {
+	return nil
+}
+
+func (d *driver) RevokeExternalConnectivity(nid, eid string) error {
+	return nil
+}
+
 func (n *network) incEndpointCount() {
 	n.Lock()
 	defer n.Unlock()

+ 23 - 0
libnetwork/drivers/remote/api/api.go

@@ -153,6 +153,29 @@ type LeaveResponse struct {
 	Response
 }
 
+// ProgramExternalConnectivityRequest describes the API for programming the external connectivity for the given endpoint.
+type ProgramExternalConnectivityRequest struct {
+	NetworkID  string
+	EndpointID string
+	Options    map[string]interface{}
+}
+
+// ProgramExternalConnectivityResponse is the answer to ProgramExternalConnectivityRequest.
+type ProgramExternalConnectivityResponse struct {
+	Response
+}
+
+// RevokeExternalConnectivityRequest describes the API for revoking the external connectivity for the given endpoint.
+type RevokeExternalConnectivityRequest struct {
+	NetworkID  string
+	EndpointID string
+}
+
+// RevokeExternalConnectivityResponse is the answer to RevokeExternalConnectivityRequest.
+type RevokeExternalConnectivityResponse struct {
+	Response
+}
+
 // DiscoveryNotification represents a discovery notification
 type DiscoveryNotification struct {
 	DiscoveryType discoverapi.DiscoveryType

+ 34 - 0
libnetwork/drivers/remote/driver.go

@@ -3,6 +3,7 @@ package remote
 import (
 	"fmt"
 	"net"
+	"strings"
 
 	log "github.com/Sirupsen/logrus"
 	"github.com/docker/docker/pkg/plugins"
@@ -13,6 +14,10 @@ import (
 	"github.com/docker/libnetwork/types"
 )
 
+const (
+	missingMethod = "404 page not found"
+)
+
 type driver struct {
 	endpoint    *plugins.Client
 	networkType string
@@ -247,6 +252,35 @@ func (d *driver) Leave(nid, eid string) error {
 	return d.call("Leave", leave, &api.LeaveResponse{})
 }
 
+// ProgramExternalConnectivity is invoked to program the rules to allow external connectivity for the endpoint.
+func (d *driver) ProgramExternalConnectivity(nid, eid string, options map[string]interface{}) error {
+	data := &api.ProgramExternalConnectivityRequest{
+		NetworkID:  nid,
+		EndpointID: eid,
+		Options:    options,
+	}
+	err := d.call("ProgramExternalConnectivity", data, &api.ProgramExternalConnectivityResponse{})
+	if err != nil && strings.Contains(err.Error(), missingMethod) {
+		// It is not mandatory yet to support this method
+		return nil
+	}
+	return err
+}
+
+// RevokeExternalConnectivity method is invoked to remove any external connectivity programming related to the endpoint.
+func (d *driver) RevokeExternalConnectivity(nid, eid string) error {
+	data := &api.RevokeExternalConnectivityRequest{
+		NetworkID:  nid,
+		EndpointID: eid,
+	}
+	err := d.call("RevokeExternalConnectivity", data, &api.RevokeExternalConnectivityResponse{})
+	if err != nil && strings.Contains(err.Error(), missingMethod) {
+		// It is not mandatory yet to support this method
+		return nil
+	}
+	return err
+}
+
 func (d *driver) Type() string {
 	return d.networkType
 }

+ 8 - 0
libnetwork/drivers/windows/windows.go

@@ -509,6 +509,14 @@ func (d *driver) Leave(nid, eid string) error {
 	return nil
 }
 
+func (d *driver) ProgramExternalConnectivity(nid, eid string, options map[string]interface{}) error {
+	return nil
+}
+
+func (d *driver) RevokeExternalConnectivity(nid, eid string) error {
+	return nil
+}
+
 func (d *driver) Type() string {
 	return d.name
 }

+ 74 - 46
libnetwork/endpoint.go

@@ -359,22 +359,16 @@ func (ep *endpoint) Join(sbox Sandbox, options ...EndpointOption) error {
 	sb.joinLeaveStart()
 	defer sb.joinLeaveEnd()
 
-	return ep.sbJoin(sbox, options...)
+	return ep.sbJoin(sb, options...)
 }
 
-func (ep *endpoint) sbJoin(sbox Sandbox, options ...EndpointOption) error {
-	var err error
-	sb, ok := sbox.(*sandbox)
-	if !ok {
-		return types.BadRequestErrorf("not a valid Sandbox interface")
-	}
-
-	network, err := ep.getNetworkFromStore()
+func (ep *endpoint) sbJoin(sb *sandbox, options ...EndpointOption) error {
+	n, err := ep.getNetworkFromStore()
 	if err != nil {
 		return fmt.Errorf("failed to get network from store during join: %v", err)
 	}
 
-	ep, err = network.getEndpointFromStore(ep.ID())
+	ep, err = n.getEndpointFromStore(ep.ID())
 	if err != nil {
 		return fmt.Errorf("failed to get endpoint from store during join: %v", err)
 	}
@@ -384,11 +378,8 @@ func (ep *endpoint) sbJoin(sbox Sandbox, options ...EndpointOption) error {
 		ep.Unlock()
 		return types.ForbiddenErrorf("another container is attached to the same network endpoint")
 	}
-	ep.Unlock()
-
-	ep.Lock()
-	ep.network = network
-	ep.sandboxID = sbox.ID()
+	ep.network = n
+	ep.sandboxID = sb.ID()
 	ep.joinInfo = &endpointJoinInfo{}
 	epid := ep.id
 	ep.Unlock()
@@ -400,32 +391,29 @@ func (ep *endpoint) sbJoin(sbox Sandbox, options ...EndpointOption) error {
 		}
 	}()
 
-	network.Lock()
-	nid := network.id
-	network.Unlock()
+	nid := n.ID()
 
 	ep.processOptions(options...)
 
-	driver, err := network.driver(true)
+	d, err := n.driver(true)
 	if err != nil {
 		return fmt.Errorf("failed to join endpoint: %v", err)
 	}
 
-	err = driver.Join(nid, epid, sbox.Key(), ep, sbox.Labels())
+	err = d.Join(nid, epid, sb.Key(), ep, sb.Labels())
 	if err != nil {
 		return err
 	}
 	defer func() {
 		if err != nil {
-			// Do not alter global err variable, it's needed by the previous defer
-			if err := driver.Leave(nid, epid); err != nil {
+			if err := d.Leave(nid, epid); err != nil {
 				log.Warnf("driver leave failed while rolling back join: %v", err)
 			}
 		}
 	}()
 
 	// Watch for service records
-	network.getController().watchSvcRecord(ep)
+	n.getController().watchSvcRecord(ep)
 
 	address := ""
 	if ip := ep.getFirstInterfaceAddress(); ip != nil {
@@ -434,27 +422,23 @@ func (ep *endpoint) sbJoin(sbox Sandbox, options ...EndpointOption) error {
 	if err = sb.updateHostsFile(address); err != nil {
 		return err
 	}
-	if err = sb.updateDNS(network.enableIPv6); err != nil {
+	if err = sb.updateDNS(n.enableIPv6); err != nil {
 		return err
 	}
 
-	if err = network.getController().updateToStore(ep); err != nil {
+	if err = n.getController().updateToStore(ep); err != nil {
 		return err
 	}
 
+	// Current endpoint providing external connectivity for the sandbox
+	extEp := sb.getGatewayEndpoint()
+
 	sb.Lock()
 	heap.Push(&sb.endpoints, ep)
 	sb.Unlock()
 	defer func() {
 		if err != nil {
-			for i, e := range sb.getConnectedEndpoints() {
-				if e == ep {
-					sb.Lock()
-					heap.Remove(&sb.endpoints, i)
-					sb.Unlock()
-					return
-				}
-			}
+			sb.removeEndpoint(ep)
 		}
 	}()
 
@@ -463,9 +447,29 @@ func (ep *endpoint) sbJoin(sbox Sandbox, options ...EndpointOption) error {
 	}
 
 	if sb.needDefaultGW() {
-		return sb.setupDefaultGW(ep)
+		return sb.setupDefaultGW()
 	}
-	return nil
+
+	moveExtConn := sb.getGatewayEndpoint() != extEp
+
+	if moveExtConn {
+		if extEp != nil {
+			log.Debugf("Revoking external connectivity on endpoint %s (%s)", extEp.Name(), extEp.ID())
+			if err := d.RevokeExternalConnectivity(extEp.network.ID(), extEp.ID()); err != nil {
+				log.Warnf("driver failed revoking external connectivity on endpoint %s (%s): %v",
+					extEp.Name(), extEp.ID(), err)
+			}
+		}
+		if !n.internal {
+			log.Debugf("Programming external connectivity on endpoint %s (%s)", ep.Name(), ep.ID())
+			if err := d.ProgramExternalConnectivity(n.ID(), ep.ID(), sb.Labels()); err != nil {
+				log.Warnf("driver failed programming external connectivity on endpoint %s (%s): %v",
+					extEp.Name(), extEp.ID(), err)
+			}
+		}
+	}
+
+	return sb.clearDefaultGW()
 }
 
 func (ep *endpoint) rename(name string) error {
@@ -533,15 +537,10 @@ func (ep *endpoint) Leave(sbox Sandbox, options ...EndpointOption) error {
 	sb.joinLeaveStart()
 	defer sb.joinLeaveEnd()
 
-	return ep.sbLeave(sbox, false, options...)
+	return ep.sbLeave(sb, false, options...)
 }
 
-func (ep *endpoint) sbLeave(sbox Sandbox, force bool, options ...EndpointOption) error {
-	sb, ok := sbox.(*sandbox)
-	if !ok {
-		return types.BadRequestErrorf("not a valid Sandbox interface")
-	}
-
+func (ep *endpoint) sbLeave(sb *sandbox, force bool, options ...EndpointOption) error {
 	n, err := ep.getNetworkFromStore()
 	if err != nil {
 		return fmt.Errorf("failed to get network from store during leave: %v", err)
@@ -559,8 +558,8 @@ func (ep *endpoint) sbLeave(sbox Sandbox, force bool, options ...EndpointOption)
 	if sid == "" {
 		return types.ForbiddenErrorf("cannot leave endpoint with no attached sandbox")
 	}
-	if sid != sbox.ID() {
-		return types.ForbiddenErrorf("unexpected sandbox ID in leave request. Expected %s. Got %s", ep.sandboxID, sbox.ID())
+	if sid != sb.ID() {
+		return types.ForbiddenErrorf("unexpected sandbox ID in leave request. Expected %s. Got %s", ep.sandboxID, sb.ID())
 	}
 
 	ep.processOptions(options...)
@@ -575,7 +574,19 @@ func (ep *endpoint) sbLeave(sbox Sandbox, force bool, options ...EndpointOption)
 	ep.network = n
 	ep.Unlock()
 
+	// Current endpoint providing external connectivity to the sandbox
+	extEp := sb.getGatewayEndpoint()
+	moveExtConn := extEp != nil && (extEp.ID() == ep.ID())
+
 	if d != nil {
+		if moveExtConn {
+			log.Debugf("Revoking external connectivity on endpoint %s (%s)", ep.Name(), ep.ID())
+			if err := d.RevokeExternalConnectivity(n.id, ep.id); err != nil {
+				log.Warnf("driver failed removing external connectivity on endpoint %s (%s): %v",
+					ep.Name(), ep.ID(), err)
+			}
+		}
+
 		if err := d.Leave(n.id, ep.id); err != nil {
 			if _, ok := err.(types.MaskableError); !ok {
 				log.Warnf("driver error disconnecting container %s : %v", ep.name, err)
@@ -597,7 +608,24 @@ func (ep *endpoint) sbLeave(sbox Sandbox, force bool, options ...EndpointOption)
 	}
 
 	sb.deleteHostsEntries(n.getSvcRecords(ep))
-	return nil
+	if !sb.inDelete && sb.needDefaultGW() {
+		if sb.getEPwithoutGateway() == nil {
+			return fmt.Errorf("endpoint without GW expected, but not found")
+		}
+		return sb.setupDefaultGW()
+	}
+
+	// New endpoint providing external connectivity for the sandbox
+	extEp = sb.getGatewayEndpoint()
+	if moveExtConn && extEp != nil {
+		log.Debugf("Programming external connectivity on endpoint %s (%s)", extEp.Name(), extEp.ID())
+		if err := d.ProgramExternalConnectivity(extEp.network.ID(), extEp.ID(), sb.Labels()); err != nil {
+			log.Warnf("driver failed programming external connectivity on endpoint %s: (%s) %v",
+				extEp.Name(), extEp.ID(), err)
+		}
+	}
+
+	return sb.clearDefaultGW()
 }
 
 func (n *network) validateForceDelete(locator string) error {
@@ -643,7 +671,7 @@ func (ep *endpoint) Delete(force bool) error {
 	}
 
 	if sb != nil {
-		if e := ep.sbLeave(sb, force); e != nil {
+		if e := ep.sbLeave(sb.(*sandbox), force); e != nil {
 			log.Warnf("failed to leave sandbox for endpoint %s : %v", name, e)
 		}
 	}

+ 6 - 0
libnetwork/libnetwork_internal_test.go

@@ -455,3 +455,9 @@ func (b *badDriver) DiscoverDelete(dType discoverapi.DiscoveryType, data interfa
 func (b *badDriver) Type() string {
 	return badDriverName
 }
+func (b *badDriver) ProgramExternalConnectivity(nid, eid string, options map[string]interface{}) error {
+	return nil
+}
+func (b *badDriver) RevokeExternalConnectivity(nid, eid string) error {
+	return nil
+}

+ 21 - 9
libnetwork/libnetwork_test.go

@@ -283,8 +283,28 @@ func TestBridge(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
+	defer func() {
+		if err := network.Delete(); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	ep, err := network.CreateEndpoint("testep")
+	if err != nil {
+		t.Fatal(err)
+	}
 
-	ep, err := network.CreateEndpoint("testep", libnetwork.CreateOptionPortMapping(getPortMapping()))
+	sb, err := controller.NewSandbox(containerID, libnetwork.OptionPortMapping(getPortMapping()))
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		if err := sb.Delete(); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	err = ep.Join(sb)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -304,14 +324,6 @@ func TestBridge(t *testing.T) {
 	if len(pm) != 5 {
 		t.Fatalf("Incomplete data for port mapping in endpoint operational data: %d", len(pm))
 	}
-
-	if err := ep.Delete(false); err != nil {
-		t.Fatal(err)
-	}
-
-	if err := network.Delete(); err != nil {
-		t.Fatal(err)
-	}
 }
 
 // Testing IPV6 from MAC address

+ 67 - 10
libnetwork/sandbox.go

@@ -10,6 +10,7 @@ import (
 
 	log "github.com/Sirupsen/logrus"
 	"github.com/docker/libnetwork/etchosts"
+	"github.com/docker/libnetwork/netlabel"
 	"github.com/docker/libnetwork/osl"
 	"github.com/docker/libnetwork/types"
 )
@@ -118,6 +119,7 @@ type containerConfig struct {
 	useDefaultSandBox bool
 	useExternalKey    bool
 	prio              int // higher the value, more the priority
+	exposedPorts      []types.TransportPort
 }
 
 func (sb *sandbox) ID() string {
@@ -136,7 +138,13 @@ func (sb *sandbox) Key() string {
 }
 
 func (sb *sandbox) Labels() map[string]interface{} {
-	return sb.config.generic
+	sb.Lock()
+	sb.Unlock()
+	opts := make(map[string]interface{}, len(sb.config.generic))
+	for k, v := range sb.config.generic {
+		opts[k] = v
+	}
+	return opts
 }
 
 func (sb *sandbox) Statistics() (map[string]*types.InterfaceStatistics, error) {
@@ -329,6 +337,18 @@ func (sb *sandbox) getConnectedEndpoints() []*endpoint {
 	return eps
 }
 
+func (sb *sandbox) removeEndpoint(ep *endpoint) {
+	sb.Lock()
+	defer sb.Unlock()
+
+	for i, e := range sb.endpoints {
+		if e == ep {
+			heap.Remove(&sb.endpoints, i)
+			return
+		}
+	}
+}
+
 func (sb *sandbox) getEndpoint(id string) *endpoint {
 	sb.Lock()
 	defer sb.Unlock()
@@ -631,14 +651,9 @@ func (sb *sandbox) populateNetworkResources(ep *endpoint) error {
 		}
 	}
 
-	for _, gwep := range sb.getConnectedEndpoints() {
-		if len(gwep.Gateway()) > 0 {
-			if gwep != ep {
-				break
-			}
-			if err := sb.updateGateway(gwep); err != nil {
-				return err
-			}
+	if ep == sb.getGatewayEndpoint() {
+		if err := sb.updateGateway(ep); err != nil {
+			return err
 		}
 	}
 
@@ -749,6 +764,13 @@ func (sb *sandbox) joinLeaveEnd() {
 	}
 }
 
+func (sb *sandbox) hasPortConfigs() bool {
+	opts := sb.Labels()
+	_, hasExpPorts := opts[netlabel.ExposedPorts]
+	_, hasPortMaps := opts[netlabel.PortMap]
+	return hasExpPorts || hasPortMaps
+}
+
 // OptionHostname function returns an option setter for hostname option to
 // be passed to NewSandbox method.
 func OptionHostname(name string) SandboxOption {
@@ -858,7 +880,42 @@ func OptionUseExternalKey() SandboxOption {
 // net container creation method. Container Labels are a good example.
 func OptionGeneric(generic map[string]interface{}) SandboxOption {
 	return func(sb *sandbox) {
-		sb.config.generic = generic
+		if sb.config.generic == nil {
+			sb.config.generic = make(map[string]interface{}, len(generic))
+		}
+		for k, v := range generic {
+			sb.config.generic[k] = v
+		}
+	}
+}
+
+// OptionExposedPorts function returns an option setter for the container exposed
+// ports option to be passed to container Create method.
+func OptionExposedPorts(exposedPorts []types.TransportPort) SandboxOption {
+	return func(sb *sandbox) {
+		if sb.config.generic == nil {
+			sb.config.generic = make(map[string]interface{})
+		}
+		// Defensive copy
+		eps := make([]types.TransportPort, len(exposedPorts))
+		copy(eps, exposedPorts)
+		// Store endpoint label and in generic because driver needs it
+		sb.config.exposedPorts = eps
+		sb.config.generic[netlabel.ExposedPorts] = eps
+	}
+}
+
+// OptionPortMapping function returns an option setter for the mapping
+// ports option to be passed to container Create method.
+func OptionPortMapping(portBindings []types.PortBinding) SandboxOption {
+	return func(sb *sandbox) {
+		if sb.config.generic == nil {
+			sb.config.generic = make(map[string]interface{})
+		}
+		// Store a copy of the bindings as generic data to pass to the driver
+		pbs := make([]types.PortBinding, len(portBindings))
+		copy(pbs, portBindings)
+		sb.config.generic[netlabel.PortMap] = pbs
 	}
 }