Skip to content

Commit 0624ab7

Browse files
committed
Merge branch 'master' of github.com:DNSCrypt/dnscrypt-proxy
2 parents 634168b + e9d11c5 commit 0624ab7

47 files changed

Lines changed: 2257 additions & 501 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/codeql-analysis.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ jobs:
1313

1414
steps:
1515
- name: Checkout repository
16-
uses: actions/checkout@v4
16+
uses: actions/checkout@v5
1717
with:
1818
fetch-depth: 2
1919

2020
- name: Setup Go
21-
uses: actions/setup-go@v5
21+
uses: actions/setup-go@v6
2222
with:
2323
go-version-file: 'go.mod'
2424

.github/workflows/releases.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ jobs:
3535
echo "Tag version: $VERSION"
3636
3737
- name: Check out code
38-
uses: actions/checkout@v4
38+
uses: actions/checkout@v5
3939

4040
- name: Set up Go
41-
uses: actions/setup-go@v5
41+
uses: actions/setup-go@v6
4242
with:
4343
go-version: 1
4444
check-latest: true

ChangeLog

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
# Version 2.1.14 (not released yet)
1+
# Version 2.1.14
2+
- Added support for client IP address encryption in logs using IPCrypt
3+
(https://ipcrypt-std.github.io/). Three algorithms are supported:
4+
deterministic, non-deterministic with 8-byte tweak, and extended
5+
non-deterministic with 16-byte tweak.
6+
- Enhanced pattern rule documentation with better examples.
7+
- Fixed an issue where nil client addresses could cause crashes.
28

39
# Version 2.1.13
410
- Fixed race conditions in WebSocket handling for the monitoring dashboard,

dnscrypt-proxy/common.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ func ExtractHostAndPort(str string, defaultPort int) (host string, port int) {
161161
host, port = host[:idx], portX
162162
}
163163
}
164-
return
164+
return host, port
165165
}
166166

167167
// ReadTextFile reads a file and returns its contents as a string.
@@ -180,6 +180,9 @@ func isDigit(b byte) bool { return b >= '0' && b <= '9' }
180180

