github.com/khulnasoft-lab/khulnasoft@v26.0.1-0.20240328202558-330a6f959fe0+incompatible/libnetwork/sandbox.go (about)

     1  package libnetwork
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"net"
     8  	"sort"
     9  	"strings"
    10  	"sync"
    11  
    12  	"github.com/containerd/log"
    13  	"github.com/docker/docker/libnetwork/etchosts"
    14  	"github.com/docker/docker/libnetwork/osl"
    15  	"github.com/docker/docker/libnetwork/types"
    16  	"go.opentelemetry.io/otel"
    17  	"go.opentelemetry.io/otel/attribute"
    18  	"go.opentelemetry.io/otel/trace"
    19  )
    20  
    21  // SandboxOption is an option setter function type used to pass various options to
    22  // NewNetContainer method. The various setter functions of type SandboxOption are
    23  // provided by libnetwork, they look like ContainerOptionXXXX(...)
    24  type SandboxOption func(sb *Sandbox)
    25  
    26  func (sb *Sandbox) processOptions(options ...SandboxOption) {
    27  	for _, opt := range options {
    28  		if opt != nil {
    29  			opt(sb)
    30  		}
    31  	}
    32  }
    33  
    34  // Sandbox provides the control over the network container entity.
    35  // It is a one to one mapping with the container.
    36  type Sandbox struct {
    37  	id                 string
    38  	containerID        string
    39  	config             containerConfig
    40  	extDNS             []extDNSEntry
    41  	osSbox             *osl.Namespace
    42  	controller         *Controller
    43  	resolver           *Resolver
    44  	resolverOnce       sync.Once
    45  	endpoints          []*Endpoint
    46  	epPriority         map[string]int
    47  	populatedEndpoints map[string]struct{}
    48  	joinLeaveDone      chan struct{}
    49  	dbIndex            uint64
    50  	dbExists           bool
    51  	isStub             bool
    52  	inDelete           bool
    53  	ingress            bool
    54  	ndotsSet           bool
    55  	oslTypes           []osl.SandboxType // slice of properties of this sandbox
    56  	loadBalancerNID    string            // NID that this SB is a load balancer for
    57  	mu                 sync.Mutex
    58  	// This mutex is used to serialize service related operation for an endpoint
    59  	// The lock is here because the endpoint is saved into the store so is not unique
    60  	service sync.Mutex
    61  }
    62  
    63  // These are the container configs used to customize container /etc/hosts file.
    64  type hostsPathConfig struct {
    65  	hostName        string
    66  	domainName      string
    67  	hostsPath       string
    68  	originHostsPath string
    69  	extraHosts      []extraHost
    70  	parentUpdates   []parentUpdate
    71  }
    72  
    73  type parentUpdate struct {
    74  	cid  string
    75  	name string
    76  	ip   string
    77  }
    78  
    79  type extraHost struct {
    80  	name string
    81  	IP   string
    82  }
    83  
    84  // These are the container configs used to customize container /etc/resolv.conf file.
    85  type resolvConfPathConfig struct {
    86  	resolvConfPath       string
    87  	originResolvConfPath string
    88  	resolvConfHashFile   string
    89  	dnsList              []string
    90  	dnsSearchList        []string
    91  	dnsOptionsList       []string
    92  }
    93  
    94  type containerConfig struct {
    95  	hostsPathConfig
    96  	resolvConfPathConfig
    97  	generic           map[string]interface{}
    98  	useDefaultSandBox bool
    99  	useExternalKey    bool
   100  	exposedPorts      []types.TransportPort
   101  }
   102  
   103  // ID returns the ID of the sandbox.
   104  func (sb *Sandbox) ID() string {
   105  	return sb.id
   106  }
   107  
   108  // ContainerID returns the container id associated to this sandbox.
   109  func (sb *Sandbox) ContainerID() string {
   110  	return sb.containerID
   111  }
   112  
   113  // Key returns the sandbox's key.
   114  func (sb *Sandbox) Key() string {
   115  	if sb.config.useDefaultSandBox {
   116  		return osl.GenerateKey("default")
   117  	}
   118  	return osl.GenerateKey(sb.id)
   119  }
   120  
   121  // Labels returns the sandbox's labels.
   122  func (sb *Sandbox) Labels() map[string]interface{} {
   123  	sb.mu.Lock()
   124  	defer sb.mu.Unlock()
   125  	opts := make(map[string]interface{}, len(sb.config.generic))
   126  	for k, v := range sb.config.generic {
   127  		opts[k] = v
   128  	}
   129  	return opts
   130  }
   131  
   132  // Delete destroys this container after detaching it from all connected endpoints.
   133  func (sb *Sandbox) Delete() error {
   134  	return sb.delete(false)
   135  }
   136  
   137  func (sb *Sandbox) delete(force bool) error {
   138  	sb.mu.Lock()
   139  	if sb.inDelete {
   140  		sb.mu.Unlock()
   141  		return types.ForbiddenErrorf("another sandbox delete in progress")
   142  	}
   143  	// Set the inDelete flag. This will ensure that we don't
   144  	// update the store until we have completed all the endpoint
   145  	// leaves and deletes. And when endpoint leaves and deletes
   146  	// are completed then we can finally delete the sandbox object
   147  	// altogether from the data store. If the daemon exits
   148  	// ungracefully in the middle of a sandbox delete this way we
   149  	// will have all the references to the endpoints in the
   150  	// sandbox so that we can clean them up when we restart
   151  	sb.inDelete = true
   152  	sb.mu.Unlock()
   153  
   154  	c := sb.controller
   155  
   156  	// Detach from all endpoints
   157  	retain := false
   158  	for _, ep := range sb.Endpoints() {
   159  		// gw network endpoint detach and removal are automatic
   160  		if ep.endpointInGWNetwork() && !force {
   161  			continue
   162  		}
   163  		// Retain the sanbdox if we can't obtain the network from store.
   164  		if _, err := c.getNetworkFromStore(ep.getNetwork().ID()); err != nil {
   165  			if !c.isSwarmNode() {
   166  				retain = true
   167  			}
   168  			log.G(context.TODO()).Warnf("Failed getting network for ep %s during sandbox %s delete: %v", ep.ID(), sb.ID(), err)
   169  			continue
   170  		}
   171  
   172  		if !force {
   173  			if err := ep.Leave(sb); err != nil {
   174  				log.G(context.TODO()).Warnf("Failed detaching sandbox %s from endpoint %s: %v\n", sb.ID(), ep.ID(), err)
   175  			}
   176  		}
   177  
   178  		if err := ep.Delete(force); err != nil {
   179  			log.G(context.TODO()).Warnf("Failed deleting endpoint %s: %v\n", ep.ID(), err)
   180  		}
   181  	}
   182  
   183  	if retain {
   184  		sb.mu.Lock()
   185  		sb.inDelete = false
   186  		sb.mu.Unlock()
   187  		return fmt.Errorf("could not cleanup all the endpoints in container %s / sandbox %s", sb.containerID, sb.id)
   188  	}
   189  	// Container is going away. Path cache in etchosts is most
   190  	// likely not required any more. Drop it.
   191  	etchosts.Drop(sb.config.hostsPath)
   192  
   193  	if sb.resolver != nil {
   194  		sb.resolver.Stop()
   195  	}
   196  
   197  	if sb.osSbox != nil && !sb.config.useDefaultSandBox {
   198  		if err := sb.osSbox.Destroy(); err != nil {
   199  			log.G(context.TODO()).WithError(err).Warn("error destroying network sandbox")
   200  		}
   201  	}
   202  
   203  	if err := sb.storeDelete(); err != nil {
   204  		log.G(context.TODO()).Warnf("Failed to delete sandbox %s from store: %v", sb.ID(), err)
   205  	}
   206  
   207  	c.mu.Lock()
   208  	if sb.ingress {
   209  		c.ingressSandbox = nil
   210  	}
   211  	delete(c.sandboxes, sb.ID())
   212  	c.mu.Unlock()
   213  
   214  	return nil
   215  }
   216  
   217  // Rename changes the name of all attached Endpoints.
   218  func (sb *Sandbox) Rename(name string) error {
   219  	var err error
   220  
   221  	for _, ep := range sb.Endpoints() {
   222  		if ep.endpointInGWNetwork() {
   223  			continue
   224  		}
   225  
   226  		oldName := ep.Name()
   227  		lEp := ep
   228  		if err = ep.rename(name); err != nil {
   229  			break
   230  		}
   231  
   232  		defer func() {
   233  			if err != nil {
   234  				if err2 := lEp.rename(oldName); err2 != nil {
   235  					log.G(context.TODO()).WithField("old", oldName).WithField("origError", err).WithError(err2).Error("error renaming sandbox")
   236  				}
   237  			}
   238  		}()
   239  	}
   240  
   241  	return err
   242  }
   243  
   244  // Refresh leaves all the endpoints, resets and re-applies the options,
   245  // re-joins all the endpoints without destroying the osl sandbox
   246  func (sb *Sandbox) Refresh(options ...SandboxOption) error {
   247  	// Store connected endpoints
   248  	epList := sb.Endpoints()
   249  
   250  	// Detach from all endpoints
   251  	for _, ep := range epList {
   252  		if err := ep.Leave(sb); err != nil {
   253  			log.G(context.TODO()).Warnf("Failed detaching sandbox %s from endpoint %s: %v\n", sb.ID(), ep.ID(), err)
   254  		}
   255  	}
   256  
   257  	// Re-apply options
   258  	sb.config = containerConfig{}
   259  	sb.processOptions(options...)
   260  
   261  	// Setup discovery files
   262  	if err := sb.setupResolutionFiles(); err != nil {
   263  		return err
   264  	}
   265  
   266  	// Re-connect to all endpoints
   267  	for _, ep := range epList {
   268  		if err := ep.Join(sb); err != nil {
   269  			log.G(context.TODO()).Warnf("Failed attach sandbox %s to endpoint %s: %v\n", sb.ID(), ep.ID(), err)
   270  		}
   271  	}
   272  
   273  	return nil
   274  }
   275  
   276  func (sb *Sandbox) MarshalJSON() ([]byte, error) {
   277  	sb.mu.Lock()
   278  	defer sb.mu.Unlock()
   279  
   280  	// We are just interested in the container ID. This can be expanded to include all of containerInfo if there is a need
   281  	return json.Marshal(sb.id)
   282  }
   283  
   284  func (sb *Sandbox) UnmarshalJSON(b []byte) (err error) {
   285  	sb.mu.Lock()
   286  	defer sb.mu.Unlock()
   287  
   288  	var id string
   289  	if err := json.Unmarshal(b, &id); err != nil {
   290  		return err
   291  	}
   292  	sb.id = id
   293  	return nil
   294  }
   295  
   296  // Endpoints returns all the endpoints connected to the sandbox.
   297  func (sb *Sandbox) Endpoints() []*Endpoint {
   298  	sb.mu.Lock()
   299  	defer sb.mu.Unlock()
   300  
   301  	eps := make([]*Endpoint, len(sb.endpoints))
   302  	copy(eps, sb.endpoints)
   303  
   304  	return eps
   305  }
   306  
   307  func (sb *Sandbox) addEndpoint(ep *Endpoint) {
   308  	sb.mu.Lock()
   309  	defer sb.mu.Unlock()
   310  
   311  	l := len(sb.endpoints)
   312  	i := sort.Search(l, func(j int) bool {
   313  		return ep.Less(sb.endpoints[j])
   314  	})
   315  
   316  	sb.endpoints = append(sb.endpoints, nil)
   317  	copy(sb.endpoints[i+1:], sb.endpoints[i:])
   318  	sb.endpoints[i] = ep
   319  }
   320  
   321  func (sb *Sandbox) removeEndpoint(ep *Endpoint) {
   322  	sb.mu.Lock()
   323  	defer sb.mu.Unlock()
   324  
   325  	sb.removeEndpointRaw(ep)
   326  }
   327  
   328  func (sb *Sandbox) removeEndpointRaw(ep *Endpoint) {
   329  	for i, e := range sb.endpoints {
   330  		if e == ep {
   331  			sb.endpoints = append(sb.endpoints[:i], sb.endpoints[i+1:]...)
   332  			return
   333  		}
   334  	}
   335  }
   336  
   337  func (sb *Sandbox) GetEndpoint(id string) *Endpoint {
   338  	sb.mu.Lock()
   339  	defer sb.mu.Unlock()
   340  
   341  	for _, ep := range sb.endpoints {
   342  		if ep.id == id {
   343  			return ep
   344  		}
   345  	}
   346  
   347  	return nil
   348  }
   349  
   350  func (sb *Sandbox) HandleQueryResp(name string, ip net.IP) {
   351  	for _, ep := range sb.Endpoints() {
   352  		n := ep.getNetwork()
   353  		n.HandleQueryResp(name, ip)
   354  	}
   355  }
   356  
   357  func (sb *Sandbox) ResolveIP(ctx context.Context, ip string) string {
   358  	var svc string
   359  	log.G(ctx).Debugf("IP To resolve %v", ip)
   360  
   361  	for _, ep := range sb.Endpoints() {
   362  		n := ep.getNetwork()
   363  		svc = n.ResolveIP(ctx, ip)
   364  		if len(svc) != 0 {
   365  			return svc
   366  		}
   367  	}
   368  
   369  	return svc
   370  }
   371  
   372  // ResolveService returns all the backend details about the containers or hosts
   373  // backing a service. Its purpose is to satisfy an SRV query.
   374  func (sb *Sandbox) ResolveService(ctx context.Context, name string) ([]*net.SRV, []net.IP) {
   375  	log.G(ctx).Debugf("Service name To resolve: %v", name)
   376  
   377  	// There are DNS implementations that allow SRV queries for names not in
   378  	// the format defined by RFC 2782. Hence specific validations checks are
   379  	// not done
   380  	if parts := strings.SplitN(name, ".", 3); len(parts) < 3 {
   381  		return nil, nil
   382  	}
   383  
   384  	for _, ep := range sb.Endpoints() {
   385  		n := ep.getNetwork()
   386  
   387  		srv, ip := n.ResolveService(ctx, name)
   388  		if len(srv) > 0 {
   389  			return srv, ip
   390  		}
   391  	}
   392  	return nil, nil
   393  }
   394  
   395  func (sb *Sandbox) ResolveName(ctx context.Context, name string, ipType int) ([]net.IP, bool) {
   396  	// Embedded server owns the docker network domain. Resolution should work
   397  	// for both container_name and container_name.network_name
   398  	// We allow '.' in service name and network name. For a name a.b.c.d the
   399  	// following have to tried;
   400  	// {a.b.c.d in the networks container is connected to}
   401  	// {a.b.c in network d},
   402  	// {a.b in network c.d},
   403  	// {a in network b.c.d},
   404  
   405  	log.G(ctx).Debugf("Name To resolve: %v", name)
   406  	name = strings.TrimSuffix(name, ".")
   407  	reqName := []string{name}
   408  	networkName := []string{""}
   409  
   410  	if strings.Contains(name, ".") {
   411  		var i int
   412  		dup := name
   413  		for {
   414  			if i = strings.LastIndex(dup, "."); i == -1 {
   415  				break
   416  			}
   417  			networkName = append(networkName, name[i+1:])
   418  			reqName = append(reqName, name[:i])
   419  
   420  			dup = dup[:i]
   421  		}
   422  	}
   423  
   424  	epList := sb.Endpoints()
   425  
   426  	// In swarm mode, services with exposed ports are connected to user overlay
   427  	// network, ingress network and docker_gwbridge networks. Name resolution
   428  	// should prioritize returning the VIP/IPs on user overlay network.
   429  	//
   430  	// Re-order the endpoints based on the network-type they're attached to;
   431  	//
   432  	//  1. dynamic networks (user overlay networks)
   433  	//  2. ingress network(s)
   434  	//  3. local networks ("docker_gwbridge")
   435  	if sb.controller.isSwarmNode() {
   436  		sort.Sort(ByNetworkType(epList))
   437  	}
   438  
   439  	for i := 0; i < len(reqName); i++ {
   440  		// First check for local container alias
   441  		ip, ipv6Miss := sb.resolveName(ctx, reqName[i], networkName[i], epList, true, ipType)
   442  		if ip != nil {
   443  			return ip, false
   444  		}
   445  		if ipv6Miss {
   446  			return ip, ipv6Miss
   447  		}
   448  
   449  		// Resolve the actual container name
   450  		ip, ipv6Miss = sb.resolveName(ctx, reqName[i], networkName[i], epList, false, ipType)
   451  		if ip != nil {
   452  			return ip, false
   453  		}
   454  		if ipv6Miss {
   455  			return ip, ipv6Miss
   456  		}
   457  	}
   458  	return nil, false
   459  }
   460  
   461  func (sb *Sandbox) resolveName(ctx context.Context, nameOrAlias string, networkName string, epList []*Endpoint, lookupAlias bool, ipType int) (_ []net.IP, ipv6Miss bool) {
   462  	ctx, span := otel.Tracer("").Start(ctx, "Sandbox.resolveName", trace.WithAttributes(
   463  		attribute.String("libnet.resolver.name-or-alias", nameOrAlias),
   464  		attribute.String("libnet.network.name", networkName),
   465  		attribute.Bool("libnet.resolver.alias-lookup", lookupAlias),
   466  		attribute.Int("libnet.resolver.ip-family", ipType)))
   467  	defer span.End()
   468  
   469  	for _, ep := range epList {
   470  		if lookupAlias && len(ep.aliases) == 0 {
   471  			continue
   472  		}
   473  
   474  		nw := ep.getNetwork()
   475  		if networkName != "" && networkName != nw.Name() {
   476  			continue
   477  		}
   478  
   479  		name := nameOrAlias
   480  		if lookupAlias {
   481  			ep.mu.Lock()
   482  			alias, ok := ep.aliases[nameOrAlias]
   483  			ep.mu.Unlock()
   484  			if !ok {
   485  				continue
   486  			}
   487  			name = alias
   488  		} else {
   489  			// If it is a regular lookup and if the requested name is an alias
   490  			// don't perform a svc lookup for this endpoint.
   491  			ep.mu.Lock()
   492  			_, ok := ep.aliases[nameOrAlias]
   493  			ep.mu.Unlock()
   494  			if ok {
   495  				continue
   496  			}
   497  		}
   498  
   499  		ip, miss := nw.ResolveName(ctx, name, ipType)
   500  		if ip != nil {
   501  			return ip, false
   502  		}
   503  		if miss {
   504  			ipv6Miss = miss
   505  		}
   506  	}
   507  	return nil, ipv6Miss
   508  }
   509  
   510  // EnableService makes a managed container's service available by adding the
   511  // endpoint to the service load balancer and service discovery.
   512  func (sb *Sandbox) EnableService() (err error) {
   513  	log.G(context.TODO()).Debugf("EnableService %s START", sb.containerID)
   514  	defer func() {
   515  		if err != nil {
   516  			if err2 := sb.DisableService(); err2 != nil {
   517  				log.G(context.TODO()).WithError(err2).WithField("origError", err).Error("Error while disabling service after original error")
   518  			}
   519  		}
   520  	}()
   521  	for _, ep := range sb.Endpoints() {
   522  		if !ep.isServiceEnabled() {
   523  			if err := ep.addServiceInfoToCluster(sb); err != nil {
   524  				return fmt.Errorf("could not update state for endpoint %s into cluster: %v", ep.Name(), err)
   525  			}
   526  			ep.enableService()
   527  		}
   528  	}
   529  	log.G(context.TODO()).Debugf("EnableService %s DONE", sb.containerID)
   530  	return nil
   531  }
   532  
   533  // DisableService removes a managed container's endpoints from the load balancer
   534  // and service discovery.
   535  func (sb *Sandbox) DisableService() (err error) {
   536  	log.G(context.TODO()).Debugf("DisableService %s START", sb.containerID)
   537  	failedEps := []string{}
   538  	defer func() {
   539  		if len(failedEps) > 0 {
   540  			err = fmt.Errorf("failed to disable service on sandbox:%s, for endpoints %s", sb.ID(), strings.Join(failedEps, ","))
   541  		}
   542  	}()
   543  	for _, ep := range sb.Endpoints() {
   544  		if ep.isServiceEnabled() {
   545  			if err := ep.deleteServiceInfoFromCluster(sb, false, "DisableService"); err != nil {
   546  				failedEps = append(failedEps, ep.Name())
   547  				log.G(context.TODO()).Warnf("failed update state for endpoint %s into cluster: %v", ep.Name(), err)
   548  			}
   549  			ep.disableService()
   550  		}
   551  	}
   552  	log.G(context.TODO()).Debugf("DisableService %s DONE", sb.containerID)
   553  	return nil
   554  }
   555  
   556  func (sb *Sandbox) clearNetworkResources(origEp *Endpoint) error {
   557  	ep := sb.GetEndpoint(origEp.id)
   558  	if ep == nil {
   559  		return fmt.Errorf("could not find the sandbox endpoint data for endpoint %s",
   560  			origEp.id)
   561  	}
   562  
   563  	sb.mu.Lock()
   564  	osSbox := sb.osSbox
   565  	inDelete := sb.inDelete
   566  	sb.mu.Unlock()
   567  	if osSbox != nil {
   568  		releaseOSSboxResources(osSbox, ep)
   569  	}
   570  
   571  	sb.mu.Lock()
   572  	delete(sb.populatedEndpoints, ep.ID())
   573  
   574  	if len(sb.endpoints) == 0 {
   575  		// sb.endpoints should never be empty and this is unexpected error condition
   576  		// We log an error message to note this down for debugging purposes.
   577  		log.G(context.TODO()).Errorf("No endpoints in sandbox while trying to remove endpoint %s", ep.Name())
   578  		sb.mu.Unlock()
   579  		return nil
   580  	}
   581  
   582  	var (
   583  		gwepBefore, gwepAfter *Endpoint
   584  		index                 = -1
   585  	)
   586  	for i, e := range sb.endpoints {
   587  		if e == ep {
   588  			index = i
   589  		}
   590  		if len(e.Gateway()) > 0 && gwepBefore == nil {
   591  			gwepBefore = e
   592  		}
   593  		if index != -1 && gwepBefore != nil {
   594  			break
   595  		}
   596  	}
   597  
   598  	if index == -1 {
   599  		log.G(context.TODO()).Warnf("Endpoint %s has already been deleted", ep.Name())
   600  		sb.mu.Unlock()
   601  		return nil
   602  	}
   603  
   604  	sb.removeEndpointRaw(ep)
   605  	for _, e := range sb.endpoints {
   606  		if len(e.Gateway()) > 0 {
   607  			gwepAfter = e
   608  			break
   609  		}
   610  	}
   611  	delete(sb.epPriority, ep.ID())
   612  	sb.mu.Unlock()
   613  
   614  	if gwepAfter != nil && gwepBefore != gwepAfter {
   615  		if err := sb.updateGateway(gwepAfter); err != nil {
   616  			return err
   617  		}
   618  	}
   619  
   620  	// Only update the store if we did not come here as part of
   621  	// sandbox delete. If we came here as part of delete then do
   622  	// not bother updating the store. The sandbox object will be
   623  	// deleted anyway
   624  	if !inDelete {
   625  		return sb.storeUpdate()
   626  	}
   627  
   628  	return nil
   629  }
   630  
   631  // joinLeaveStart waits to ensure there are no joins or leaves in progress and
   632  // marks this join/leave in progress without race
   633  func (sb *Sandbox) joinLeaveStart() {
   634  	sb.mu.Lock()
   635  	defer sb.mu.Unlock()
   636  
   637  	for sb.joinLeaveDone != nil {
   638  		joinLeaveDone := sb.joinLeaveDone
   639  		sb.mu.Unlock()
   640  
   641  		<-joinLeaveDone
   642  
   643  		sb.mu.Lock()
   644  	}
   645  
   646  	sb.joinLeaveDone = make(chan struct{})
   647  }
   648  
   649  // joinLeaveEnd marks the end of this join/leave operation and
   650  // signals the same without race to other join and leave waiters
   651  func (sb *Sandbox) joinLeaveEnd() {
   652  	sb.mu.Lock()
   653  	defer sb.mu.Unlock()
   654  
   655  	if sb.joinLeaveDone != nil {
   656  		close(sb.joinLeaveDone)
   657  		sb.joinLeaveDone = nil
   658  	}
   659  }
   660  
   661  // <=> Returns true if a < b, false if a > b and advances to next level if a == b
   662  // epi.prio <=> epj.prio           # 2 < 1
   663  // epi.gw <=> epj.gw               # non-gw < gw
   664  // epi.internal <=> epj.internal   # non-internal < internal
   665  // epi.joininfo <=> epj.joininfo   # ipv6 < ipv4
   666  // epi.name <=> epj.name           # bar < foo
   667  func (epi *Endpoint) Less(epj *Endpoint) bool {
   668  	var prioi, prioj int
   669  
   670  	sbi, _ := epi.getSandbox()
   671  	sbj, _ := epj.getSandbox()
   672  
   673  	// Prio defaults to 0
   674  	if sbi != nil {
   675  		prioi = sbi.epPriority[epi.ID()]
   676  	}
   677  	if sbj != nil {
   678  		prioj = sbj.epPriority[epj.ID()]
   679  	}
   680  
   681  	if prioi != prioj {
   682  		return prioi > prioj
   683  	}
   684  
   685  	gwi := epi.endpointInGWNetwork()
   686  	gwj := epj.endpointInGWNetwork()
   687  	if gwi != gwj {
   688  		return gwj
   689  	}
   690  
   691  	inti := epi.getNetwork().Internal()
   692  	intj := epj.getNetwork().Internal()
   693  	if inti != intj {
   694  		return intj
   695  	}
   696  
   697  	jii := 0
   698  	if epi.joinInfo != nil {
   699  		if epi.joinInfo.gw != nil {
   700  			jii = jii + 1
   701  		}
   702  		if epi.joinInfo.gw6 != nil {
   703  			jii = jii + 2
   704  		}
   705  	}
   706  
   707  	jij := 0
   708  	if epj.joinInfo != nil {
   709  		if epj.joinInfo.gw != nil {
   710  			jij = jij + 1
   711  		}
   712  		if epj.joinInfo.gw6 != nil {
   713  			jij = jij + 2
   714  		}
   715  	}
   716  
   717  	if jii != jij {
   718  		return jii > jij
   719  	}
   720  
   721  	return epi.network.Name() < epj.network.Name()
   722  }
   723  
   724  func (sb *Sandbox) NdotsSet() bool {
   725  	return sb.ndotsSet
   726  }