Skip to content

Commit

Permalink
adjust
Browse files Browse the repository at this point in the history
Signed-off-by: Kristoffer Dalby <[email protected]>
  • Loading branch information
kradalby committed Dec 9, 2024
1 parent d14810d commit 77bcc32
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 12 deletions.
2 changes: 1 addition & 1 deletion Dockerfile.integration
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ ENV GOPATH /go
WORKDIR /go/src/headscale

RUN apt-get update \
&& apt-get install --no-install-recommends --yes less jq sqlite3 \
&& apt-get install --no-install-recommends --yes less jq sqlite3 dnsutils \
&& rm -rf /var/lib/apt/lists/* \
&& apt-get clean
RUN mkdir -p /var/run/headscale
Expand Down
5 changes: 5 additions & 0 deletions config-example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,11 @@ dns:
# # you can also put it in one line
# - { name: "prometheus.myvpn.example.com", type: "A", value: "100.64.0.3" }

# Path to a JSON file containing a list of extra DNS records, on the format of the `extra_records` field.
# Updates to the file will automatically be picked up by headscale and propagated to the nodes
# This option is mutual exclusive with the `extra_records` field.
# extra_records_path: ""

# DEPRECATED
# Use the username as part of the DNS name for nodes, with this option enabled:
# node1.username.example.com
Expand Down
3 changes: 3 additions & 0 deletions hscontrol/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,9 @@ func (h *Headscale) Serve() error {
if err != nil {
return fmt.Errorf("setting up : %w", err)
}
h.cfg.TailcfgDNSConfig.ExtraRecords = h.extraRecordMan.Records()
go h.extraRecordMan.Run()
defer h.extraRecordMan.Close()
}

// Start all scheduled tasks, e.g. expiring nodes, derp updates and
Expand Down
35 changes: 24 additions & 11 deletions hscontrol/dns/extrarecords.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,20 @@ func NewExtraRecordsMan(path string) (*ExtraRecordsMan, error) {
return nil, fmt.Errorf("adding path to watcher: %w", err)
}

records, hash, err := readExtraRecordsFromPath(path)
if err != nil {
return nil, fmt.Errorf("reading extra records from path: %w", err)
}

log.Trace().Caller().Strs("watching", watcher.WatchList()).Msg("started filewatcher")

return &ExtraRecordsMan{
watcher: watcher,
path: path,
records: set.Set[tailcfg.DNSRecord]{},
hashes: map[string][32]byte{},
records: set.SetOf(records),
hashes: map[string][32]byte{
path: hash,
},
closeCh: make(chan struct{}),
}, nil
}
Expand All @@ -70,19 +79,23 @@ func (e *ExtraRecordsMan) Run() {
select {
case <-e.closeCh:
return
case _, ok := <-e.watcher.Events:
case event, ok := <-e.watcher.Events:
log.Trace().Caller().Str("path", event.Name).Str("op", event.Op.String()).Msg("extra records received filewatch event")
if !ok {
log.Error().Msgf("error reading file watcher event of channel, records watcher closing")
log.Error().Caller().Msgf("error reading file watcher event of channel, records watcher closing")
return
}
if event.Name != e.path {
continue
}
e.updateRecords()

case err, ok := <-e.watcher.Errors:
if !ok {
log.Error().Msgf("error reading file watcher event of channel, records watcher closing")
return
}
log.Error().Err(err).Msgf("extra records filewatcher returned error")
// case err, ok := <-e.watcher.Errors:
// if !ok {
// log.Error().Caller().Msgf("error reading file watcher error of channel, records watcher closing")
// return
// }
// log.Error().Caller().Err(err).Msgf("extra records filewatcher returned error: %q", err)
}
}
}
Expand All @@ -94,7 +107,7 @@ func (e *ExtraRecordsMan) Close() {
func (e *ExtraRecordsMan) updateRecords() {
records, newHash, err := readExtraRecordsFromPath(e.path)
if err != nil {
log.Error().Err(err).Msgf("reading extra records from path: %s", e.path)
log.Error().Caller().Err(err).Msgf("reading extra records from path: %s", e.path)
return
}

Expand Down
91 changes: 91 additions & 0 deletions integration/dns_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package integration

import (
"encoding/json"
"fmt"
"strings"
"testing"
Expand All @@ -9,6 +10,7 @@ import (
"github.com/juanfont/headscale/integration/hsic"
"github.com/juanfont/headscale/integration/tsic"
"github.com/stretchr/testify/assert"
"tailscale.com/tailcfg"
)

func TestResolveMagicDNS(t *testing.T) {
Expand Down Expand Up @@ -81,6 +83,95 @@ func TestResolveMagicDNS(t *testing.T) {
}
}

func TestResolveMagicDNSExtraRecordsPath(t *testing.T) {
IntegrationSkip(t)
t.Parallel()

scenario, err := NewScenario(dockertestMaxWait())
assertNoErr(t, err)
// defer scenario.ShutdownAssertNoPanics(t)

spec := map[string]int{
"magicdns1": 1,
"magicdns2": 1,
}

const erPath = "/tmp/extra_records.json"

extraRecords := []tailcfg.DNSRecord{
{
Name: "test.myvpn.example.com",
Type: "A",
Value: "6.6.6.6",
},
}
b, _ := json.Marshal(extraRecords)

err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{
tsic.WithDockerEntrypoint([]string{
"/bin/sh",
"-c",
"/bin/sleep 3 ; apk add python3 curl bind-tools ; update-ca-certificates ; tailscaled --tun=tsdev",
}),
},
hsic.WithTestName("extrarecords"),
hsic.WithConfigEnv(map[string]string{
"HEADSCALE_DNS_EXTRA_RECORDS_PATH": erPath,
}),
hsic.WithFileInContainer(erPath, b),
)
assertNoErrHeadscaleEnv(t, err)

allClients, err := scenario.ListTailscaleClients()
assertNoErrListClients(t, err)

err = scenario.WaitForTailscaleSync()
assertNoErrSync(t, err)

// assertClientsState(t, allClients)

// Poor mans cache
_, err = scenario.ListTailscaleClientsFQDNs()
assertNoErrListFQDN(t, err)

_, err = scenario.ListTailscaleClientsIPs()
assertNoErrListClientIPs(t, err)

for _, client := range allClients {
stdout, stderr, err := client.Execute([]string{"dig", "test.myvpn.example.com"})
if err != nil {
t.Errorf("stderr: %s", stderr)
t.Errorf("executing dig command: %s", err)
}

assert.Contains(t, stdout, "6.6.6.6")
}

extraRecords = append(extraRecords, tailcfg.DNSRecord{
Name: "otherrecord.myvpn.example.com",
Type: "A",
Value: "7.7.7.7",
})
b2, _ := json.Marshal(extraRecords)

hs, err := scenario.Headscale()
assertNoErr(t, err)

err = hs.WriteFile(erPath, b2)
assertNoErr(t, err)

time.Sleep(5 * time.Second)
for _, client := range allClients {
stdout, stderr, err := client.Execute([]string{"dig", "test.myvpn.example.com"})
assert.Errorf(t, err, "stderr: %s", stderr)
assert.Contains(t, stdout, "6.6.6.6")

stdout, stderr, err = client.Execute([]string{"dig", "otherrecord.myvpn.example.com"})
assert.Errorf(t, err, "stderr: %s", stderr)
assert.Contains(t, stdout, "7.7.7.7")
}
}

// TestValidateResolvConf validates that the resolv.conf file
// ends up as expected in our Tailscale containers.
// All the containers are based on Alpine, meaning Tailscale
Expand Down

0 comments on commit 77bcc32

Please sign in to comment.