Skip to content

Commit ba484be

Browse files
committed
Local DNS Server for client.
1 parent a525b50 commit ba484be

5 files changed

Lines changed: 171 additions & 0 deletions

File tree

client_config.toml.simple

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ PROTOCOL_TYPE = "SOCKS5"
1313
# Must match server DOMAIN values.
1414
DOMAINS = ["v.domain.com"]
1515

16+
LOCAL_DNS_ENABLED = false
17+
LOCAL_DNS_IP = "127.0.0.1"
18+
LOCAL_DNS_PORT = 5353
19+
LOCAL_DNS_WORKERS = 2
20+
LOCAL_DNS_QUEUE_SIZE = 512
21+
1622
# Resolver balancing strategy:
1723
# 0=Round Robin (default), 1=Random, 2=Round Robin, 3=Least Loss, 4=Lowest Latency
1824
RESOLVER_BALANCING_STRATEGY = 0

cmd/client/main.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@
88
package main
99

1010
import (
11+
"context"
1112
"fmt"
1213
"os"
14+
"os/signal"
15+
"syscall"
1316

1417
"masterdnsvpn-go/internal/client"
1518
)
@@ -41,6 +44,12 @@ func main() {
4144
"📡 <green>Loaded Resolvers</green> <magenta>|</magenta> <magenta>%d</magenta> <blue>endpoints</blue>",
4245
len(cfg.Resolvers),
4346
)
47+
log.Infof(
48+
"🧭 <green>Local DNS Listener</green> <magenta>|</magenta> <blue>Enabled</blue>: <yellow>%t</yellow> <magenta>|</magenta> <blue>Addr</blue>: <cyan>%s:%d</cyan>",
49+
cfg.LocalDNSEnabled,
50+
cfg.LocalDNSIP,
51+
cfg.LocalDNSPort,
52+
)
4453
log.Infof(
4554
"🗂️ <green>Connection Catalog</green> <magenta>|</magenta> <magenta>%d</magenta> <blue>domain-resolver pairs</blue>",
4655
len(app.Connections()),
@@ -72,4 +81,16 @@ func main() {
7281
app.SessionCookie(),
7382
)
7483
log.Infof("🎯 <green>Client Bootstrap Ready</green>")
84+
85+
if !cfg.LocalDNSEnabled {
86+
return
87+
}
88+
89+
runCtx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
90+
defer stop()
91+
92+
if err := app.RunLocalDNSListener(runCtx); err != nil {
93+
_, _ = fmt.Fprintf(os.Stderr, "Local DNS listener failed: %v\n", err)
94+
os.Exit(1)
95+
}
7596
}

internal/client/client_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,3 +218,20 @@ func TestApplySessionCompressionPolicyKeepsLargeMTUDirections(t *testing.T) {
218218
t.Fatalf("download compression should stay off, got=%d", c.downloadCompression)
219219
}
220220
}
221+
222+
func TestNewKeepsLocalDNSDefaults(t *testing.T) {
223+
c := New(config.ClientConfig{
224+
LocalDNSEnabled: true,
225+
LocalDNSIP: "127.0.0.1",
226+
LocalDNSPort: 5353,
227+
LocalDNSWorkers: 2,
228+
LocalDNSQueueSize: 512,
229+
}, nil, nil)
230+
231+
if !c.cfg.LocalDNSEnabled {
232+
t.Fatal("expected local dns listener to stay enabled in config")
233+
}
234+
if c.cfg.LocalDNSIP != "127.0.0.1" || c.cfg.LocalDNSPort != 5353 {
235+
t.Fatalf("unexpected local dns bind config: %s:%d", c.cfg.LocalDNSIP, c.cfg.LocalDNSPort)
236+
}
237+
}

