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 }