181181
// ExtractClientIPStr extracts client IP string from pluginsState based on protocol
182182
func ExtractClientIPStr(pluginsState *PluginsState) (string, bool) {
183+
if pluginsState.clientAddr == nil {
184+
return "", false
185+
}
183186
switch pluginsState.clientProto {
184187
case "udp":
185188
return (*pluginsState.clientAddr).(*net.UDPAddr).IP.String(), true
@@ -190,6 +193,15 @@ func ExtractClientIPStr(pluginsState *PluginsState) (string, bool) {
190193
}
191194
}
192195

196+
// ExtractClientIPStrEncrypted extracts and optionally encrypts client IP string
197+
func ExtractClientIPStrEncrypted(pluginsState *PluginsState, ipCryptConfig *IPCryptConfig) (string, bool) {
198+
ipStr, ok := ExtractClientIPStr(pluginsState)
199+
if !ok || ipCryptConfig == nil {
200+
return ipStr, ok
201+
}
202+
return ipCryptConfig.EncryptIPString(ipStr), ok
203+
}
204+
193205
// FormatLogLine formats a log line based on the specified format (tsv or ltsv)
194206
func FormatLogLine(format, clientIP, qName, reason string, additionalFields ...string) (string, error) {
195207
if format == "tsv" {

dnscrypt-proxy/common_test.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package main
2+
3+
import (
4+
"net"
5+
"testing"
6+
)
7+
8+
func TestExtractClientIPStr(t *testing.T) {
9+
tests := []struct {
10+
name string
11+
pluginsState *PluginsState
12+
wantIP string
13+
wantOK bool
14+
}{
15+
{
16+
name: "nil clientAddr should return empty",
17+
pluginsState: &PluginsState{
18+
clientProto: "tcp",
19+
clientAddr: nil,
20+
},
21+
wantIP: "",
22+
wantOK: false,
23+
},
24+
{
25+
name: "valid UDP address",
26+
pluginsState: &PluginsState{
27+
clientProto: "udp",
28+
clientAddr: func() *net.Addr {
29+
addr := net.Addr(
30+
&net.UDPAddr{
31+
IP: net.ParseIP("192.168.1.1"),
32+
Port: 53,
33+
},
34+
)
35+
return &addr
36+
}(),
37+
},
38+
wantIP: "192.168.1.1",
39+
wantOK: true,
40+
},
41+
{
42+
name: "valid TCP address",
43+
pluginsState: &PluginsState{
44+
clientProto: "tcp",
45+
clientAddr: func() *net.Addr {
46+
addr := net.Addr(
47+
&net.TCPAddr{
48+
IP: net.ParseIP("10.0.0.1"),
49+
Port: 53,
50+
},
51+
)
52+
return &addr
53+
}(),
54+
},
55+
wantIP: "10.0.0.1",
56+
wantOK: true,
57+
},
58+
{
59+
name: "unknown protocol",
60+
pluginsState: &PluginsState{
61+
clientProto: "unknown",
62+
clientAddr: func() *net.Addr {
63+
addr := net.Addr(
64+
&net.TCPAddr{
65+
IP: net.ParseIP("10.0.0.1"),
66+
Port: 53,
67+
},
68+
)
69+
return &addr
70+
}(),
71+
},
72+
wantIP: "",
73+
wantOK: false,
74+
},
75+
}
76+
77+
for _, tt := range tests {
78+
t.Run(tt.name, func(t *testing.T) {
79+
gotIP, gotOK := ExtractClientIPStr(tt.pluginsState)
80+
if gotIP != tt.wantIP {
81+
t.Errorf("ExtractClientIPStr() IP = %v, want %v", gotIP, tt.wantIP)
82+
}
83+
if gotOK != tt.wantOK {
84+
t.Errorf("ExtractClientIPStr() OK = %v, want %v", gotOK, tt.wantOK)
85+
}
86+
})
87+
}
88+
}

dnscrypt-proxy/config.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ type Config struct {
105105
DoHClientX509AuthLegacy DoHClientX509AuthConfig `toml:"tls_client_auth"`
106106
DNS64 DNS64Config `toml:"dns64"`
107107
EDNSClientSubnet []string `toml:"edns_client_subnet"`
108+
IPEncryption IPEncryptionConfig `toml:"ip_encryption"`
108109
}
109110

110111
func newConfig() Config {
@@ -291,6 +292,11 @@ type DNS64Config struct {
291292
Resolvers []string `toml:"resolver"`
292293
}
293294

295+
type IPEncryptionConfig struct {
296+
Key string `toml:"key"`
297+
Algorithm string `toml:"algorithm"`
298+
}
299+
294300
type CaptivePortalsConfig struct {
295301
MapFile string `toml:"map_file"`
296302
}
@@ -449,6 +455,11 @@ func ConfigLoad(proxy *Proxy, flags *ConfigFlags) error {
449455
// Configure DNS64
450456
configureDNS64(proxy, &config)
451457

458+
// Configure IP encryption
459+
if err := configureIPEncryption(proxy, &config); err != nil {
460+
return err
461+
}
462+
452463
// Configure source restrictions
453464
configureSourceRestrictions(proxy, flags, &config)
454465

@@ -544,6 +555,19 @@ func configureDNS64(proxy *Proxy, config *Config) {
544555
proxy.dns64Resolvers = config.DNS64.Resolvers
545556
}
546557

558+
// configureIPEncryption - Helper function for IP encryption
559+
func configureIPEncryption(proxy *Proxy, config *Config) error {
560+
ipCryptConfig, err := NewIPCryptConfig(
561+
config.IPEncryption.Key,
562+
config.IPEncryption.Algorithm,
563+
)
564+
if err != nil {
565+
return fmt.Errorf("IP encryption configuration error: %w", err)
566+
}
567+
proxy.ipCryptConfig = ipCryptConfig
568+
return nil
569+
}
570+
547571
func (config *Config) printRegisteredServers(proxy *Proxy, jsonOutput bool, includeRelays bool) error {
548572
var summary []ServerSummary
549573
if includeRelays {

dnscrypt-proxy/crypto.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ func ComputeSharedKey(
6969
}
7070
}
7171
}
72-
return
72+
return sharedKey
7373
}
7474

7575
func (proxy *Proxy) Encrypt(
@@ -116,7 +116,7 @@ func (proxy *Proxy) Encrypt(
116116
}
117117
if QueryOverhead+len(packet)+1 > paddedLength {
118118
err = errors.New("Question too large; cannot be padded")
119-
return
119+
return sharedKey, encrypted, clientNonce, err
120120
}
121121
encrypted = append(serverInfo.MagicQuery[:], publicKey[:]...)
122122
encrypted = append(encrypted, nonce[:HalfNonceSize]...)
@@ -128,7 +128,7 @@ func (proxy *Proxy) Encrypt(
128128
copy(xsalsaNonce[:], nonce)
129129
encrypted = secretbox.Seal(encrypted, padded, &xsalsaNonce, sharedKey)
130130
}
131-
return
131+
return sharedKey, encrypted, clientNonce, err
132132
}
133133

134134
func (proxy *Proxy) Decrypt(

dnscrypt-proxy/dnscrypt_certs.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ func FetchCurrentDNSCryptCert(
7474
now := uint32(time.Now().Unix())
7575
certInfo := CertInfo{CryptoConstruction: UndefinedConstruction}
7676
highestSerial := uint32(0)
77-
var certCountStr string
77+
certCountStr := ""
7878
for _, answerRr := range in.Answer {
7979
var txt string
8080
if t, ok := answerRr.(*dns.TXT); !ok {

dnscrypt-proxy/example-dnscrypt-proxy.toml

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -790,7 +790,7 @@ prefix = ''
790790
### Quad9
791791

792792
# [sources.quad9-resolvers]
793-
# urls = ['https://quad9.net/dnscrypt/quad9-resolvers.md', 'https://raw.githubusercontent.com/Quad9DNS/dnscrypt-settings/main/dnscrypt/quad9-resolvers.md']
793+
# urls = ['https://quad9.net/dnscrypt/quad9-resolvers.md']
794794
# minisign_key = 'RWQBphd2+f6eiAqBsvDZEBXBGHQBJfeG6G+wJPPKxCZMoEQYpmoysKUN'
795795
# cache_file = 'quad9-resolvers.md'
796796
# prefix = 'quad9-'
@@ -949,6 +949,36 @@ skip_incompatible = false
949949
# resolver = ['[2606:4700:4700::64]:53', '[2001:4860:4860::64]:53']
950950

951951

952+
###############################################################################
953+
# IP Encryption #
954+
###############################################################################
955+
956+
[ip_encryption]
957+
958+
## Encrypt client IP addresses in plugin logs using IPCrypt
959+
## This provides privacy for client IP addresses while maintaining
960+
## the ability to distinguish between different clients in logs
961+
962+
## Encryption algorithm (default: "none")
963+
## - "none": No encryption (default)
964+
## - "ipcrypt-deterministic": Deterministic encryption (same IP always encrypts to same value) - requires 16-byte key
965+
## - "ipcrypt-nd": Non-deterministic encryption with 8-byte tweak - requires 16-byte key
966+
## - "ipcrypt-ndx": Non-deterministic encryption with 16-byte tweak (extended) - requires 32-byte key
967+
968+
algorithm = "none"
969+
970+
## Encryption key in hexadecimal format (required if algorithm is not "none")
971+
## Key size depends on algorithm:
972+
## - ipcrypt-deterministic: 32 hex chars (16 bytes) - Generate with: openssl rand -hex 16
973+
## - ipcrypt-nd: 32 hex chars (16 bytes) - Generate with: openssl rand -hex 16
974+
## - ipcrypt-ndx: 64 hex chars (32 bytes) - Generate with: openssl rand -hex 32
975+
## Example for deterministic/nd: key = "1234567890abcdef1234567890abcdef"
976+
## Example for ndx: key = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
977+
## IMPORTANT: Keep this key secret
978+
979+
key = ""
980+
981+
952982
###############################################################################
953983
# Monitoring UI #
954984
###############################################################################

0 commit comments

Comments
 (0)