github.com/telepresenceio/telepresence/v2@v2.20.0-pro.6.0.20240517030216-236ea954e789/pkg/client/docker/network.go (about)

     1  package docker
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math/rand"
     7  	"strings"
     8  
     9  	"github.com/docker/docker/api/types"
    10  	"github.com/docker/docker/api/types/network"
    11  	dockerClient "github.com/docker/docker/client"
    12  
    13  	"github.com/datawire/dlib/dlog"
    14  )
    15  
    16  // EnsureNetwork checks if a network with the given name exists, and creates it if that is not the case.
    17  func EnsureNetwork(ctx context.Context, name string) error {
    18  	cli, err := GetClient(ctx)
    19  	if err != nil {
    20  		return err
    21  	}
    22  	resource, err := cli.NetworkInspect(ctx, name, types.NetworkInspectOptions{})
    23  	if err != nil {
    24  		if !dockerClient.IsErrNotFound(err) {
    25  			return fmt.Errorf("docker network inspect failed: %w", err)
    26  		}
    27  	} else {
    28  		// this is required, or services like apache will fail to do DNS lookups (even
    29  		// if IPv6 is not used).
    30  		if resource.EnableIPv6 {
    31  			dlog.Debugf(ctx, "found IPv6 enabled network %s", name)
    32  			return nil
    33  		}
    34  		dlog.Infof(ctx, "network %s does not have IPv6 enabled. Will attempt to recreate it", name)
    35  		if err = cli.NetworkRemove(ctx, name); err != nil {
    36  			dlog.Warnf(ctx, "failed to remove network %s. A network without IPv6 can impact DNS badly, even when IPv6 is not used", name)
    37  		}
    38  	}
    39  
    40  	// Make an attempt to create the network with IPv6 enabled. This will fail unless the user has enabled
    41  	// IPv6 in /etc/docker/daemon.json.
    42  	rsp, err := cli.NetworkCreate(ctx, name, types.NetworkCreate{
    43  		CheckDuplicate: false,
    44  		Driver:         "bridge",
    45  		Scope:          "local",
    46  		EnableIPv6:     true,
    47  	})
    48  
    49  	if err == nil {
    50  		// Creation of the IPv6 enabled network succeeded, so we're done here.
    51  		if rsp.Warning != "" {
    52  			dlog.Warningf(ctx, "network create %s: %s", name, rsp.Warning)
    53  		} else {
    54  			dlog.Debugf(ctx, "network create: %s", name)
    55  		}
    56  		return nil
    57  	}
    58  
    59  	// IPv6 is probably not configured in /etc/docker/daemon.yaml.
    60  	if !strings.Contains(err.Error(), "non-overlapping IPv6 address") {
    61  		// Some other error prevented us from creating the network.
    62  		return err
    63  	}
    64  
    65  	// Adding an IPv6 subnet and gateway in addition to enabling IPv6 should work even
    66  	// when no IPv6 is enabled in /etc/docker/daemon.yaml
    67  
    68  	// First, create a dummy network without IPv6 so that we get a proper IPAM config
    69  	dummyNet, err := cli.NetworkCreate(ctx, fmt.Sprintf("tp-dummy-%08x", rand.Int31()), types.NetworkCreate{
    70  		CheckDuplicate: false,
    71  		Driver:         "bridge",
    72  		Scope:          "local",
    73  		EnableIPv6:     false,
    74  	})
    75  	if err != nil {
    76  		return nil
    77  	}
    78  	resource, err = cli.NetworkInspect(ctx, dummyNet.ID, types.NetworkInspectOptions{})
    79  	if dummyErr := cli.NetworkRemove(ctx, dummyNet.ID); dummyErr != nil {
    80  		dlog.Warnf(ctx, "failed to remove network %s: %v", dummyNet.ID, dummyErr)
    81  	}
    82  	if err != nil {
    83  		return err
    84  	}
    85  	ipam := &resource.IPAM
    86  
    87  	// Save original Config to use a source for copy
    88  	numConfigs := len(ipam.Config)
    89  	ipamConfig := make([]network.IPAMConfig, numConfigs)
    90  	copy(ipamConfig, ipam.Config)
    91  
    92  	// Make attempts to add a random IPv6 subnet and gateway to IPAM
    93  	for retry := 0; retry < 5; retry++ {
    94  		ipn := fmt.Sprintf("%016x", rand.Int63())
    95  		ipn = fmt.Sprintf("%s:%s:%s:%s::", ipn[:4], ipn[4:8], ipn[8:12], ipn[12:])
    96  		ipam.Config = make([]network.IPAMConfig, numConfigs+1)
    97  		copy(ipam.Config, ipamConfig)
    98  		ipam.Config[numConfigs] = network.IPAMConfig{
    99  			Subnet:  fmt.Sprintf("%s/64", ipn),
   100  			Gateway: fmt.Sprintf("%s1", ipn),
   101  		}
   102  
   103  		// Create the IPv6 enabled network
   104  		_, err = cli.NetworkCreate(ctx, name, types.NetworkCreate{
   105  			CheckDuplicate: false,
   106  			Driver:         "bridge",
   107  			Scope:          "local",
   108  			EnableIPv6:     true,
   109  			IPAM:           ipam,
   110  		})
   111  		if err == nil {
   112  			return nil
   113  		}
   114  		err = fmt.Errorf("failed to create network with random IPv6 subnet: %w", err)
   115  		dlog.Debug(ctx, err)
   116  	}
   117  	return err
   118  }