plugins.go 7.12 KB
Newer Older
1 2 3
package main

import (
4
	"errors"
5
	"net"
6 7
	"sync"

8
	"github.com/jedisct1/dlog"
9 10 11 12 13 14 15 16
	"github.com/miekg/dns"
)

type PluginsAction int

const (
	PluginsActionNone    = 0
	PluginsActionForward = 1
17 18
	PluginsActionDrop    = 2
	PluginsActionReject  = 3
19
	PluginsActionSynth   = 4
20 21
)

22 23 24 25
type PluginsGlobals struct {
	sync.RWMutex
	queryPlugins    *[]Plugin
	responsePlugins *[]Plugin
26
	loggingPlugins  *[]Plugin
27 28
}

Frank Denis's avatar
Frank Denis committed
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
type PluginsReturnCode int

const (
	PluginsReturnCodePass = iota
	PluginsReturnCodeForward
	PluginsReturnCodeDrop
	PluginsReturnCodeReject
	PluginsReturnCodeSynth
	PluginsReturnCodeParseError
	PluginsReturnCodeNXDomain
	PluginsReturnCodeResponseError
	PluginsReturnCodeServerError
)

var PluginsReturnCodeToString = map[PluginsReturnCode]string{
	PluginsReturnCodePass:          "PASS",
	PluginsReturnCodeForward:       "FORWARD",
	PluginsReturnCodeDrop:          "DROP",
	PluginsReturnCodeReject:        "REJECT",
	PluginsReturnCodeSynth:         "SYNTH",
	PluginsReturnCodeParseError:    "PARSE_ERROR",
	PluginsReturnCodeNXDomain:      "NXDOMAIN",
	PluginsReturnCodeResponseError: "RESPONSE_ERROR",
	PluginsReturnCodeServerError:   "SERVER_ERROR",
}

55 56 57 58 59
type PluginsState struct {
	sessionData            map[string]interface{}
	action                 PluginsAction
	originalMaxPayloadSize int
	maxPayloadSize         int
60 61
	clientProto            string
	clientAddr             *net.Addr
62
	synthResponse          *dns.Msg
63
	dnssec                 bool
64
	cacheSize              int
65 66
	cacheNegMinTTL         uint32
	cacheNegMaxTTL         uint32
67 68
	cacheMinTTL            uint32
	cacheMaxTTL            uint32
69
	questionMsg            *dns.Msg
Frank Denis's avatar
Frank Denis committed
70
	returnCode             PluginsReturnCode
71 72
}

73
func InitPluginsGlobals(pluginsGlobals *PluginsGlobals, proxy *Proxy) error {
74
	queryPlugins := &[]Plugin{}
Frank Denis's avatar
Frank Denis committed
75 76 77
	if len(proxy.whitelistNameFile) != 0 {
		*queryPlugins = append(*queryPlugins, Plugin(new(PluginWhitelistName)))
	}
78 79 80
	if len(proxy.blockNameFile) != 0 {
		*queryPlugins = append(*queryPlugins, Plugin(new(PluginBlockName)))
	}
81 82 83
	if proxy.pluginBlockIPv6 {
		*queryPlugins = append(*queryPlugins, Plugin(new(PluginBlockIPv6)))
	}
84 85 86
	if len(proxy.cloakFile) != 0 {
		*queryPlugins = append(*queryPlugins, Plugin(new(PluginCloak)))
	}
87
	*queryPlugins = append(*queryPlugins, Plugin(new(PluginGetSetPayloadSize)))
Frank Denis's avatar
Frank Denis committed
88 89 90
	if proxy.cache {
		*queryPlugins = append(*queryPlugins, Plugin(new(PluginCache)))
	}
Frank Denis's avatar
Frank Denis committed
91 92 93
	if len(proxy.forwardFile) != 0 {
		*queryPlugins = append(*queryPlugins, Plugin(new(PluginForward)))
	}
94

95
	responsePlugins := &[]Plugin{}
96 97 98
	if len(proxy.nxLogFile) != 0 {
		*responsePlugins = append(*responsePlugins, Plugin(new(PluginNxLog)))
	}
Frank Denis's avatar
Frank Denis committed
99 100 101 102 103 104
	if len(proxy.blockIPFile) != 0 {
		*responsePlugins = append(*responsePlugins, Plugin(new(PluginBlockIP)))
	}
	if proxy.cache {
		*responsePlugins = append(*responsePlugins, Plugin(new(PluginCacheResponse)))
	}
105 106 107 108 109 110

	loggingPlugins := &[]Plugin{}
	if len(proxy.queryLogFile) != 0 {
		*loggingPlugins = append(*loggingPlugins, Plugin(new(PluginQueryLog)))
	}

111 112 113 114 115 116 117 118 119 120
	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
		}
	}
121 122 123 124 125
	for _, plugin := range *loggingPlugins {
		if err := plugin.Init(proxy); err != nil {
			return err
		}
	}
126 127 128

	(*pluginsGlobals).queryPlugins = queryPlugins
	(*pluginsGlobals).responsePlugins = responsePlugins
129
	(*pluginsGlobals).loggingPlugins = loggingPlugins
