Skip to content

Commit f641d38

Browse files
committed
Add support for client IP address encryption
1 parent 96f285e commit f641d38

18 files changed

Lines changed: 883 additions & 22 deletions

dnscrypt-proxy/common.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,15 @@ func ExtractClientIPStr(pluginsState *PluginsState) (string, bool) {
190190
}
191191
}
192192

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

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
}
@@ -443,6 +449,11 @@ func ConfigLoad(proxy *Proxy, flags *ConfigFlags) error {
443449
// Configure DNS64
444450
configureDNS64(proxy, &config)
445451

452+
// Configure IP encryption
453+
if err := configureIPEncryption(proxy, &config); err != nil {
454+
return err
455+
}
456+
446457
// Configure source restrictions
447458
configureSourceRestrictions(proxy, flags, &config)
448459

@@ -538,6 +549,19 @@ func configureDNS64(proxy *Proxy, config *Config) {
538549
proxy.dns64Resolvers = config.DNS64.Resolvers
539550
}
540551

552+
// configureIPEncryption - Helper function for IP encryption
553+
func configureIPEncryption(proxy *Proxy, config *Config) error {
554+
ipCryptConfig, err := NewIPCryptConfig(
555+
config.IPEncryption.Key,
556+
config.IPEncryption.Algorithm,
557+
)
558+
if err != nil {
559+
return fmt.Errorf("IP encryption configuration error: %w", err)
560+
}
561+
proxy.ipCryptConfig = ipCryptConfig
562+
return nil
563+
}
564+
541565
func (config *Config) printRegisteredServers(proxy *Proxy, jsonOutput bool, includeRelays bool) error {
542566
var summary []ServerSummary
543567
if includeRelays {

dnscrypt-proxy/example-dnscrypt-proxy.toml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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 and store it securely
978+
979+
# key = ""
980+
981+
952982
###############################################################################
953983
# Monitoring UI #
954984
###############################################################################

dnscrypt-proxy/plugin_allow_ip.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ type PluginAllowedIP struct {
1515
allowedIPs map[string]interface{}
1616
logger io.Writer
1717
format string
18+
ipCryptConfig *IPCryptConfig
1819

1920
// Hot-reloading support
2021
rwLock sync.RWMutex
@@ -50,6 +51,7 @@ func (plugin *PluginAllowedIP) Init(proxy *Proxy) error {
5051
}
5152

5253
plugin.logger, plugin.format = InitializePluginLogger(proxy.allowedIPLogFile, proxy.allowedIPFormat, proxy.logMaxSize, proxy.logMaxAge, proxy.logMaxBackups)
54+
plugin.ipCryptConfig = proxy.ipCryptConfig
5355

5456
return nil
5557
}
@@ -169,7 +171,7 @@ func (plugin *PluginAllowedIP) Eval(pluginsState *PluginsState, msg *dns.Msg) er
169171
pluginsState.sessionData["whitelisted"] = true
170172
if plugin.logger != nil {
171173
qName := pluginsState.qName
172-
clientIPStr, ok := ExtractClientIPStr(pluginsState)
174+
clientIPStr, ok := ExtractClientIPStrEncrypted(pluginsState, plugin.ipCryptConfig)
173175
if !ok {
174176
// Ignore internal flow.
175177
return nil

dnscrypt-proxy/plugin_allow_name.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ type PluginAllowName struct {
1414
patternMatcher *PatternMatcher
1515
logger io.Writer
1616
format string
17+
ipCryptConfig *IPCryptConfig
1718

1819
// Hot-reloading support
1920
rwLock sync.RWMutex
@@ -47,6 +48,7 @@ func (plugin *PluginAllowName) Init(proxy *Proxy) error {
4748
}
4849

4950
plugin.logger, plugin.format = InitializePluginLogger(proxy.allowNameLogFile, proxy.allowNameFormat, proxy.logMaxSize, proxy.logMaxAge, proxy.logMaxBackups)
51+
plugin.ipCryptConfig = proxy.ipCryptConfig
5052

5153
return nil
5254
}
@@ -153,7 +155,7 @@ func (plugin *PluginAllowName) Eval(pluginsState *PluginsState, msg *dns.Msg) er
153155
if allowList {
154156
pluginsState.sessionData["whitelisted"] = true
155157
if plugin.logger != nil {
156-
clientIPStr, ok := ExtractClientIPStr(pluginsState)
158+
clientIPStr, ok := ExtractClientIPStrEncrypted(pluginsState, plugin.ipCryptConfig)
157159
if !ok {
158160
// Ignore internal flow.
159161
return nil

dnscrypt-proxy/plugin_block_ip.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ type PluginBlockIP struct {
1515
blockedIPs map[string]interface{}
1616
logger io.Writer
1717
format string
18+
ipCryptConfig *IPCryptConfig
1819

1920
// Hot-reloading support
2021
rwLock sync.RWMutex
@@ -50,6 +51,7 @@ func (plugin *PluginBlockIP) Init(proxy *Proxy) error {
5051
}
5152

5253
plugin.logger, plugin.format = InitializePluginLogger(proxy.blockIPLogFile, proxy.blockIPFormat, proxy.logMaxSize, proxy.logMaxAge, proxy.logMaxBackups)
54+
plugin.ipCryptConfig = proxy.ipCryptConfig
5355

5456
return nil
5557
}
@@ -174,7 +176,7 @@ func (plugin *PluginBlockIP) Eval(pluginsState *PluginsState, msg *dns.Msg) erro
174176
pluginsState.returnCode = PluginsReturnCodeReject
175177
if plugin.logger != nil {
176178
qName := pluginsState.qName
177-
clientIPStr, ok := ExtractClientIPStr(pluginsState)
179+
clientIPStr, ok := ExtractClientIPStrEncrypted(pluginsState, plugin.ipCryptConfig)
178180
if !ok {
179181
// Ignore internal flow.
180182
return nil

dnscrypt-proxy/plugin_block_name.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ type BlockedNames struct {
1414
patternMatcher *PatternMatcher
1515
logger io.Writer
1616
format string
17+
ipCryptConfig *IPCryptConfig
1718
}
1819

1920
const aliasesLimit = 8
@@ -44,7 +45,7 @@ func (blockedNames *BlockedNames) check(pluginsState *PluginsState, qName string
4445
pluginsState.action = PluginsActionReject
4546
pluginsState.returnCode = PluginsReturnCodeReject
4647
if blockedNames.logger != nil {
47-
clientIPStr, ok := ExtractClientIPStr(pluginsState)
48+
clientIPStr, ok := ExtractClientIPStrEncrypted(pluginsState, blockedNames.ipCryptConfig)
4849
if !ok {
4950
// Ignore internal flow.
5051
return false, nil
@@ -86,6 +87,7 @@ func (plugin *PluginBlockName) Init(proxy *Proxy) error {
8687
xBlockedNames := BlockedNames{
8788
allWeeklyRanges: proxy.allWeeklyRanges,
8889
patternMatcher: NewPatternMatcher(),
90+
ipCryptConfig: proxy.ipCryptConfig,
8991
}
9092

9193
if err := plugin.loadRules(lines, &xBlockedNames); err != nil {

dnscrypt-proxy/plugin_nx_log.go

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@ import (
44
"errors"
55
"fmt"
66
"io"
7-
"net"
87
"time"
98

109
"github.com/jedisct1/dlog"
1110
"github.com/miekg/dns"
1211
)
1312

1413
type PluginNxLog struct {
15-
logger io.Writer
16-
format string
14+
logger io.Writer
15+
format string
16+
ipCryptConfig *IPCryptConfig
1717
}
1818

1919
func (plugin *PluginNxLog) Name() string {
@@ -27,6 +27,7 @@ func (plugin *PluginNxLog) Description() string {
2727
func (plugin *PluginNxLog) Init(proxy *Proxy) error {
2828
plugin.logger = Logger(proxy.logMaxSize, proxy.logMaxAge, proxy.logMaxBackups, proxy.nxLogFile)
2929
plugin.format = proxy.nxLogFormat
30+
plugin.ipCryptConfig = proxy.ipCryptConfig
3031

3132
return nil
3233
}
@@ -43,13 +44,8 @@ func (plugin *PluginNxLog) Eval(pluginsState *PluginsState, msg *dns.Msg) error
4344
if msg.Rcode != dns.RcodeNameError {
4445
return nil
4546
}
46-
var clientIPStr string
47-
switch pluginsState.clientProto {
48-
case "udp":
49-
clientIPStr = (*pluginsState.clientAddr).(*net.UDPAddr).IP.String()
50-
case "tcp", "local_doh":
51-
clientIPStr = (*pluginsState.clientAddr).(*net.TCPAddr).IP.String()
52-
default:
47+
clientIPStr, ok := ExtractClientIPStrEncrypted(pluginsState, plugin.ipCryptConfig)
48+
if !ok {
5349
// Ignore internal flow.
5450
return nil
5551
}

dnscrypt-proxy/plugin_query_log.go

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"errors"
55
"fmt"
66
"io"
7-
"net"
87
"strings"
98
"time"
109

@@ -16,6 +15,7 @@ type PluginQueryLog struct {
1615
logger io.Writer
1716
format string
1817
ignoredQtypes []string
18+
ipCryptConfig *IPCryptConfig
1919
}
2020

2121
func (plugin *PluginQueryLog) Name() string {
@@ -30,6 +30,7 @@ func (plugin *PluginQueryLog) Init(proxy *Proxy) error {
3030
plugin.logger = Logger(proxy.logMaxSize, proxy.logMaxAge, proxy.logMaxBackups, proxy.queryLogFile)
3131
plugin.format = proxy.queryLogFormat
3232
plugin.ignoredQtypes = proxy.queryLogIgnoredQtypes
33+
plugin.ipCryptConfig = proxy.ipCryptConfig
3334

3435
return nil
3536
}
@@ -43,13 +44,8 @@ func (plugin *PluginQueryLog) Reload() error {
4344
}
4445

4546
func (plugin *PluginQueryLog) Eval(pluginsState *PluginsState, msg *dns.Msg) error {
46-
var clientIPStr string
47-
switch pluginsState.clientProto {
48-
case "udp":
49-
clientIPStr = (*pluginsState.clientAddr).(*net.UDPAddr).IP.String()
50-
case "tcp", "local_doh":
51-
clientIPStr = (*pluginsState.clientAddr).(*net.TCPAddr).IP.String()
52-
default:
47+
clientIPStr, ok := ExtractClientIPStrEncrypted(pluginsState, plugin.ipCryptConfig)
48+
if !ok {
5349
// Ignore internal flow.
5450
return nil
5551
}

dnscrypt-proxy/proxy.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ type Proxy struct {
106106
SourceDoH bool
107107
SourceODoH bool
108108
listenersMu sync.Mutex
109+
ipCryptConfig *IPCryptConfig
109110
}
110111

111112
func (proxy *Proxy) registerUDPListener(conn *net.UDPConn) {

0 commit comments

Comments
 (0)