Skip to content

Commit 98cd077

Browse files
committed
Enhance DNS server configuration with customizable listen addresses
- Update README to reflect new configurable listen addresses for tsdnsproxy - Modify main.go to introduce a flag for specifying listen addresses - Implement logic in server.go to handle multiple listen addresses, including Tailscale and LAN options - Update Kubernetes deployment to support new listen address configuration
1 parent 25971cc commit 98cd077

File tree

4 files changed

+108
-30
lines changed

4 files changed

+108
-30
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ A DNS proxy server for Tailscale networks that enables per-identity DNS routing,
1313

1414
## How It Works
1515

16-
tsdnsproxy runs as a tsnet application on your tailnet, listening on UDP port 53. When it receives DNS requests:
16+
tsdnsproxy runs as a tsnet application on your tailnet, listening on configurable addresses (default: Tailscale IP port 53). When it receives DNS requests:
1717

1818
1. Identifies the requesting node using LocalAPI whois
1919
2. Retrieves DNS grants from the node's capabilities
@@ -66,6 +66,7 @@ docker run -d \
6666
--name tsdnsproxy \
6767
-e TS_AUTHKEY=tskey-auth-YOUR-KEY \
6868
-e TSDNSPROXY_HOSTNAME=tsdnsproxy \
69+
-e TSDNSPROXY_LISTEN_ADDRS=tailscale,0.0.0.0:53 \
6970
-p 53:53/udp \
7071
ghcr.io/rajsinghtech/tsdnsproxy:latest
7172
```
@@ -102,6 +103,7 @@ tsdnsproxy -authkey tskey-auth-YOUR-KEY
102103
- `TSDNSPROXY_STATE_DIR`: State directory (default: `/var/lib/tsdnsproxy`)
103104
- `TSDNSPROXY_STATE`: State storage backend (e.g., `kube:secret-name`)
104105
- `TSDNSPROXY_DEFAULT_DNS`: Default DNS servers (comma-separated)
106+
- `TSDNSPROXY_LISTEN_ADDRS`: Listen addresses (default: `tailscale`) - see Network Configuration
105107
- `TSDNSPROXY_HEALTH_ADDR`: Health check endpoint address (default: `:8080`)
106108
- `TSDNSPROXY_VERBOSE`: Enable verbose logging (default: `false`)
107109

@@ -111,6 +113,7 @@ tsdnsproxy -authkey tskey-auth-YOUR-KEY
111113
tsdnsproxy \
112114
-authkey tskey-auth-YOUR-KEY \
113115
-hostname tsdnsproxy \
116+
-listen-addrs tailscale,0.0.0.0:53 \
114117
-statedir /var/lib/tsdnsproxy \
115118
-state kube:tsdnsproxy-state \
116119
-default-dns 8.8.8.8:53,8.8.4.4:53 \

cmd/tsdnsproxy/main.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ func main() {
7676
defaultDNS = flag.String("default-dns", envOr("TSDNSPROXY_DEFAULT_DNS", ""), "default DNS servers (comma-separated)")
7777
cacheExpiry = flag.Duration("cache-expiry", constants.DefaultCacheExpiry, "whois cache expiry duration")
7878
healthAddr = flag.String("health-addr", envOr("TSDNSPROXY_HEALTH_ADDR", ":8080"), "health check endpoint address")
79+
listenAddrs = flag.String("listen-addrs", envOr("TSDNSPROXY_LISTEN_ADDRS", "tailscale"), "listen addresses (comma-separated: tailscale,0.0.0.0:53,127.0.0.1:5353)")
7980
verbose = flag.Bool("verbose", envOr("TSDNSPROXY_VERBOSE", "false") == "true", "enable verbose logging")
8081
)
8182
flag.Parse()
@@ -193,7 +194,7 @@ func main() {
193194

194195
// Start DNS server (handles both UDP and TCP)
195196
log.Printf("starting DNS server")
196-
if err := dnsServer.Run(ctx); err != nil {
197+
if err := dnsServer.RunWithAddrs(ctx, *listenAddrs, status); err != nil {
197198
log.Printf("DNS server error: %v", err)
198199
}
199200

internal/dns/server.go

Lines changed: 99 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,18 @@ type Server struct {
5252
serverGrants []grants.GrantConfig
5353
}
5454

55-
// Run starts both UDP and TCP DNS servers
55+
// Run starts both UDP and TCP DNS servers on the default Tailscale address
5656
func (s *Server) Run(ctx context.Context) error {
57-
s.workerPool = make(chan struct{}, 100)
58-
5957
status, err := s.LocalClient.Status(ctx)
6058
if err != nil {
6159
return fmt.Errorf("get status: %w", err)
6260
}
61+
return s.RunWithAddrs(ctx, "tailscale", status)
62+
}
63+
64+
// RunWithAddrs starts DNS servers on specified addresses
65+
func (s *Server) RunWithAddrs(ctx context.Context, listenAddrsStr string, status *ipnstate.Status) error {
66+
s.workerPool = make(chan struct{}, 100)
6367

6468
// Load server's own grants
6569
if err := s.loadServerGrants(ctx, status); err != nil {
@@ -69,34 +73,32 @@ func (s *Server) Run(ctx context.Context) error {
6973
s.grantsMu.Unlock()
7074
}
7175

72-
var listenAddr string
73-
if status.Self != nil && len(status.Self.TailscaleIPs) > 0 {
74-
// Use the first tailscale IP
75-
listenAddr = fmt.Sprintf("%s:53", status.Self.TailscaleIPs[0])
76-
} else {
77-
listenAddr = ":53"
78-
}
76+
// Parse listen addresses
77+
listenAddrs := s.parseListenAddresses(listenAddrsStr, status)
7978

80-
// Create error channel for goroutines
81-
errCh := make(chan error, 2)
79+
// Create error channel for goroutines (2 per address)
80+
errCh := make(chan error, len(listenAddrs)*2)
8281
ctx, cancel := context.WithCancel(ctx)
8382
defer cancel()
8483

85-
// Start UDP server
86-
go func() {
87-
if err := s.runUDP(ctx, listenAddr); err != nil {
88-
errCh <- fmt.Errorf("UDP server: %w", err)
89-
}
90-
}()
84+
// Start UDP and TCP servers for each listen address
85+
for _, addr := range listenAddrs {
86+
// Start UDP server
87+
go func(listenAddr string) {
88+
if err := s.runUDP(ctx, listenAddr); err != nil {
89+
errCh <- fmt.Errorf("UDP server on %s: %w", listenAddr, err)
90+
}
91+
}(addr)
9192

92-
// Start TCP server
93-
go func() {
94-
if err := s.runTCP(ctx, listenAddr); err != nil {
95-
errCh <- fmt.Errorf("TCP server: %w", err)
96-
}
97-
}()
93+
// Start TCP server
94+
go func(listenAddr string) {
95+
if err := s.runTCP(ctx, listenAddr); err != nil {
96+
errCh <- fmt.Errorf("TCP server on %s: %w", listenAddr, err)
97+
}
98+
}(addr)
99+
}
98100

99-
// Wait for either server to fail or context to be done
101+
// Wait for any server to fail or context to be done
100102
select {
101103
case err := <-errCh:
102104
cancel()
@@ -106,9 +108,52 @@ func (s *Server) Run(ctx context.Context) error {
106108
}
107109
}
108110

111+
// parseListenAddresses converts the listen addresses string into concrete addresses
112+
func (s *Server) parseListenAddresses(listenAddrsStr string, status *ipnstate.Status) []string {
113+
addrs := strings.Split(listenAddrsStr, ",")
114+
var result []string
115+
116+
for _, addr := range addrs {
117+
addr = strings.TrimSpace(addr)
118+
switch addr {
119+
case "tailscale":
120+
// Use the first tailscale IP
121+
if status.Self != nil && len(status.Self.TailscaleIPs) > 0 {
122+
result = append(result, fmt.Sprintf("%s:53", status.Self.TailscaleIPs[0]))
123+
} else {
124+
s.Logf("warning: tailscale address requested but no Tailscale IPs available, using :53")
125+
result = append(result, ":53")
126+
}
127+
case "0.0.0.0:53", "0.0.0.0", "all":
128+
result = append(result, "0.0.0.0:53")
129+
case "127.0.0.1:53", "127.0.0.1", "localhost":
130+
result = append(result, "127.0.0.1:53")
131+
case "[::]:53", "[::]", "ipv6":
132+
result = append(result, "[::]:53")
133+
default:
134+
// Custom address - validate it has a port
135+
if !strings.Contains(addr, ":") {
136+
addr += ":53"
137+
}
138+
result = append(result, addr)
139+
}
140+
}
141+
142+
s.Logf("parsed listen addresses: %v", result)
143+
return result
144+
}
145+
109146
// runUDP handles UDP DNS queries
110147
func (s *Server) runUDP(ctx context.Context, listenAddr string) error {
111-
pc, err := s.TSServer.ListenPacket("udp", listenAddr)
148+
var pc net.PacketConn
149+
var err error
150+
151+
// Determine if we should use tsnet or standard networking
152+
if s.shouldUseTSNet(listenAddr) {
153+
pc, err = s.TSServer.ListenPacket("udp", listenAddr)
154+
} else {
155+
pc, err = net.ListenPacket("udp", listenAddr)
156+
}
112157
if err != nil {
113158
return fmt.Errorf("listen UDP: %w", err)
114159
}
@@ -176,9 +221,36 @@ func (s *Server) runUDP(ctx context.Context, listenAddr string) error {
176221
}
177222
}
178223

224+
// shouldUseTSNet determines if we should use tsnet for the given address
225+
func (s *Server) shouldUseTSNet(listenAddr string) bool {
226+
// Extract host from address
227+
host, _, err := net.SplitHostPort(listenAddr)
228+
if err != nil {
229+
// If we can't parse, assume it's a tsnet address
230+
return true
231+
}
232+
233+
// Use standard networking for common non-tailscale addresses
234+
switch host {
235+
case "0.0.0.0", "127.0.0.1", "localhost", "", "[::]":
236+
return false
237+
default:
238+
// If it looks like a tailscale IP or custom address, use tsnet
239+
return true
240+
}
241+
}
242+
179243
// runTCP handles TCP DNS queries
180244
func (s *Server) runTCP(ctx context.Context, listenAddr string) error {
181-
listener, err := s.TSServer.Listen("tcp", listenAddr)
245+
var listener net.Listener
246+
var err error
247+
248+
// Determine if we should use tsnet or standard networking
249+
if s.shouldUseTSNet(listenAddr) {
250+
listener, err = s.TSServer.Listen("tcp", listenAddr)
251+
} else {
252+
listener, err = net.Listen("tcp", listenAddr)
253+
}
182254
if err != nil {
183255
return fmt.Errorf("listen TCP: %w", err)
184256
}

k8s/deployment.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ spec:
7777
value: "true"
7878
- name: TSDNSPROXY_DEFAULT_DNS
7979
value: "8.8.8.8:53,8.8.4.4:53" # Configure your default DNS
80+
- name: TSDNSPROXY_LISTEN_ADDRS
81+
value: "tailscale,0.0.0.0:53" # Listen on both Tailscale and LAN
8082
- name: TS_AUTHKEY
8183
valueFrom:
8284
secretKeyRef:
@@ -121,7 +123,7 @@ metadata:
121123
labels:
122124
app: tsdnsproxy
123125
spec:
124-
type: ClusterIP
126+
type: LoadBalancer # Expose DNS service to LAN
125127
selector:
126128
app: tsdnsproxy
127129
ports:

0 commit comments

Comments
 (0)