dnscrypt-proxy/dnscrypt-proxy/plugins.go

402 lines
12 KiB
Go
Raw Permalink Normal View History

package main
import (
"errors"
"net"
"strings"
2018-01-10 18:32:05 +01:00
"sync"
"time"
2018-01-10 18:32:05 +01:00
"github.com/jedisct1/dlog"
"github.com/miekg/dns"
)
type PluginsAction int
const (
PluginsActionNone = 0
PluginsActionContinue = 1
PluginsActionDrop = 2
PluginsActionReject = 3
PluginsActionSynth = 4
)
type PluginsGlobals struct {
sync.RWMutex
queryPlugins *[]Plugin
responsePlugins *[]Plugin
loggingPlugins *[]Plugin
refusedCodeInResponses bool
respondWithIPv4 net.IP
respondWithIPv6 net.IP
}
2018-06-04 23:18:28 +02:00
type PluginsReturnCode int
const (
PluginsReturnCodePass = iota
PluginsReturnCodeForward
PluginsReturnCodeDrop
PluginsReturnCodeReject
PluginsReturnCodeSynth
PluginsReturnCodeParseError
PluginsReturnCodeNXDomain
PluginsReturnCodeResponseError
PluginsReturnCodeServFail
2019-11-17 19:48:15 +01:00
PluginsReturnCodeNetworkError
2019-03-02 18:01:21 +01:00
PluginsReturnCodeCloak
PluginsReturnCodeServerTimeout
2021-01-03 18:09:03 +01:00
PluginsReturnCodeNotReady
2018-06-04 23:18:28 +02:00
)
var PluginsReturnCodeToString = map[PluginsReturnCode]string{
PluginsReturnCodePass: "PASS",
PluginsReturnCodeForward: "FORWARD",
PluginsReturnCodeDrop: "DROP",
PluginsReturnCodeReject: "REJECT",
PluginsReturnCodeSynth: "SYNTH",
PluginsReturnCodeParseError: "PARSE_ERROR",
PluginsReturnCodeNXDomain: "NXDOMAIN",
PluginsReturnCodeResponseError: "RESPONSE_ERROR",
PluginsReturnCodeServFail: "SERVFAIL",
2019-11-17 19:48:15 +01:00
PluginsReturnCodeNetworkError: "NETWORK_ERROR",
2019-03-02 18:01:21 +01:00
PluginsReturnCodeCloak: "CLOAK",
PluginsReturnCodeServerTimeout: "SERVER_TIMEOUT",
2021-01-03 18:09:03 +01:00
PluginsReturnCodeNotReady: "NOT_READY",
2018-06-04 23:18:28 +02:00
}
type PluginsState struct {
2020-11-14 14:46:59 +01:00
requestStart time.Time
requestEnd time.Time
clientProto string
2020-11-14 14:46:59 +01:00
serverName string
serverProto string
qName string
clientAddr *net.Addr
synthResponse *dns.Msg
2020-11-14 14:46:59 +01:00
questionMsg *dns.Msg
sessionData map[string]interface{}
action PluginsAction
timeout time.Duration
returnCode PluginsReturnCode
maxPayloadSize int
cacheSize int
2020-11-14 14:46:59 +01:00
originalMaxPayloadSize int
maxUnencryptedUDPSafePayloadSize int
rejectTTL uint32
cacheMaxTTL uint32
cacheNegMaxTTL uint32
2020-11-14 14:46:59 +01:00
cacheNegMinTTL uint32
cacheMinTTL uint32
cacheHit bool
2020-11-14 14:46:59 +01:00
dnssec bool
2018-01-10 10:11:59 +01:00
}
2019-10-09 17:59:46 +02:00
func (proxy *Proxy) InitPluginsGlobals() error {
2018-01-10 17:23:20 +01:00
queryPlugins := &[]Plugin{}
2019-09-07 16:19:47 +02:00
if proxy.captivePortalMap != nil {
*queryPlugins = append(*queryPlugins, Plugin(new(PluginCaptivePortal)))
}
2019-09-07 16:19:47 +02:00
if len(proxy.queryMeta) != 0 {
*queryPlugins = append(*queryPlugins, Plugin(new(PluginQueryMeta)))
}
2021-03-30 11:02:47 +02:00
if len(proxy.allowNameFile) != 0 {
*queryPlugins = append(*queryPlugins, Plugin(new(PluginAllowName)))
2018-04-07 23:02:40 +02:00
}
*queryPlugins = append(*queryPlugins, Plugin(new(PluginFirefox)))
if len(proxy.ednsClientSubnets) != 0 {
*queryPlugins = append(*queryPlugins, Plugin(new(PluginECS)))
}
if len(proxy.blockNameFile) != 0 {
*queryPlugins = append(*queryPlugins, Plugin(new(PluginBlockName)))
}
2018-01-10 17:23:20 +01:00
if proxy.pluginBlockIPv6 {
*queryPlugins = append(*queryPlugins, Plugin(new(PluginBlockIPv6)))
}
if len(proxy.cloakFile) != 0 {
*queryPlugins = append(*queryPlugins, Plugin(new(PluginCloak)))
}
2018-01-10 17:23:20 +01:00
*queryPlugins = append(*queryPlugins, Plugin(new(PluginGetSetPayloadSize)))
2018-01-10 18:53:09 +01:00
if proxy.cache {
*queryPlugins = append(*queryPlugins, Plugin(new(PluginCache)))
}
2018-01-17 09:44:03 +01:00
if len(proxy.forwardFile) != 0 {
*queryPlugins = append(*queryPlugins, Plugin(new(PluginForward)))
}
if proxy.pluginBlockUnqualified {
*queryPlugins = append(*queryPlugins, Plugin(new(PluginBlockUnqualified)))
}
if proxy.pluginBlockUndelegated {
*queryPlugins = append(*queryPlugins, Plugin(new(PluginBlockUndelegated)))
}
2018-01-10 10:11:59 +01:00
responsePlugins := &[]Plugin{}
if len(proxy.nxLogFile) != 0 {
*responsePlugins = append(*responsePlugins, Plugin(new(PluginNxLog)))
}
2020-11-15 20:59:58 +01:00
if len(proxy.allowedIPFile) != 0 {
*responsePlugins = append(*responsePlugins, Plugin(new(PluginAllowedIP)))
}
if len(proxy.blockNameFile) != 0 {
*responsePlugins = append(*responsePlugins, Plugin(new(PluginBlockNameResponse)))
}
2018-01-21 16:07:44 +01:00
if len(proxy.blockIPFile) != 0 {
*responsePlugins = append(*responsePlugins, Plugin(new(PluginBlockIP)))
}
2020-06-08 18:23:03 +02:00
if len(proxy.dns64Resolvers) != 0 || len(proxy.dns64Prefixes) != 0 {
2020-06-19 11:37:53 +02:00
*responsePlugins = append(*responsePlugins, Plugin(new(PluginDNS64)))
2020-06-08 18:23:03 +02:00
}
2018-01-21 16:07:44 +01:00
if proxy.cache {
*responsePlugins = append(*responsePlugins, Plugin(new(PluginCacheResponse)))
}
loggingPlugins := &[]Plugin{}
if len(proxy.queryLogFile) != 0 {
*loggingPlugins = append(*loggingPlugins, Plugin(new(PluginQueryLog)))
}
for _, plugin := range *queryPlugins {
if err := plugin.Init(proxy); err != nil {
return err
}
}
for _, plugin := range *responsePlugins {
if err := plugin.Init(proxy); err != nil {
return err
}
}
for _, plugin := range *loggingPlugins {
if err := plugin.Init(proxy); err != nil {
return err
}
}
2019-10-09 17:59:46 +02:00
proxy.pluginsGlobals.queryPlugins = queryPlugins
proxy.pluginsGlobals.responsePlugins = responsePlugins
proxy.pluginsGlobals.loggingPlugins = loggingPlugins
2019-10-09 17:59:46 +02:00
parseBlockedQueryResponse(proxy.blockedQueryResponse, &proxy.pluginsGlobals)
return nil
}
// blockedQueryResponse can be 'refused', 'hinfo' or IP responses 'a:IPv4,aaaa:IPv6
2019-10-12 22:18:10 +02:00
func parseBlockedQueryResponse(blockedResponse string, pluginsGlobals *PluginsGlobals) {
blockedResponse = StringStripSpaces(strings.ToLower(blockedResponse))
2019-10-12 22:18:10 +02:00
if strings.HasPrefix(blockedResponse, "a:") {
blockedIPStrings := strings.Split(blockedResponse, ",")
pluginsGlobals.respondWithIPv4 = net.ParseIP(strings.TrimPrefix(blockedIPStrings[0], "a:"))
if pluginsGlobals.respondWithIPv4 == nil {
dlog.Notice("Error parsing IPv4 response given in blocked_query_response option, defaulting to `hinfo`")
pluginsGlobals.refusedCodeInResponses = false
return
}
if len(blockedIPStrings) > 1 {
if strings.HasPrefix(blockedIPStrings[1], "aaaa:") {
ipv6Response := strings.TrimPrefix(blockedIPStrings[1], "aaaa:")
if strings.HasPrefix(ipv6Response, "[") {
ipv6Response = strings.Trim(ipv6Response, "[]")
}
pluginsGlobals.respondWithIPv6 = net.ParseIP(ipv6Response)
if pluginsGlobals.respondWithIPv6 == nil {
dlog.Notice(
"Error parsing IPv6 response given in blocked_query_response option, defaulting to IPv4",
)
}
} else {
dlog.Noticef("Invalid IPv6 response given in blocked_query_response option [%s], the option should take the form 'a:<IPv4>,aaaa:<IPv6>'", blockedIPStrings[1])
}
}
if pluginsGlobals.respondWithIPv6 == nil {
pluginsGlobals.respondWithIPv6 = pluginsGlobals.respondWithIPv4
}
} else {
2019-10-12 22:18:10 +02:00
switch blockedResponse {
case "refused":
pluginsGlobals.refusedCodeInResponses = true
case "hinfo":
pluginsGlobals.refusedCodeInResponses = false
default:
2019-10-12 22:18:10 +02:00
dlog.Noticef("Invalid blocked_query_response option [%s], defaulting to `hinfo`", blockedResponse)
pluginsGlobals.refusedCodeInResponses = false
}
}
}
type Plugin interface {
Name() string
Description() string
Init(proxy *Proxy) error
Drop() error
Reload() error
Eval(pluginsState *PluginsState, msg *dns.Msg) error
}
func NewPluginsState(
proxy *Proxy,
clientProto string,
clientAddr *net.Addr,
serverProto string,
start time.Time,
) PluginsState {
2018-01-10 18:32:05 +01:00
return PluginsState{
action: PluginsActionContinue,
2019-12-17 18:54:49 +01:00
returnCode: PluginsReturnCodePass,
maxPayloadSize: MaxDNSUDPPacketSize - ResponseOverhead,
clientProto: clientProto,
clientAddr: clientAddr,
cacheSize: proxy.cacheSize,
cacheNegMinTTL: proxy.cacheNegMinTTL,
cacheNegMaxTTL: proxy.cacheNegMaxTTL,
cacheMinTTL: proxy.cacheMinTTL,
cacheMaxTTL: proxy.cacheMaxTTL,
rejectTTL: proxy.rejectTTL,
questionMsg: nil,
qName: "",
serverName: "-",
serverProto: serverProto,
timeout: proxy.timeout,
requestStart: start,
maxUnencryptedUDPSafePayloadSize: MaxDNSUDPSafePacketSize,
2020-01-30 13:15:29 +01:00
sessionData: make(map[string]interface{}),
2018-01-10 18:32:05 +01:00
}
}
func (pluginsState *PluginsState) ApplyQueryPlugins(
pluginsGlobals *PluginsGlobals,
packet []byte,
needsEDNS0Padding bool,
) ([]byte, error) {
msg := dns.Msg{}
if err := msg.Unpack(packet); err != nil {
return packet, err
}
if len(msg.Question) != 1 {
return packet, errors.New("Unexpected number of questions")
}
qName, err := NormalizeQName(msg.Question[0].Name)
if err != nil {
return packet, err
}
2020-08-09 13:09:37 +02:00
dlog.Debugf("Handling query for [%v]", qName)
pluginsState.qName = qName
pluginsState.questionMsg = &msg
if len(*pluginsGlobals.queryPlugins) == 0 && len(*pluginsGlobals.loggingPlugins) == 0 {
return packet, nil
}
pluginsGlobals.RLock()
defer pluginsGlobals.RUnlock()
for _, plugin := range *pluginsGlobals.queryPlugins {
if err := plugin.Eval(pluginsState, &msg); err != nil {
2018-01-10 10:11:59 +01:00
pluginsState.action = PluginsActionDrop
return packet, err
2018-01-10 10:11:59 +01:00
}
if pluginsState.action == PluginsActionReject {
synth := RefusedResponseFromMessage(
&msg,
pluginsGlobals.refusedCodeInResponses,
pluginsGlobals.respondWithIPv4,
pluginsGlobals.respondWithIPv6,
pluginsState.rejectTTL,
)
pluginsState.synthResponse = synth
}
if pluginsState.action != PluginsActionContinue {
2018-01-10 17:23:20 +01:00
break
}
}
packet2, err := msg.PackBuffer(packet)
if err != nil {
return packet, err
}
2019-12-23 11:37:45 +01:00
if needsEDNS0Padding && pluginsState.action == PluginsActionContinue {
2019-12-23 23:17:46 +01:00
padLen := 63 - ((len(packet2) + 63) & 63)
2019-12-23 11:37:45 +01:00
if paddedPacket2, _ := addEDNS0PaddingIfNoneFound(&msg, packet2, padLen); paddedPacket2 != nil {
return paddedPacket2, nil
}
}
return packet2, nil
}
func (pluginsState *PluginsState) ApplyResponsePlugins(
pluginsGlobals *PluginsGlobals,
packet []byte,
ttl *uint32,
) ([]byte, error) {
2019-11-18 01:13:18 +01:00
msg := dns.Msg{Compress: true}
2018-01-10 18:32:05 +01:00
if err := msg.Unpack(packet); err != nil {
if len(packet) >= MinDNSPacketSize && HasTCFlag(packet) {
err = nil
}
2018-01-10 18:32:05 +01:00
return packet, err
}
2018-06-04 23:18:28 +02:00
switch Rcode(packet) {
case dns.RcodeSuccess:
pluginsState.returnCode = PluginsReturnCodePass
case dns.RcodeNameError:
pluginsState.returnCode = PluginsReturnCodeNXDomain
case dns.RcodeServerFailure:
pluginsState.returnCode = PluginsReturnCodeServFail
2018-06-04 23:18:28 +02:00
default:
pluginsState.returnCode = PluginsReturnCodeResponseError
}
2019-12-22 18:02:33 +01:00
removeEDNS0Options(&msg)
pluginsGlobals.RLock()
defer pluginsGlobals.RUnlock()
for _, plugin := range *pluginsGlobals.responsePlugins {
if err := plugin.Eval(pluginsState, &msg); err != nil {
2018-01-10 18:32:05 +01:00
pluginsState.action = PluginsActionDrop
return packet, err
2018-01-10 18:32:05 +01:00
}
if pluginsState.action == PluginsActionReject {
synth := RefusedResponseFromMessage(
&msg,
pluginsGlobals.refusedCodeInResponses,
pluginsGlobals.respondWithIPv4,
pluginsGlobals.respondWithIPv6,
pluginsState.rejectTTL,
)
pluginsState.synthResponse = synth
}
if pluginsState.action != PluginsActionContinue {
2018-01-10 18:32:05 +01:00
break
}
}
if ttl != nil {
setMaxTTL(&msg, *ttl)
}
2018-01-10 18:32:05 +01:00
packet2, err := msg.PackBuffer(packet)
if err != nil {
return packet, err
}
return packet2, nil
}
func (pluginsState *PluginsState) ApplyLoggingPlugins(pluginsGlobals *PluginsGlobals) error {
if len(*pluginsGlobals.loggingPlugins) == 0 {
return nil
}
pluginsState.requestEnd = time.Now()
questionMsg := pluginsState.questionMsg
if questionMsg == nil {
return errors.New("Question not found")
}
pluginsGlobals.RLock()
defer pluginsGlobals.RUnlock()
for _, plugin := range *pluginsGlobals.loggingPlugins {
if err := plugin.Eval(pluginsState, questionMsg); err != nil {
return err
}
}
return nil
}