Skip to content

Commit

Permalink
feat: add host dns support for resolving member addrs
Browse files Browse the repository at this point in the history
Closes #8330

Signed-off-by: Dmitriy Matrenichev <[email protected]>
  • Loading branch information
DmitriyMV committed Apr 18, 2024
1 parent 0d20b63 commit 908f67f
Show file tree
Hide file tree
Showing 12 changed files with 924 additions and 566 deletions.
1 change: 1 addition & 0 deletions api/resource/definitions/network/network.proto
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ message HostDNSConfigSpec {
bool enabled = 1;
repeated common.NetIPPort listen_addresses = 2;
common.NetIP service_host_dns_address = 3;
bool resolve_member_names = 4;
}

// HostnameSpecSpec describes node hostname.
Expand Down
10 changes: 10 additions & 0 deletions hack/release.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@ machine:
```
Please note that on running cluster you will have to kill CoreDNS pods for this change to apply.
If you want to can also enable the resolving of member addresses through their host and node names:
```yaml
machine:
features:
hostDNS:
enabled: true
resolveMemberNames: true
```
"""

[notes.secureboot-image]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,33 @@ import (
"fmt"
"net"
"net/netip"
"slices"
"strings"
"sync"
"time"

"github.com/coredns/coredns/plugin/pkg/proxy"
"github.com/cosi-project/runtime/pkg/controller"
"github.com/cosi-project/runtime/pkg/safe"
"github.com/cosi-project/runtime/pkg/state"
dnssrv "github.com/miekg/dns"
"github.com/siderolabs/gen/optional"
"github.com/siderolabs/gen/pair"
"go.uber.org/zap"

"github.com/siderolabs/talos/internal/pkg/dns"
"github.com/siderolabs/talos/pkg/machinery/resources/cluster"
"github.com/siderolabs/talos/pkg/machinery/resources/network"
)

// DNSResolveCacheController starts dns server on both udp and tcp ports based on finalized network configuration.
type DNSResolveCacheController struct {
State state.State
Logger *zap.Logger

mx sync.Mutex
handler *dns.Handler
nodeHandler *dns.NodeHandler
cache *dns.Cache
runners map[runnerConfig]pair.Pair[func(), <-chan struct{}]
reconcile chan struct{}
Expand Down Expand Up @@ -115,6 +121,8 @@ func (ctrl *DNSResolveCacheController) Run(ctx context.Context, r controller.Run
continue
}

ctrl.nodeHandler.SetEnabled(cfg.TypedSpec().ResolveMemberNames)

touchedRunners := make(map[runnerConfig]struct{}, len(ctrl.runners))

for _, addr := range cfg.TypedSpec().ListenAddresses {
Expand Down Expand Up @@ -191,7 +199,8 @@ func (ctrl *DNSResolveCacheController) init(ctx context.Context) {

ctrl.originalCtx = ctx
ctrl.handler = dns.NewHandler(ctrl.Logger)
ctrl.cache = dns.NewCache(ctrl.handler, ctrl.Logger)
ctrl.nodeHandler = dns.NewNodeHandler(ctrl.handler, &stateMapper{state: ctrl.State}, ctrl.Logger)
ctrl.cache = dns.NewCache(ctrl.nodeHandler, ctrl.Logger)
ctrl.runners = map[runnerConfig]pair.Pair[func(), <-chan struct{}]{}
ctrl.reconcile = make(chan struct{}, 1)

Expand Down Expand Up @@ -288,3 +297,49 @@ func newDNSRunner(cfg runnerConfig, cache *dns.Cache, logger *zap.Logger) (*dns.

return dns.NewServer(serverOpts), nil
}

type stateMapper struct {
state state.State
}

func (s *stateMapper) ResolveAddr(ctx context.Context, qType uint16, name string) []netip.Addr {
name = strings.TrimRight(name, ".")

list, err := safe.ReaderListAll[*cluster.Member](ctx, s.state)
if err != nil {
return nil
}

elem, ok := list.Find(func(res *cluster.Member) bool {
return fqdnMatch(name, res.TypedSpec().Hostname) || fqdnMatch(name, res.Metadata().ID())
})
if !ok {
return nil
}

result := slices.DeleteFunc(slices.Clone(elem.TypedSpec().Addresses), func(addr netip.Addr) bool {
return !((qType == dnssrv.TypeA && addr.Is4()) || (qType == dnssrv.TypeAAAA && addr.Is6()))
})

if len(result) == 0 {
return nil
}

return result
}

func fqdnMatch(what, where string) bool {
what = strings.TrimRight(what, ".")
where = strings.TrimRight(where, ".")

if what == where {
return true
}

first, _, found := strings.Cut(where, ".")
if !found {
return false
}

return what == first
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package network_test

import (
"errors"
"net"
"net/netip"
"sync"
Expand All @@ -23,6 +24,8 @@ import (

"github.com/siderolabs/talos/internal/app/machined/pkg/controllers/ctest"
netctrl "github.com/siderolabs/talos/internal/app/machined/pkg/controllers/network"
"github.com/siderolabs/talos/pkg/machinery/config/machine"
"github.com/siderolabs/talos/pkg/machinery/resources/cluster"
"github.com/siderolabs/talos/pkg/machinery/resources/network"
)

Expand Down Expand Up @@ -141,6 +144,108 @@ func (suite *DNSServer) TestSetupStartStop() {
rtestutils.AssertLength[*network.DNSUpstream](suite.Ctx(), suite.T(), suite.State(), len(dnsSlice))
}

func (suite *DNSServer) TestResolveMembers() {
port := must.Value(getDynamicPort())(suite.T())

const (
id = "talos-default-controlplane-1"
id2 = "foo.example.com."
)

member := cluster.NewMember(cluster.NamespaceName, id)
*member.TypedSpec() = cluster.MemberSpec{
NodeID: id,
Addresses: []netip.Addr{
netip.MustParseAddr("172.20.0.2"),
},
Hostname: id,
MachineType: machine.TypeControlPlane,
OperatingSystem: "Talos dev",
ControlPlane: nil,
}

suite.Require().NoError(suite.State().Create(suite.Ctx(), member))

member = cluster.NewMember(cluster.NamespaceName, id2)
*member.TypedSpec() = cluster.MemberSpec{
NodeID: id2,
Addresses: []netip.Addr{
netip.MustParseAddr("172.20.0.3"),
},
Hostname: id2,
MachineType: machine.TypeWorker,
OperatingSystem: "Talos dev",
ControlPlane: nil,
}

suite.Require().NoError(suite.State().Create(suite.Ctx(), member))

cfg := network.NewHostDNSConfig(network.HostDNSConfigID)
cfg.TypedSpec().Enabled = true
cfg.TypedSpec().ListenAddresses = makeAddrs(port)
cfg.TypedSpec().ResolveMemberNames = true
suite.Require().NoError(suite.State().Create(suite.Ctx(), cfg))

rtestutils.AssertResources(suite.Ctx(), suite.T(), suite.State(),
expectedDNSRunners(port),
func(r *network.DNSResolveCache, assert *assert.Assertions) {
assert.Equal("running", r.TypedSpec().Status)
},
)

suite.Require().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(func() error {
exchange, err := dns.Exchange(
&dns.Msg{
MsgHdr: dns.MsgHdr{Id: dns.Id(), RecursionDesired: true},
Question: []dns.Question{
{Name: dns.Fqdn(id), Qtype: dns.TypeA, Qclass: dns.ClassINET},
},
},
"127.0.0.53:"+port,
)
if err != nil {
return retry.ExpectedError(err)
}

if exchange.Rcode != dns.RcodeSuccess {
return retry.ExpectedErrorf("expected rcode %d, got %d for %q", dns.RcodeSuccess, exchange.Rcode, id)
}

proper := dns.Fqdn(id)

if exchange.Answer[0].Header().Name != proper {
return retry.ExpectedErrorf("expected answer name %q, got %q", proper, exchange.Answer[0].Header().Name)
}

return nil
}))

suite.Require().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(func() error {
exchange, err := dns.Exchange(
&dns.Msg{
MsgHdr: dns.MsgHdr{Id: dns.Id(), RecursionDesired: true},
Question: []dns.Question{
{Name: dns.Fqdn("foo"), Qtype: dns.TypeA, Qclass: dns.ClassINET},
},
},
"127.0.0.53:"+port,
)
if err != nil {
return retry.ExpectedError(err)
}

if exchange.Rcode != dns.RcodeSuccess {
return retry.ExpectedErrorf("expected rcode %d, got %d for %q", dns.RcodeSuccess, exchange.Rcode, id2)
}

if !exchange.Answer[0].(*dns.A).A.Equal(net.ParseIP("172.20.0.3")) {
return retry.ExpectedError(errors.New("unexpected ip"))
}

return nil
}))
}

func TestDNSServer(t *testing.T) {
suite.Run(t, &DNSServer{
DefaultSuite: ctest.DefaultSuite{
Expand All @@ -149,6 +254,7 @@ func TestDNSServer(t *testing.T) {
suite.Require().NoError(suite.Runtime().RegisterController(&netctrl.DNSUpstreamController{}))
suite.Require().NoError(suite.Runtime().RegisterController(&netctrl.DNSResolveCacheController{
Logger: zaptest.NewLogger(t),
State: suite.State(),
}))
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ func (ctrl *HostDNSConfigController) Run(ctx context.Context, r controller.Runti
}

res.TypedSpec().Enabled = cfgProvider.Machine().Features().HostDNS().Enabled()
res.TypedSpec().ResolveMemberNames = cfgProvider.Machine().Features().HostDNS().ResolveMemberNames()

if cfgProvider.Machine().Features().HostDNS().ForwardKubeDNSToHost() {
serviceCIDRStr := cfgProvider.Cluster().Network().ServiceCIDRs()[0]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ func (ctrl *Controller) Run(ctx context.Context, drainer *runtime.Drainer) error
&network.AddressStatusController{},
&network.DeviceConfigController{},
&network.DNSResolveCacheController{
State: ctrl.v1alpha1Runtime.State().V1Alpha2().Resources(),
Logger: dnsCacheLogger,
},
&network.DNSUpstreamController{},
Expand Down
Loading

0 comments on commit 908f67f

Please sign in to comment.