internal/client/local_dns.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// ==============================================================================
2+
// MasterDnsVPN
3+
// Author: MasterkinG32
4+
// Github: https://github.com/masterking32
5+
// Year: 2026
6+
// ==============================================================================
7+
8+
package client
9+
10+
import (
11+
"context"
12+
"net"
13+
"sync"
14+
15+
DnsParser "masterdnsvpn-go/internal/dnsparser"
16+
)
17+
18+
type localDNSRequest struct {
19+
packet []byte
20+
addr *net.UDPAddr
21+
}
22+
23+
func (c *Client) RunLocalDNSListener(ctx context.Context) error {
24+
if c == nil || !c.cfg.LocalDNSEnabled {
25+
return nil
26+
}
27+
28+
conn, err := net.ListenUDP("udp", &net.UDPAddr{
29+
IP: net.ParseIP(c.cfg.LocalDNSIP),
30+
Port: c.cfg.LocalDNSPort,
31+
})
32+
if err != nil {
33+
return err
34+
}
35+
defer conn.Close()
36+
37+
c.log.Infof(
38+
"📡 <green>Local DNS Listener Ready</green> <magenta>|</magenta> <blue>Addr</blue>: <cyan>%s:%d</cyan> <magenta>|</magenta> <blue>Workers</blue>: <magenta>%d</magenta>",
39+
c.cfg.LocalDNSIP,
40+
c.cfg.LocalDNSPort,
41+
c.cfg.LocalDNSWorkers,
42+
)
43+
44+
queue := make(chan localDNSRequest, c.cfg.LocalDNSQueueSize)
45+
var workerWG sync.WaitGroup
46+
for range c.cfg.LocalDNSWorkers {
47+
workerWG.Add(1)
48+
go func() {
49+
defer workerWG.Done()
50+
c.localDNSWorker(ctx, conn, queue)
51+
}()
52+
}
53+
54+
go func() {
55+
<-ctx.Done()
56+
_ = conn.Close()
57+
}()
58+
59+
buffer := make([]byte, EDnsSafeUDPSize)
60+
for {
61+
n, addr, err := conn.ReadFromUDP(buffer)
62+
if err != nil {
63+
if ctx.Err() != nil {
64+
break
65+
}
66+
return err
67+
}
68+
69+
packet := append([]byte(nil), buffer[:n]...)
70+
select {
71+
case queue <- localDNSRequest{packet: packet, addr: addr}:
72+
case <-ctx.Done():
73+
close(queue)
74+
workerWG.Wait()
75+
return nil
76+
default:
77+
response, responseErr := DnsParser.BuildRefusedResponse(packet)
78+
if responseErr == nil {
79+
_, _ = conn.WriteToUDP(response, addr)
80+
}
81+
}
82+
}
83+
84+
close(queue)
85+
workerWG.Wait()
86+
return nil
87+
}
88+
89+
func (c *Client) localDNSWorker(ctx context.Context, conn *net.UDPConn, queue <-chan localDNSRequest) {
90+
for {
91+
select {
92+
case <-ctx.Done():
93+
return
94+
case req, ok := <-queue:
95+
if !ok {
96+
return
97+
}
98+
response, err := DnsParser.BuildRefusedResponse(req.packet)
99+
if err == nil {
100+
_, _ = conn.WriteToUDP(response, req.addr)
101+
}
102+
}
103+
}
104+
}

internal/config/client.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ type ClientConfig struct {
2424
ConfigPath string `toml:"-"`
2525
ProtocolType string `toml:"PROTOCOL_TYPE"`
2626
Domains []string `toml:"DOMAINS"`
27+
LocalDNSEnabled bool `toml:"LOCAL_DNS_ENABLED"`
28+
LocalDNSIP string `toml:"LOCAL_DNS_IP"`
29+
LocalDNSPort int `toml:"LOCAL_DNS_PORT"`
30+
LocalDNSWorkers int `toml:"LOCAL_DNS_WORKERS"`
31+
LocalDNSQueueSize int `toml:"LOCAL_DNS_QUEUE_SIZE"`
2732
ResolverBalancingStrategy int `toml:"RESOLVER_BALANCING_STRATEGY"`
2833
BaseEncodeData bool `toml:"BASE_ENCODE_DATA"`
2934
UploadCompressionType int `toml:"UPLOAD_COMPRESSION_TYPE"`
@@ -47,6 +52,11 @@ func defaultClientConfig() ClientConfig {
4752
return ClientConfig{
4853
ProtocolType: "SOCKS5",
4954
Domains: nil,
55+
LocalDNSEnabled: false,
56+
LocalDNSIP: "127.0.0.1",
57+
LocalDNSPort: 5353,
58+
LocalDNSWorkers: 2,
59+
LocalDNSQueueSize: 512,
5060
ResolverBalancingStrategy: 0,
5161
BaseEncodeData: false,
5262
UploadCompressionType: compression.TypeOff,
@@ -99,6 +109,19 @@ func LoadClientConfig(filename string) (ClientConfig, error) {
99109
if cfg.DataEncryptionMethod < 0 || cfg.DataEncryptionMethod > 5 {
100110
return cfg, fmt.Errorf("invalid DATA_ENCRYPTION_METHOD: %d", cfg.DataEncryptionMethod)
101111
}
112+
cfg.LocalDNSIP = strings.TrimSpace(cfg.LocalDNSIP)
113+
if cfg.LocalDNSIP == "" {
114+
cfg.LocalDNSIP = "127.0.0.1"
115+
}
116+
if cfg.LocalDNSPort < 0 || cfg.LocalDNSPort > 65535 {
117+
return cfg, fmt.Errorf("invalid LOCAL_DNS_PORT: %d", cfg.LocalDNSPort)
118+
}
119+
if cfg.LocalDNSWorkers < 1 {
120+
cfg.LocalDNSWorkers = 1
121+
}
122+
if cfg.LocalDNSQueueSize < 1 {
123+
cfg.LocalDNSQueueSize = 512
124+
}
102125
if cfg.UploadCompressionType < compression.TypeOff || cfg.UploadCompressionType > compression.TypeZLIB {
103126
return cfg, fmt.Errorf("invalid UPLOAD_COMPRESSION_TYPE: %d", cfg.UploadCompressionType)
104127
}

0 commit comments

Comments
 (0)