* plugin/rewrite: streamline the ResponseRule handling. The functionality of a response rule is now completely encapsulated behind a `ResponseRule` interface. This significantly simplifies the complete processing flow, it enables more flexible response handling and it is possible to eliminate lots of state flags, ifs and switches. Based on the new flexibility the pull request also enables to support a response name rewrite for all name rewrite types. To be compatible, an explicit `answer auto` option is added to support a best effort response rewrite (name and value). Additionally now all name rewrite rules support additional name and value reponse rewrite options. Using this feature it is also possible now to rewrite a complete sub domain hierarchy to a single domain name combined with a correct rewrite (#2389). Signed-off-by: Uwe Krueger <uwe.krueger@sap.com> * revert policy Signed-off-by: Uwe Krueger <uwe.krueger@sap.com> Co-authored-by: Miek Gieben <miek@miek.nl>
372 lines
9.4 KiB
Go
372 lines
9.4 KiB
Go
// Package rewrite is a plugin for rewriting requests internally to something different.
|
|
package rewrite
|
|
|
|
import (
|
|
"context"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"net"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/coredns/coredns/plugin/metadata"
|
|
"github.com/coredns/coredns/plugin/pkg/edns"
|
|
"github.com/coredns/coredns/request"
|
|
|
|
"github.com/miekg/dns"
|
|
)
|
|
|
|
// edns0LocalRule is a rewrite rule for EDNS0_LOCAL options.
|
|
type edns0LocalRule struct {
|
|
mode string
|
|
action string
|
|
code uint16
|
|
data []byte
|
|
}
|
|
|
|
// edns0VariableRule is a rewrite rule for EDNS0_LOCAL options with variable.
|
|
type edns0VariableRule struct {
|
|
mode string
|
|
action string
|
|
code uint16
|
|
variable string
|
|
}
|
|
|
|
// ends0NsidRule is a rewrite rule for EDNS0_NSID options.
|
|
type edns0NsidRule struct {
|
|
mode string
|
|
action string
|
|
}
|
|
|
|
// setupEdns0Opt will retrieve the EDNS0 OPT or create it if it does not exist.
|
|
func setupEdns0Opt(r *dns.Msg) *dns.OPT {
|
|
o := r.IsEdns0()
|
|
if o == nil {
|
|
r.SetEdns0(4096, false)
|
|
o = r.IsEdns0()
|
|
}
|
|
return o
|
|
}
|
|
|
|
// Rewrite will alter the request EDNS0 NSID option
|
|
func (rule *edns0NsidRule) Rewrite(ctx context.Context, state request.Request) (ResponseRules, Result) {
|
|
o := setupEdns0Opt(state.Req)
|
|
|
|
for _, s := range o.Option {
|
|
if e, ok := s.(*dns.EDNS0_NSID); ok {
|
|
if rule.action == Replace || rule.action == Set {
|
|
e.Nsid = "" // make sure it is empty for request
|
|
return nil, RewriteDone
|
|
}
|
|
}
|
|
}
|
|
|
|
// add option if not found
|
|
if rule.action == Append || rule.action == Set {
|
|
o.Option = append(o.Option, &dns.EDNS0_NSID{Code: dns.EDNS0NSID, Nsid: ""})
|
|
return nil, RewriteDone
|
|
}
|
|
|
|
return nil, RewriteIgnored
|
|
}
|
|
|
|
// Mode returns the processing mode.
|
|
func (rule *edns0NsidRule) Mode() string { return rule.mode }
|
|
|
|
// Rewrite will alter the request EDNS0 local options.
|
|
func (rule *edns0LocalRule) Rewrite(ctx context.Context, state request.Request) (ResponseRules, Result) {
|
|
o := setupEdns0Opt(state.Req)
|
|
|
|
for _, s := range o.Option {
|
|
if e, ok := s.(*dns.EDNS0_LOCAL); ok {
|
|
if rule.code == e.Code {
|
|
if rule.action == Replace || rule.action == Set {
|
|
e.Data = rule.data
|
|
return nil, RewriteDone
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// add option if not found
|
|
if rule.action == Append || rule.action == Set {
|
|
o.Option = append(o.Option, &dns.EDNS0_LOCAL{Code: rule.code, Data: rule.data})
|
|
return nil, RewriteDone
|
|
}
|
|
|
|
return nil, RewriteIgnored
|
|
}
|
|
|
|
// Mode returns the processing mode.
|
|
func (rule *edns0LocalRule) Mode() string { return rule.mode }
|
|
|
|
// newEdns0Rule creates an EDNS0 rule of the appropriate type based on the args
|
|
func newEdns0Rule(mode string, args ...string) (Rule, error) {
|
|
if len(args) < 2 {
|
|
return nil, fmt.Errorf("too few arguments for an EDNS0 rule")
|
|
}
|
|
|
|
ruleType := strings.ToLower(args[0])
|
|
action := strings.ToLower(args[1])
|
|
switch action {
|
|
case Append:
|
|
case Replace:
|
|
case Set:
|
|
default:
|
|
return nil, fmt.Errorf("invalid action: %q", action)
|
|
}
|
|
|
|
switch ruleType {
|
|
case "local":
|
|
if len(args) != 4 {
|
|
return nil, fmt.Errorf("EDNS0 local rules require exactly three args")
|
|
}
|
|
// Check for variable option.
|
|
if strings.HasPrefix(args[3], "{") && strings.HasSuffix(args[3], "}") {
|
|
return newEdns0VariableRule(mode, action, args[2], args[3])
|
|
}
|
|
return newEdns0LocalRule(mode, action, args[2], args[3])
|
|
case "nsid":
|
|
if len(args) != 2 {
|
|
return nil, fmt.Errorf("EDNS0 NSID rules do not accept args")
|
|
}
|
|
return &edns0NsidRule{mode: mode, action: action}, nil
|
|
case "subnet":
|
|
if len(args) != 4 {
|
|
return nil, fmt.Errorf("EDNS0 subnet rules require exactly three args")
|
|
}
|
|
return newEdns0SubnetRule(mode, action, args[2], args[3])
|
|
default:
|
|
return nil, fmt.Errorf("invalid rule type %q", ruleType)
|
|
}
|
|
}
|
|
|
|
func newEdns0LocalRule(mode, action, code, data string) (*edns0LocalRule, error) {
|
|
c, err := strconv.ParseUint(code, 0, 16)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
decoded := []byte(data)
|
|
if strings.HasPrefix(data, "0x") {
|
|
decoded, err = hex.DecodeString(data[2:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Add this code to the ones the server supports.
|
|
edns.SetSupportedOption(uint16(c))
|
|
|
|
return &edns0LocalRule{mode: mode, action: action, code: uint16(c), data: decoded}, nil
|
|
}
|
|
|
|
// newEdns0VariableRule creates an EDNS0 rule that handles variable substitution
|
|
func newEdns0VariableRule(mode, action, code, variable string) (*edns0VariableRule, error) {
|
|
c, err := strconv.ParseUint(code, 0, 16)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
//Validate
|
|
if !isValidVariable(variable) {
|
|
return nil, fmt.Errorf("unsupported variable name %q", variable)
|
|
}
|
|
|
|
// Add this code to the ones the server supports.
|
|
edns.SetSupportedOption(uint16(c))
|
|
|
|
return &edns0VariableRule{mode: mode, action: action, code: uint16(c), variable: variable}, nil
|
|
}
|
|
|
|
// ruleData returns the data specified by the variable.
|
|
func (rule *edns0VariableRule) ruleData(ctx context.Context, state request.Request) ([]byte, error) {
|
|
|
|
switch rule.variable {
|
|
case queryName:
|
|
return []byte(state.QName()), nil
|
|
|
|
case queryType:
|
|
return uint16ToWire(state.QType()), nil
|
|
|
|
case clientIP:
|
|
return ipToWire(state.Family(), state.IP())
|
|
|
|
case serverIP:
|
|
return ipToWire(state.Family(), state.LocalIP())
|
|
|
|
case clientPort:
|
|
return portToWire(state.Port())
|
|
|
|
case serverPort:
|
|
return portToWire(state.LocalPort())
|
|
|
|
case protocol:
|
|
return []byte(state.Proto()), nil
|
|
}
|
|
|
|
fetcher := metadata.ValueFunc(ctx, rule.variable[1:len(rule.variable)-1])
|
|
if fetcher != nil {
|
|
value := fetcher()
|
|
if len(value) > 0 {
|
|
return []byte(value), nil
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("unable to extract data for variable %s", rule.variable)
|
|
}
|
|
|
|
// Rewrite will alter the request EDNS0 local options with specified variables.
|
|
func (rule *edns0VariableRule) Rewrite(ctx context.Context, state request.Request) (ResponseRules, Result) {
|
|
data, err := rule.ruleData(ctx, state)
|
|
if err != nil || data == nil {
|
|
return nil, RewriteIgnored
|
|
}
|
|
|
|
o := setupEdns0Opt(state.Req)
|
|
for _, s := range o.Option {
|
|
if e, ok := s.(*dns.EDNS0_LOCAL); ok {
|
|
if rule.code == e.Code {
|
|
if rule.action == Replace || rule.action == Set {
|
|
e.Data = data
|
|
return nil, RewriteDone
|
|
}
|
|
return nil, RewriteIgnored
|
|
}
|
|
}
|
|
}
|
|
|
|
// add option if not found
|
|
if rule.action == Append || rule.action == Set {
|
|
o.Option = append(o.Option, &dns.EDNS0_LOCAL{Code: rule.code, Data: data})
|
|
return nil, RewriteDone
|
|
}
|
|
|
|
return nil, RewriteIgnored
|
|
}
|
|
|
|
// Mode returns the processing mode.
|
|
func (rule *edns0VariableRule) Mode() string { return rule.mode }
|
|
|
|
func isValidVariable(variable string) bool {
|
|
switch variable {
|
|
case
|
|
queryName,
|
|
queryType,
|
|
clientIP,
|
|
clientPort,
|
|
protocol,
|
|
serverIP,
|
|
serverPort:
|
|
return true
|
|
}
|
|
// we cannot validate the labels of metadata - but we can verify it has the syntax of a label
|
|
if strings.HasPrefix(variable, "{") && strings.HasSuffix(variable, "}") && metadata.IsLabel(variable[1:len(variable)-1]) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ends0SubnetRule is a rewrite rule for EDNS0 subnet options
|
|
type edns0SubnetRule struct {
|
|
mode string
|
|
v4BitMaskLen uint8
|
|
v6BitMaskLen uint8
|
|
action string
|
|
}
|
|
|
|
func newEdns0SubnetRule(mode, action, v4BitMaskLen, v6BitMaskLen string) (*edns0SubnetRule, error) {
|
|
v4Len, err := strconv.ParseUint(v4BitMaskLen, 0, 16)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// validate V4 length
|
|
if v4Len > net.IPv4len*8 {
|
|
return nil, fmt.Errorf("invalid IPv4 bit mask length %d", v4Len)
|
|
}
|
|
|
|
v6Len, err := strconv.ParseUint(v6BitMaskLen, 0, 16)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// validate V6 length
|
|
if v6Len > net.IPv6len*8 {
|
|
return nil, fmt.Errorf("invalid IPv6 bit mask length %d", v6Len)
|
|
}
|
|
|
|
return &edns0SubnetRule{mode: mode, action: action,
|
|
v4BitMaskLen: uint8(v4Len), v6BitMaskLen: uint8(v6Len)}, nil
|
|
}
|
|
|
|
// fillEcsData sets the subnet data into the ecs option
|
|
func (rule *edns0SubnetRule) fillEcsData(state request.Request, ecs *dns.EDNS0_SUBNET) error {
|
|
family := state.Family()
|
|
if (family != 1) && (family != 2) {
|
|
return fmt.Errorf("unable to fill data for EDNS0 subnet due to invalid IP family")
|
|
}
|
|
|
|
ecs.Family = uint16(family)
|
|
ecs.SourceScope = 0
|
|
|
|
ipAddr := state.IP()
|
|
switch family {
|
|
case 1:
|
|
ipv4Mask := net.CIDRMask(int(rule.v4BitMaskLen), 32)
|
|
ipv4Addr := net.ParseIP(ipAddr)
|
|
ecs.SourceNetmask = rule.v4BitMaskLen
|
|
ecs.Address = ipv4Addr.Mask(ipv4Mask).To4()
|
|
case 2:
|
|
ipv6Mask := net.CIDRMask(int(rule.v6BitMaskLen), 128)
|
|
ipv6Addr := net.ParseIP(ipAddr)
|
|
ecs.SourceNetmask = rule.v6BitMaskLen
|
|
ecs.Address = ipv6Addr.Mask(ipv6Mask).To16()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Rewrite will alter the request EDNS0 subnet option.
|
|
func (rule *edns0SubnetRule) Rewrite(ctx context.Context, state request.Request) (ResponseRules, Result) {
|
|
o := setupEdns0Opt(state.Req)
|
|
|
|
for _, s := range o.Option {
|
|
if e, ok := s.(*dns.EDNS0_SUBNET); ok {
|
|
if rule.action == Replace || rule.action == Set {
|
|
if rule.fillEcsData(state, e) == nil {
|
|
return nil, RewriteDone
|
|
}
|
|
}
|
|
return nil, RewriteIgnored
|
|
}
|
|
}
|
|
|
|
// add option if not found
|
|
if rule.action == Append || rule.action == Set {
|
|
opt := &dns.EDNS0_SUBNET{Code: dns.EDNS0SUBNET}
|
|
if rule.fillEcsData(state, opt) == nil {
|
|
o.Option = append(o.Option, opt)
|
|
return nil, RewriteDone
|
|
}
|
|
}
|
|
|
|
return nil, RewriteIgnored
|
|
}
|
|
|
|
// Mode returns the processing mode
|
|
func (rule *edns0SubnetRule) Mode() string { return rule.mode }
|
|
|
|
// These are all defined actions.
|
|
const (
|
|
Replace = "replace"
|
|
Set = "set"
|
|
Append = "append"
|
|
)
|
|
|
|
// Supported local EDNS0 variables
|
|
const (
|
|
queryName = "{qname}"
|
|
queryType = "{qtype}"
|
|
clientIP = "{client_ip}"
|
|
clientPort = "{client_port}"
|
|
protocol = "{protocol}"
|
|
serverIP = "{server_ip}"
|
|
serverPort = "{server_port}"
|
|
)
|