@@ -76,66 +76,159 @@ func CreateNetwork(ociBin, networkName, subnet, staticIP string) (net.IP, error)
7676 return CreateNetworkWithIPFamily (ociBin , networkName , subnet , "" , staticIP , "" , "ipv4" )
7777}
7878
79- func CreateNetworkWithIPFamily (ociBin , networkName , subnet , subnetv6 , staticIP , staticIPv6 , ipFamily string ) (net.IP , error ) {
80- bridgeName := defaultBridgeName (ociBin )
81- if networkName == bridgeName {
82- klog .Infof ("skipping creating network since default network %s was specified" , networkName )
83- return nil , nil
84- }
8579
86- // For IPv6-only or dual-stack networks, use the v6/dual creator
87- if ipFamily == "ipv6" || ipFamily == "dual" {
88- return createV6OrDualNetwork (ociBin , networkName , subnet , subnetv6 , ipFamily )
89- }
90-
91- // check if the network already exists
92- info , err := containerNetworkInspect (ociBin , networkName )
93- if err == nil {
94- klog .Infof ("Found existing network %+v" , info )
95- return info .gateway , nil
96- }
80+ func CreateNetworkWithIPFamily (ociBin , networkName , subnet , subnetv6 , staticIP , staticIPv6 , ipFamily string ) (net.IP , error ) {
81+ bridgeName := defaultBridgeName (ociBin )
82+ if networkName == bridgeName {
83+ klog .Infof ("skipping creating network since default network %s was specified" , networkName )
84+ return nil , nil
85+ }
9786
98- // will try to get MTU from the docker network to avoid issue with systems with exotic MTU settings.
99- // related issue #9528
100- info , err = containerNetworkInspect (ociBin , bridgeName )
101- if err != nil {
102- klog .Warningf ("failed to get mtu information from the %s's default network %q: %v" , ociBin , bridgeName , err )
103- }
87+ // If already exists, reuse.
88+ if info , err := containerNetworkInspect (ociBin , networkName ); err == nil {
89+ klog .Infof ("Found existing network %+v" , info )
90+ return info .gateway , nil
91+ }
10492
105- tries := 20
93+ // Learn MTU from the default bridge (best effort)
94+ bridgeInfo , berr := containerNetworkInspect (ociBin , bridgeName )
95+ if berr != nil {
96+ klog .Warningf ("failed to get mtu information from the %s's default network %q: %v" , ociBin , bridgeName , berr )
97+ }
98+ // ----- IPv6/dual flow -----
99+ if ipFamily == "ipv6" || ipFamily == "dual" {
100+ // Decide v6 subnet
101+ if subnetv6 == "" {
102+ if staticIPv6 != "" {
103+ if s , err := cidr64ForIP (staticIPv6 ); err == nil {
104+ subnetv6 = s
105+ } else {
106+ return nil , errors .Wrap (err , "derive /64 from --static-ipv6" )
107+ }
108+ } else {
109+ subnetv6 = firstSubnetAddrv6 (subnetv6 ) // default fd00::/64
110+ }
111+ }
112+
113+ // Build args; enable IPv6 always in this branch
114+ args := []string {"network" , "create" , "--driver=bridge" , "--ipv6" }
115+
116+ // For dual, also choose a free IPv4 subnet similar to the v4-only flow
117+ if ipFamily == "dual" {
118+ tries := 20
119+ start := firstSubnetAddr (subnet )
120+ if staticIP != "" {
121+ tries = 1
122+ start = staticIP
123+ }
124+
125+ // Try up to 5 candidate /24s starting at start, stepping by 9 (as before)
126+ var lastErr error
127+ for attempts , subnetAddr := 0 , start ; attempts < 5 ; attempts ++ {
128+ var p * network.Parameters
129+ p , lastErr = network .FreeSubnet (subnetAddr , 9 , tries )
130+ if lastErr != nil {
131+ klog .Errorf ("failed to find free IPv4 subnet for %s network %s after %d attempts: %v" , ociBin , networkName , 20 , lastErr )
132+ return nil , fmt .Errorf ("un-retryable: %w" , lastErr )
133+ }
134+
135+ argsWithV4 := append ([]string {}, args ... )
136+ argsWithV4 = append (argsWithV4 , "--subnet" , p .CIDR , "--gateway" , p .Gateway )
137+ argsWithV4 = append (argsWithV4 , "--subnet" , subnetv6 )
138+ if ociBin == Docker && bridgeInfo .mtu > 0 {
139+ argsWithV4 = append (argsWithV4 , "-o" , fmt .Sprintf ("com.docker.network.driver.mtu=%d" , bridgeInfo .mtu ))
140+ }
141+ argsWithV4 = append (argsWithV4 ,
142+ fmt .Sprintf ("--label=%s=%s" , CreatedByLabelKey , "true" ),
143+ fmt .Sprintf ("--label=%s=%s" , ProfileLabelKey , networkName ),
144+ networkName ,
145+ )
146+
147+ rr , err := runCmd (exec .Command (ociBin , argsWithV4 ... ))
148+ if err == nil {
149+ ni , _ := containerNetworkInspect (ociBin , networkName )
150+ return ni .gateway , nil
151+ }
152+
153+ out := rr .Output ()
154+ // Respect same retry conditions as v4-only
155+ if strings .Contains (out , "Pool overlaps" ) ||
156+ (strings .Contains (out , "failed to allocate gateway" ) && strings .Contains (out , "Address already in use" )) ||
157+ strings .Contains (out , "is being used by a network interface" ) ||
158+ strings .Contains (out , "is already used on the host or by another config" ) {
159+ klog .Warningf ("failed to create %s network %s %s (dual): %v; retrying with next IPv4 subnet" , ociBin , networkName , p .CIDR , err )
160+ subnetAddr = p .IP
161+ continue
162+ }
163+ // Non-retryable
164+ klog .Errorf ("error creating dual-stack network %s: %v" , networkName , err )
165+ return nil , fmt .Errorf ("un-retryable: %w" , err )
166+ }
167+ return nil , fmt .Errorf ("failed to create %s network %s (dual): %w" , ociBin , networkName , lastErr )
168+ }
169+
170+ // ipv6-only (no IPv4)
171+ args = append (args , "--subnet" , subnetv6 )
172+ if ociBin == Docker && bridgeInfo .mtu > 0 {
173+ args = append (args , "-o" , fmt .Sprintf ("com.docker.network.driver.mtu=%d" , bridgeInfo .mtu ))
174+ }
175+ args = append (args ,
176+ fmt .Sprintf ("--label=%s=%s" , CreatedByLabelKey , "true" ),
177+ fmt .Sprintf ("--label=%s=%s" , ProfileLabelKey , networkName ),
178+ networkName ,
179+ )
180+
181+ if _ , err := runCmd (exec .Command (ociBin , args ... )); err != nil {
182+ klog .Warningf ("failed to create %s network %q (ipv6-only): %v" , ociBin , networkName , err )
183+ return nil , fmt .Errorf ("create %s network %q: %w" , ociBin , networkName , err )
184+ }
185+ ni , _ := containerNetworkInspect (ociBin , networkName )
186+ return ni .gateway , nil
187+ }
106188
107- // we don't want to increment the subnet IP on network creation failure if the user specifies a static IP, so set tries to 1
108- if staticIP != "" {
109- tries = 1
110- subnet = staticIP
111- }
189+ // ----- IPv4-only flow (existing logic) -----
190+ // keep current implementation
191+ tries := 20
192+ if staticIP != "" {
193+ tries = 1
194+ subnet = staticIP
195+ }
196+ var lastErr error
197+ for attempts , subnetAddr := 0 , firstSubnetAddr (subnet ); attempts < 5 ; attempts ++ {
198+ var p * network.Parameters
199+ p , lastErr = network .FreeSubnet (subnetAddr , 9 , tries )
200+ if lastErr != nil {
201+ klog .Errorf ("failed to find free subnet for %s network %s after %d attempts: %v" , ociBin , networkName , 20 , lastErr )
202+ return nil , fmt .Errorf ("un-retryable: %w" , lastErr )
203+ }
204+ gw , err := tryCreateDockerNetwork (ociBin , p , bridgeInfo .mtu , networkName )
205+ if err == nil {
206+ klog .Infof ("%s network %s %s created" , ociBin , networkName , p .CIDR )
207+ return gw , nil
208+ }
209+ if ! errors .Is (err , ErrNetworkSubnetTaken ) && ! errors .Is (err , ErrNetworkGatewayTaken ) {
210+ klog .Errorf ("error while trying to create %s network %s %s: %v" , ociBin , networkName , p .CIDR , err )
211+ return nil , fmt .Errorf ("un-retryable: %w" , err )
212+ }
213+ klog .Warningf ("failed to create %s network %s %s, will retry: %v" , ociBin , networkName , p .CIDR , err )
214+ subnetAddr = p .IP
215+ }
216+ return nil , fmt .Errorf ("failed to create %s network %s: %w" , ociBin , networkName , lastErr )
217+
218+ }
112219
113- // retry up to 5 times to create container network
114- for attempts , subnetAddr := 0 , firstSubnetAddr (subnet ); attempts < 5 ; attempts ++ {
115- // Rather than iterate through all of the valid subnets, give up at 20 to avoid a lengthy user delay for something that is unlikely to work.
116- // will be like 192.168.49.0/24,..., 192.168.220.0/24 (in increment steps of 9)
117- var subnet * network.Parameters
118- subnet , err = network .FreeSubnet (subnetAddr , 9 , tries )
119- if err != nil {
120- klog .Errorf ("failed to find free subnet for %s network %s after %d attempts: %v" , ociBin , networkName , 20 , err )
121- return nil , fmt .Errorf ("un-retryable: %w" , err )
122- }
123- info .gateway , err = tryCreateDockerNetwork (ociBin , subnet , info .mtu , networkName )
124- if err == nil {
125- klog .Infof ("%s network %s %s created" , ociBin , networkName , subnet .CIDR )
126- return info .gateway , nil
127- }
128- // don't retry if error is not address is taken
129- if ! errors .Is (err , ErrNetworkSubnetTaken ) && ! errors .Is (err , ErrNetworkGatewayTaken ) {
130- klog .Errorf ("error while trying to create %s network %s %s: %v" , ociBin , networkName , subnet .CIDR , err )
131- return nil , fmt .Errorf ("un-retryable: %w" , err )
132- }
133- klog .Warningf ("failed to create %s network %s %s, will retry: %v" , ociBin , networkName , subnet .CIDR , err )
134- subnetAddr = subnet .IP
135- }
136- return info .gateway , fmt .Errorf ("failed to create %s network %s: %w" , ociBin , networkName , err )
220+ // cidr64ForIP returns a /64 CIDR string covering the provided IPv6 address.
221+ func cidr64ForIP (ipStr string ) (string , error ) {
222+ ip := net .ParseIP (ipStr )
223+ if ip == nil || ip .To16 () == nil || ip .To4 () != nil {
224+ return "" , fmt .Errorf ("not a valid IPv6 address: %q" , ipStr )
225+ }
226+ mask := net .CIDRMask (64 , 128 )
227+ ipMasked := ip .Mask (mask )
228+ return (& net.IPNet {IP : ipMasked , Mask : mask }).String (), nil
137229}
138230
231+
139232// createV6OrDualNetwork creates a user-defined bridge network with IPv6 enabled,
140233// and adds both subnets when ipFamily == "dual". Returns the gateway reported by inspect (may be nil for v6-only).
141234func createV6OrDualNetwork (ociBin , name , subnetV4 , subnetV6 , ipFamily string ) (net.IP , error ) {
0 commit comments