When a network-create request does not specify any IPAM config, on
daemon restart the network needs to be restored with the previously
allocated subnet and gateway.
Those fields were copied from "ipamInfo" (state from the old network)
into "ipamConfig" (user-requested config).
Avoid that by checking for this situation in the IPAM allocation
function - if no subnet/gateway is specified, and there's a value
in "ipamInfo", use it.
Also eliminate some pointer shenanigans (so now my IDE can find the
assignment to Network.ipamInfo).
Signed-off-by: Rob Murray <rob.murray@docker.com>
| ... | ... |
@@ -761,30 +761,6 @@ func (c *Controller) reservePools() {
|
| 761 | 761 |
if !doReplayPoolReserve(n) {
|
| 762 | 762 |
continue |
| 763 | 763 |
} |
| 764 |
- // Construct pseudo configs for the auto IP case |
|
| 765 |
- autoIPv4 := (len(n.ipamV4Config) == 0 || (len(n.ipamV4Config) == 1 && n.ipamV4Config[0].PreferredPool == "")) && len(n.ipamV4Info) > 0 |
|
| 766 |
- autoIPv6 := (len(n.ipamV6Config) == 0 || (len(n.ipamV6Config) == 1 && n.ipamV6Config[0].PreferredPool == "")) && len(n.ipamV6Info) > 0 |
|
| 767 |
- if n.enableIPv4 && autoIPv4 {
|
|
| 768 |
- n.ipamV4Config = []*IpamConf{{PreferredPool: n.ipamV4Info[0].Pool.String()}}
|
|
| 769 |
- } |
|
| 770 |
- if n.enableIPv6 && autoIPv6 {
|
|
| 771 |
- n.ipamV6Config = []*IpamConf{{PreferredPool: n.ipamV6Info[0].Pool.String()}}
|
|
| 772 |
- } |
|
| 773 |
- // Account current network gateways |
|
| 774 |
- if n.enableIPv4 {
|
|
| 775 |
- for i, cfg := range n.ipamV4Config {
|
|
| 776 |
- if cfg.Gateway == "" && n.ipamV4Info[i].Gateway != nil {
|
|
| 777 |
- cfg.Gateway = n.ipamV4Info[i].Gateway.IP.String() |
|
| 778 |
- } |
|
| 779 |
- } |
|
| 780 |
- } |
|
| 781 |
- if n.enableIPv6 {
|
|
| 782 |
- for i, cfg := range n.ipamV6Config {
|
|
| 783 |
- if cfg.Gateway == "" && n.ipamV6Info[i].Gateway != nil {
|
|
| 784 |
- cfg.Gateway = n.ipamV6Info[i].Gateway.IP.String() |
|
| 785 |
- } |
|
| 786 |
- } |
|
| 787 |
- } |
|
| 788 | 764 |
// Reserve pools |
| 789 | 765 |
if err := n.ipamAllocate(); err != nil {
|
| 790 | 766 |
log.G(context.TODO()).Warnf("Failed to allocate ipam pool(s) for network %q (%s): %v", n.Name(), n.ID(), err)
|
| ... | ... |
@@ -1510,9 +1510,14 @@ func (n *Network) ipamAllocate() (retErr error) {
|
| 1510 | 1510 |
} |
| 1511 | 1511 |
|
| 1512 | 1512 |
if n.enableIPv4 {
|
| 1513 |
- if err := n.ipamAllocateVersion(4, ipam); err != nil {
|
|
| 1513 |
+ if len(n.ipamV4Config) == 0 {
|
|
| 1514 |
+ n.ipamV4Config = []*IpamConf{{}}
|
|
| 1515 |
+ } |
|
| 1516 |
+ info, err := n.ipamAllocateVersion(ipam, false, n.ipamV4Config, n.ipamV4Info, n.skipGwAllocIPv4) |
|
| 1517 |
+ if err != nil {
|
|
| 1514 | 1518 |
return err |
| 1515 | 1519 |
} |
| 1520 |
+ n.ipamV4Info = info |
|
| 1516 | 1521 |
defer func() {
|
| 1517 | 1522 |
if retErr != nil {
|
| 1518 | 1523 |
n.ipamReleaseVersion(4, ipam) |
| ... | ... |
@@ -1521,123 +1526,129 @@ func (n *Network) ipamAllocate() (retErr error) {
|
| 1521 | 1521 |
} |
| 1522 | 1522 |
|
| 1523 | 1523 |
if n.enableIPv6 {
|
| 1524 |
- if err := n.ipamAllocateVersion(6, ipam); err != nil {
|
|
| 1524 |
+ if len(n.ipamV6Config) == 0 {
|
|
| 1525 |
+ n.ipamV6Config = []*IpamConf{{}}
|
|
| 1526 |
+ } |
|
| 1527 |
+ info, err := n.ipamAllocateVersion(ipam, true, n.ipamV6Config, n.ipamV6Info, n.skipGwAllocIPv6) |
|
| 1528 |
+ if err != nil {
|
|
| 1525 | 1529 |
return err |
| 1526 | 1530 |
} |
| 1531 |
+ n.ipamV6Info = info |
|
| 1527 | 1532 |
} |
| 1528 | 1533 |
|
| 1529 | 1534 |
return nil |
| 1530 | 1535 |
} |
| 1531 | 1536 |
|
| 1532 |
-func (n *Network) ipamAllocateVersion(ipVer int, ipam ipamapi.Ipam) error {
|
|
| 1533 |
- var ( |
|
| 1534 |
- cfgList *[]*IpamConf |
|
| 1535 |
- infoList *[]*IpamInfo |
|
| 1536 |
- skipGwAlloc bool |
|
| 1537 |
- err error |
|
| 1538 |
- ) |
|
| 1539 |
- |
|
| 1540 |
- switch ipVer {
|
|
| 1541 |
- case 4: |
|
| 1542 |
- cfgList = &n.ipamV4Config |
|
| 1543 |
- infoList = &n.ipamV4Info |
|
| 1544 |
- skipGwAlloc = n.skipGwAllocIPv4 |
|
| 1545 |
- case 6: |
|
| 1546 |
- cfgList = &n.ipamV6Config |
|
| 1547 |
- infoList = &n.ipamV6Info |
|
| 1548 |
- skipGwAlloc = n.skipGwAllocIPv6 |
|
| 1549 |
- default: |
|
| 1550 |
- return types.InternalErrorf("incorrect ip version passed to ipam allocate: %d", ipVer)
|
|
| 1551 |
- } |
|
| 1552 |
- |
|
| 1553 |
- if len(*cfgList) == 0 {
|
|
| 1554 |
- *cfgList = []*IpamConf{{}}
|
|
| 1555 |
- } |
|
| 1556 |
- |
|
| 1557 |
- *infoList = make([]*IpamInfo, len(*cfgList)) |
|
| 1537 |
+func (n *Network) ipamAllocateVersion(ipam ipamapi.Ipam, v6 bool, ipamConf []*IpamConf, ipamInfo []*IpamInfo, skipGwAlloc bool) (_ []*IpamInfo, retErr error) {
|
|
| 1538 |
+ var newInfo []*IpamInfo |
|
| 1558 | 1539 |
|
| 1559 |
- log.G(context.TODO()).Debugf("Allocating IPv%d pools for network %s (%s)", ipVer, n.Name(), n.ID())
|
|
| 1540 |
+ log.G(context.TODO()).WithFields(log.Fields{
|
|
| 1541 |
+ "network": n.Name(), |
|
| 1542 |
+ "nid": stringid.TruncateID(n.ID()), |
|
| 1543 |
+ "ipv6": v6, |
|
| 1544 |
+ }).Debug("Allocating pools for network")
|
|
| 1560 | 1545 |
|
| 1561 |
- for i, cfg := range *cfgList {
|
|
| 1562 |
- if err = cfg.Validate(); err != nil {
|
|
| 1563 |
- return err |
|
| 1546 |
+ for i, cfg := range ipamConf {
|
|
| 1547 |
+ if err := cfg.Validate(); err != nil {
|
|
| 1548 |
+ return nil, err |
|
| 1564 | 1549 |
} |
| 1565 |
- d := &IpamInfo{}
|
|
| 1566 |
- (*infoList)[i] = d |
|
| 1567 |
- |
|
| 1568 |
- d.AddressSpace = n.addrSpace |
|
| 1569 | 1550 |
|
| 1570 | 1551 |
var reserved []netip.Prefix |
| 1571 | 1552 |
if n.Scope() != scope.Global {
|
| 1572 |
- reserved = netutils.InferReservedNetworks(ipVer == 6) |
|
| 1553 |
+ reserved = netutils.InferReservedNetworks(v6) |
|
| 1554 |
+ } |
|
| 1555 |
+ |
|
| 1556 |
+ // During network restore, if no subnet was specified in the original network-create |
|
| 1557 |
+ // request, use the previously allocated subnet. |
|
| 1558 |
+ prefPool := cfg.PreferredPool |
|
| 1559 |
+ if prefPool == "" && len(ipamInfo) > i {
|
|
| 1560 |
+ prefPool = ipamInfo[i].Pool.String() |
|
| 1573 | 1561 |
} |
| 1574 | 1562 |
|
| 1575 | 1563 |
alloc, err := ipam.RequestPool(ipamapi.PoolRequest{
|
| 1576 | 1564 |
AddressSpace: n.addrSpace, |
| 1577 |
- Pool: cfg.PreferredPool, |
|
| 1565 |
+ Pool: prefPool, |
|
| 1578 | 1566 |
SubPool: cfg.SubPool, |
| 1579 | 1567 |
Options: n.ipamOptions, |
| 1580 | 1568 |
Exclude: reserved, |
| 1581 |
- V6: ipVer == 6, |
|
| 1569 |
+ V6: v6, |
|
| 1582 | 1570 |
}) |
| 1583 | 1571 |
if err != nil {
|
| 1584 |
- return err |
|
| 1572 |
+ return nil, err |
|
| 1585 | 1573 |
} |
| 1586 |
- |
|
| 1587 |
- d.PoolID = alloc.PoolID |
|
| 1588 |
- d.Pool = netiputil.ToIPNet(alloc.Pool) |
|
| 1589 |
- d.Meta = alloc.Meta |
|
| 1590 |
- |
|
| 1591 | 1574 |
defer func() {
|
| 1592 |
- if err != nil {
|
|
| 1593 |
- if err := ipam.ReleasePool(d.PoolID); err != nil {
|
|
| 1594 |
- log.G(context.TODO()).Warnf("Failed to release address pool %s after failure to create network %s (%s)", d.PoolID, n.Name(), n.ID())
|
|
| 1575 |
+ if retErr != nil {
|
|
| 1576 |
+ if err := ipam.ReleasePool(alloc.PoolID); err != nil {
|
|
| 1577 |
+ log.G(context.TODO()).WithFields(log.Fields{
|
|
| 1578 |
+ "network": n.Name(), |
|
| 1579 |
+ "nid": stringid.TruncateID(n.ID()), |
|
| 1580 |
+ "pool": alloc.PoolID, |
|
| 1581 |
+ "retErr": retErr, |
|
| 1582 |
+ "error": err, |
|
| 1583 |
+ }).Warn("Failed to release address pool after failure to create network")
|
|
| 1595 | 1584 |
} |
| 1596 | 1585 |
} |
| 1597 | 1586 |
}() |
| 1598 | 1587 |
|
| 1588 |
+ d := &IpamInfo{
|
|
| 1589 |
+ PoolID: alloc.PoolID, |
|
| 1590 |
+ Meta: alloc.Meta, |
|
| 1591 |
+ IPAMData: driverapi.IPAMData{
|
|
| 1592 |
+ Pool: netiputil.ToIPNet(alloc.Pool), |
|
| 1593 |
+ AddressSpace: n.addrSpace, |
|
| 1594 |
+ }, |
|
| 1595 |
+ } |
|
| 1596 |
+ newInfo = append(newInfo, d) |
|
| 1597 |
+ |
|
| 1598 |
+ // During network restore, if the original network-create request did not specify a |
|
| 1599 |
+ // gateway, use the previously allocated gateway. |
|
| 1600 |
+ prefGateway := cfg.Gateway |
|
| 1601 |
+ if prefGateway == "" && len(ipamInfo) > i && ipamInfo[i].Gateway != nil {
|
|
| 1602 |
+ prefGateway = ipamInfo[i].Gateway.IP.String() |
|
| 1603 |
+ } |
|
| 1604 |
+ |
|
| 1599 | 1605 |
// If there's no user-configured gateway address but the IPAM driver returned a gw when it |
| 1600 | 1606 |
// set up the pool, use it. (It doesn't need to be requested/reserved in IPAM.) |
| 1601 |
- if cfg.Gateway == "" {
|
|
| 1607 |
+ if prefGateway == "" {
|
|
| 1602 | 1608 |
if gws, ok := d.Meta[netlabel.Gateway]; ok {
|
| 1603 | 1609 |
if d.Gateway, err = types.ParseCIDR(gws); err != nil {
|
| 1604 |
- return types.InvalidParameterErrorf("failed to parse gateway address (%v) returned by ipam driver: %v", gws, err)
|
|
| 1610 |
+ return nil, types.InvalidParameterErrorf("failed to parse gateway address (%v) returned by ipam driver: %v", gws, err)
|
|
| 1605 | 1611 |
} |
| 1606 | 1612 |
} |
| 1607 | 1613 |
} |
| 1608 | 1614 |
|
| 1609 | 1615 |
// If there's still no gateway, reserve cfg.Gateway if the user specified it. Else, |
| 1610 | 1616 |
// if the driver wants a gateway, let the IPAM driver select an address. |
| 1611 |
- if d.Gateway == nil && (cfg.Gateway != "" || !skipGwAlloc) {
|
|
| 1617 |
+ if d.Gateway == nil && (prefGateway != "" || !skipGwAlloc) {
|
|
| 1612 | 1618 |
gatewayOpts := map[string]string{
|
| 1613 | 1619 |
ipamapi.RequestAddressType: netlabel.Gateway, |
| 1614 | 1620 |
} |
| 1615 |
- if d.Gateway, _, err = ipam.RequestAddress(d.PoolID, net.ParseIP(cfg.Gateway), gatewayOpts); err != nil {
|
|
| 1616 |
- return types.InternalErrorf("failed to allocate gateway (%v): %v", cfg.Gateway, err)
|
|
| 1621 |
+ if d.Gateway, _, err = ipam.RequestAddress(d.PoolID, net.ParseIP(prefGateway), gatewayOpts); err != nil {
|
|
| 1622 |
+ return nil, types.InternalErrorf("failed to allocate gateway (%v): %v", prefGateway, err)
|
|
| 1617 | 1623 |
} |
| 1618 | 1624 |
} |
| 1619 | 1625 |
|
| 1620 |
- // Auxiliary addresses must be part of the master address pool |
|
| 1621 |
- // If they fall into the container addressable pool, libnetwork will reserve them |
|
| 1626 |
+ // Auxiliary addresses must be part of the master address pool. |
|
| 1627 |
+ // If they fall into the container addressable pool, reserve them. |
|
| 1622 | 1628 |
if cfg.AuxAddresses != nil {
|
| 1623 | 1629 |
var ip net.IP |
| 1624 | 1630 |
d.IPAMData.AuxAddresses = make(map[string]*net.IPNet, len(cfg.AuxAddresses)) |
| 1625 | 1631 |
for k, v := range cfg.AuxAddresses {
|
| 1626 | 1632 |
if ip = net.ParseIP(v); ip == nil {
|
| 1627 |
- return types.InvalidParameterErrorf("non parsable secondary ip address (%s:%s) passed for network %s", k, v, n.Name())
|
|
| 1633 |
+ return nil, types.InvalidParameterErrorf("non parsable secondary ip address (%s:%s) passed for network %s", k, v, n.Name())
|
|
| 1628 | 1634 |
} |
| 1629 | 1635 |
if !d.Pool.Contains(ip) {
|
| 1630 |
- return types.ForbiddenErrorf("auxiliary address: (%s:%s) must belong to the master pool: %s", k, v, d.Pool)
|
|
| 1636 |
+ return nil, types.ForbiddenErrorf("auxiliary address: (%s:%s) must belong to the master pool: %s", k, v, d.Pool)
|
|
| 1631 | 1637 |
} |
| 1632 | 1638 |
// Attempt reservation in the container addressable pool, silent the error if address does not belong to that pool |
| 1633 | 1639 |
if d.IPAMData.AuxAddresses[k], _, err = ipam.RequestAddress(d.PoolID, ip, nil); err != nil && !errors.Is(err, ipamapi.ErrIPOutOfRange) {
|
| 1634 |
- return types.InternalErrorf("failed to allocate secondary ip address (%s:%s): %v", k, v, err)
|
|
| 1640 |
+ return nil, types.InternalErrorf("failed to allocate secondary ip address (%s:%s): %v", k, v, err)
|
|
| 1635 | 1641 |
} |
| 1636 | 1642 |
} |
| 1637 | 1643 |
} |
| 1638 | 1644 |
} |
| 1639 | 1645 |
|
| 1640 |
- return nil |
|
| 1646 |
+ return newInfo, nil |
|
| 1641 | 1647 |
} |
| 1642 | 1648 |
|
| 1643 | 1649 |
func (n *Network) ipamRelease() {
|