Populate DNS records for IPv6-only endpoints

Also, return IPv6 records from Network.getSvcRecords()
so that /etc/hosts entries are deleted when an IPv6-only
endpoint is removed.

Signed-off-by: Rob Murray <rob.murray@docker.com>
This commit is contained in:
Rob Murray
2024-07-29 18:36:16 +01:00
parent 265f0a7fa7
commit 344039b9ae
3 changed files with 135 additions and 34 deletions

View File

@@ -143,6 +143,10 @@ func mergeRecords(path string, recs []Record) ([]byte, error) {
}
// Delete deletes an arbitrary number of Records already existing in /etc/hosts file
//
// FIXME(robmry) - this only matches on hostname, not address. So, if a container
// is connected to two networks then disconnected from one of them, the hosts
// entries for both networks are deleted.
func Delete(path string, recs []Record) error {
defer pathLock(path)()

View File

@@ -13,6 +13,7 @@ import (
"github.com/docker/docker/internal/testutils/netnsutils"
"github.com/docker/docker/libnetwork/config"
"github.com/docker/docker/libnetwork/driverapi"
"github.com/docker/docker/libnetwork/etchosts"
"github.com/docker/docker/libnetwork/ipams/defaultipam"
"github.com/docker/docker/libnetwork/ipamutils"
"github.com/docker/docker/libnetwork/netlabel"
@@ -358,6 +359,91 @@ func TestAuxAddresses(t *testing.T) {
}
}
func TestUpdateSvcRecord(t *testing.T) {
skip.If(t, runtime.GOOS == "windows", "bridge driver and IPv6, only works on linux")
tests := []struct {
name string
epName string
addr4 string
addr6 string
expSvcRecs []etchosts.Record
}{
{
name: "v4only",
epName: "ep4",
addr4: "172.16.0.2/24",
expSvcRecs: []etchosts.Record{
{Hosts: "id-ep4", IP: "172.16.0.2"},
},
},
/* TODO(robmry) - add this test when the bridge driver understands v6-only
{
name: "v6only",
epName: "ep6",
addr6: "fde6:045d:b2aa::2/64",
expSvcRecs: []etchosts.Record{
{Hosts: "id-ep6", IP: "fde6:45d:b2aa::2"},
},
},
*/
{
name: "dual-stack",
epName: "ep46",
addr4: "172.16.1.2/24",
addr6: "fd60:8677:5a4c::2/64",
expSvcRecs: []etchosts.Record{
{Hosts: "id-ep46", IP: "172.16.1.2"},
{Hosts: "id-ep46", IP: "fd60:8677:5a4c::2"},
},
},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
defer netnsutils.SetupTestOSContext(t)()
ctrlr, err := New(OptionBoltdbWithRandomDBFile(t))
assert.NilError(t, err)
defer ctrlr.Stop()
var ipam4, ipam6 []*IpamConf
var ip4, ip6 net.IP
if tc.addr4 != "" {
var net4 *net.IPNet
ip4, net4, err = net.ParseCIDR(tc.addr4)
assert.NilError(t, err)
ipam4 = []*IpamConf{{PreferredPool: net4.String()}}
}
if tc.addr6 != "" {
var net6 *net.IPNet
ip6, net6, err = net.ParseCIDR(tc.addr6)
assert.NilError(t, err)
ipam6 = []*IpamConf{{PreferredPool: net6.String()}}
}
n, err := ctrlr.NewNetwork("bridge", "net1", "", nil,
NetworkOptionEnableIPv4(tc.addr4 != ""),
NetworkOptionEnableIPv6(tc.addr6 != ""),
NetworkOptionIpam(defaultipam.DriverName, "", ipam4, ipam6, nil),
)
assert.NilError(t, err)
ep, err := n.CreateEndpoint(context.Background(), tc.epName,
CreateOptionDNSNames([]string{tc.epName, "id-" + tc.epName}),
CreateOptionIpam(ip4, ip6, nil, nil),
)
assert.NilError(t, err)
n.updateSvcRecord(context.Background(), ep, true)
recs := n.getSvcRecords(ep)
assert.Check(t, is.DeepEqual(recs, tc.expSvcRecs))
n.updateSvcRecord(context.Background(), ep, false)
recs = n.getSvcRecords(ep)
assert.Check(t, is.Nil(recs))
})
}
}
func TestSRVServiceQuery(t *testing.T) {
skip.If(t, runtime.GOOS == "windows", "test only works on linux")

View File

@@ -1329,11 +1329,14 @@ func (n *Network) updateSvcRecord(ctx context.Context, ep *Endpoint, isAdd bool)
defer span.End()
iface := ep.Iface()
if iface == nil || iface.Address() == nil {
if iface == nil {
return
}
var ipv6 net.IP
var ipv4, ipv6 net.IP
if iface.Address() != nil {
ipv4 = iface.Address().IP
}
if iface.AddressIPv6() != nil {
ipv6 = iface.AddressIPv6().IP
}
@@ -1347,12 +1350,12 @@ func (n *Network) updateSvcRecord(ctx context.Context, ep *Endpoint, isAdd bool)
if isAdd {
for i, dnsName := range dnsNames {
ipMapUpdate := i == 0 // ipMapUpdate indicates whether PTR records should be updated.
n.addSvcRecords(ep.ID(), dnsName, serviceID, iface.Address().IP, ipv6, ipMapUpdate, "updateSvcRecord")
n.addSvcRecords(ep.ID(), dnsName, serviceID, ipv4, ipv6, ipMapUpdate, "updateSvcRecord")
}
} else {
for i, dnsName := range dnsNames {
ipMapUpdate := i == 0 // ipMapUpdate indicates whether PTR records should be updated.
n.deleteSvcRecords(ep.ID(), dnsName, serviceID, iface.Address().IP, ipv6, ipMapUpdate, "updateSvcRecord")
n.deleteSvcRecords(ep.ID(), dnsName, serviceID, ipv4, ipv6, ipMapUpdate, "updateSvcRecord")
}
}
}
@@ -1392,14 +1395,14 @@ func delNameToIP(svcMap *setmatrix.SetMatrix[svcMapEntry], name, serviceID strin
}
// TODO(aker): remove ipMapUpdate param and add a proper method dedicated to update PTR records.
func (n *Network) addSvcRecords(eID, name, serviceID string, epIP, epIPv6 net.IP, ipMapUpdate bool, method string) {
func (n *Network) addSvcRecords(eID, name, serviceID string, epIPv4, epIPv6 net.IP, ipMapUpdate bool, method string) {
// Do not add service names for ingress network as this is a
// routing only network
if n.ingress {
return
}
networkID := n.ID()
log.G(context.TODO()).Debugf("%s (%.7s).addSvcRecords(%s, %s, %s, %t) %s sid:%s", eID, networkID, name, epIP, epIPv6, ipMapUpdate, method, serviceID)
log.G(context.TODO()).Debugf("%s (%.7s).addSvcRecords(%s, %s, %s, %t) %s sid:%s", eID, networkID, name, epIPv4, epIPv6, ipMapUpdate, method, serviceID)
c := n.getController()
c.mu.Lock()
@@ -1412,26 +1415,30 @@ func (n *Network) addSvcRecords(eID, name, serviceID string, epIP, epIPv6 net.IP
}
if ipMapUpdate {
addIPToName(&sr.ipMap, name, serviceID, epIP)
if epIPv4 != nil {
addIPToName(&sr.ipMap, name, serviceID, epIPv4)
}
if epIPv6 != nil {
addIPToName(&sr.ipMap, name, serviceID, epIPv6)
}
}
addNameToIP(&sr.svcMap, name, serviceID, epIP)
if epIPv4 != nil {
addNameToIP(&sr.svcMap, name, serviceID, epIPv4)
}
if epIPv6 != nil {
addNameToIP(&sr.svcIPv6Map, name, serviceID, epIPv6)
}
}
func (n *Network) deleteSvcRecords(eID, name, serviceID string, epIP net.IP, epIPv6 net.IP, ipMapUpdate bool, method string) {
func (n *Network) deleteSvcRecords(eID, name, serviceID string, epIPv4, epIPv6 net.IP, ipMapUpdate bool, method string) {
// Do not delete service names from ingress network as this is a
// routing only network
if n.ingress {
return
}
networkID := n.ID()
log.G(context.TODO()).Debugf("%s (%.7s).deleteSvcRecords(%s, %s, %s, %t) %s sid:%s ", eID, networkID, name, epIP, epIPv6, ipMapUpdate, method, serviceID)
log.G(context.TODO()).Debugf("%s (%.7s).deleteSvcRecords(%s, %s, %s, %t) %s sid:%s ", eID, networkID, name, epIPv4, epIPv6, ipMapUpdate, method, serviceID)
c := n.getController()
c.mu.Lock()
@@ -1443,15 +1450,17 @@ func (n *Network) deleteSvcRecords(eID, name, serviceID string, epIP net.IP, epI
}
if ipMapUpdate {
delIPToName(&sr.ipMap, name, serviceID, epIP)
if epIPv4 != nil {
delIPToName(&sr.ipMap, name, serviceID, epIPv4)
}
if epIPv6 != nil {
delIPToName(&sr.ipMap, name, serviceID, epIPv6)
}
}
delNameToIP(&sr.svcMap, name, serviceID, epIP)
if epIPv4 != nil {
delNameToIP(&sr.svcMap, name, serviceID, epIPv4)
}
if epIPv6 != nil {
delNameToIP(&sr.svcIPv6Map, name, serviceID, epIPv6)
}
@@ -1476,27 +1485,29 @@ func (n *Network) getSvcRecords(ep *Endpoint) []etchosts.Record {
return nil
}
svcMapKeys := sr.svcMap.Keys()
// Loop on service names on this network
for _, k := range svcMapKeys {
if strings.Split(k, ".")[0] == epName {
continue
}
// Get all the IPs associated to this service
mapEntryList, ok := sr.svcMap.Get(k)
if !ok {
// The key got deleted
continue
}
if len(mapEntryList) == 0 {
log.G(context.TODO()).Warnf("Found empty list of IP addresses for service %s on network %s (%s)", k, n.name, n.id)
continue
}
for _, svcMap := range []*setmatrix.SetMatrix[svcMapEntry]{&sr.svcMap, &sr.svcIPv6Map} {
svcMapKeys := svcMap.Keys()
// Loop on service names on this network
for _, k := range svcMapKeys {
if strings.Split(k, ".")[0] == epName {
continue
}
// Get all the IPs associated to this service
mapEntryList, ok := svcMap.Get(k)
if !ok {
// The key got deleted
continue
}
if len(mapEntryList) == 0 {
log.G(context.TODO()).Warnf("Found empty list of IP addresses for service %s on network %s (%s)", k, n.name, n.id)
continue
}
recs = append(recs, etchosts.Record{
Hosts: k,
IP: mapEntryList[0].ip,
})
recs = append(recs, etchosts.Record{
Hosts: k,
IP: mapEntryList[0].ip,
})
}
}
return recs