130 131 132 133 134 135 136 137 138 139 140 141 142
	return nil
}

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) PluginsState {
143
	return PluginsState{
144 145 146 147 148
		action:         PluginsActionForward,
		maxPayloadSize: MaxDNSUDPPacketSize - ResponseOverhead,
		clientProto:    clientProto,
		clientAddr:     clientAddr,
		cacheSize:      proxy.cacheSize,
149 150
		cacheNegMinTTL: proxy.cacheNegMinTTL,
		cacheNegMaxTTL: proxy.cacheNegMaxTTL,
151 152
		cacheMinTTL:    proxy.cacheMinTTL,
		cacheMaxTTL:    proxy.cacheMaxTTL,
153
		questionMsg:    nil,
154
	}
155 156
}

157
func (pluginsState *PluginsState) ApplyQueryPlugins(pluginsGlobals *PluginsGlobals, packet []byte) ([]byte, error) {
158
	if len(*pluginsGlobals.queryPlugins) == 0 && len(*pluginsGlobals.loggingPlugins) == 0 {
159 160
		return packet, nil
	}
161
	pluginsState.action = PluginsActionForward
162 163 164 165
	msg := dns.Msg{}
	if err := msg.Unpack(packet); err != nil {
		return packet, err
	}
166 167 168
	if len(msg.Question) > 1 {
		return packet, errors.New("Unexpected number of questions")
	}
169
	pluginsState.questionMsg = &msg
170 171
	pluginsGlobals.RLock()
	for _, plugin := range *pluginsGlobals.queryPlugins {
172
		if ret := plugin.Eval(pluginsState, &msg); ret != nil {
173
			pluginsGlobals.RUnlock()
174 175 176
			pluginsState.action = PluginsActionDrop
			return packet, ret
		}
177 178 179 180 181 182 183
		if pluginsState.action == PluginsActionReject {
			synth, err := RefusedResponseFromMessage(&msg)
			if err != nil {
				return nil, err
			}
			pluginsState.synthResponse = synth
		}
184 185 186
		if pluginsState.action != PluginsActionForward {
			break
		}
187
	}
188
	pluginsGlobals.RUnlock()
189 190 191 192 193 194 195
	packet2, err := msg.PackBuffer(packet)
	if err != nil {
		return packet, err
	}
	return packet2, nil
}

196
func (pluginsState *PluginsState) ApplyResponsePlugins(pluginsGlobals *PluginsGlobals, packet []byte, ttl *uint32) ([]byte, error) {
197
	if len(*pluginsGlobals.responsePlugins) == 0 && len(*pluginsGlobals.loggingPlugins) == 0 {
198 199 200 201 202
		return packet, nil
	}
	pluginsState.action = PluginsActionForward
	msg := dns.Msg{}
	if err := msg.Unpack(packet); err != nil {
203 204 205
		if len(packet) >= MinDNSPacketSize && HasTCFlag(packet) {
			err = nil
		}
206 207
		return packet, err
	}
Frank Denis's avatar
Frank Denis committed
208 209 210 211 212 213 214 215 216 217
	switch Rcode(packet) {
	case dns.RcodeSuccess:
		pluginsState.returnCode = PluginsReturnCodePass
	case dns.RcodeNameError:
		pluginsState.returnCode = PluginsReturnCodeNXDomain
	case dns.RcodeServerFailure:
		pluginsState.returnCode = PluginsReturnCodeServerError
	default:
		pluginsState.returnCode = PluginsReturnCodeResponseError
	}
218 219
	pluginsGlobals.RLock()
	for _, plugin := range *pluginsGlobals.responsePlugins {
220
		if ret := plugin.Eval(pluginsState, &msg); ret != nil {
221
			pluginsGlobals.RUnlock()
222 223 224
			pluginsState.action = PluginsActionDrop
			return packet, ret
		}
225 226 227 228 229 230 231 232
		if pluginsState.action == PluginsActionReject {
			synth, err := RefusedResponseFromMessage(&msg)
			if err != nil {
				return nil, err
			}
			dlog.Infof("Blocking [%s]", synth.Question[0].Name)
			pluginsState.synthResponse = synth
		}
233 234 235 236
		if pluginsState.action != PluginsActionForward {
			break
		}
	}
237
	pluginsGlobals.RUnlock()
238 239 240
	if ttl != nil {
		setMaxTTL(&msg, *ttl)
	}
241 242 243 244 245 246
	packet2, err := msg.PackBuffer(packet)
	if err != nil {
		return packet, err
	}
	return packet2, nil
}
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265

func (pluginsState *PluginsState) ApplyLoggingPlugins(pluginsGlobals *PluginsGlobals) error {
	if len(*pluginsGlobals.loggingPlugins) == 0 {
		return nil
	}
	questionMsg := pluginsState.questionMsg
	if questionMsg == nil || len(questionMsg.Question) > 1 {
		return errors.New("Unexpected number of questions")
	}
	pluginsGlobals.RLock()
	for _, plugin := range *pluginsGlobals.loggingPlugins {
		if ret := plugin.Eval(pluginsState, questionMsg); ret != nil {
			pluginsGlobals.RUnlock()
			return ret
		}
	}
	pluginsGlobals.RUnlock()
	return nil
}