mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-08-23 01:27:13 +02:00
all: add MAC address base policy
While at it, also update the config doc to clarify the order of matching preference, and the matter of rules order within each policy.
This commit is contained in:
+15
-2
@@ -62,7 +62,7 @@ func (p *prog) serveDNS(listenerNum string) error {
|
|||||||
t := time.Now()
|
t := time.Now()
|
||||||
ctx := context.WithValue(context.Background(), ctrld.ReqIdCtxKey{}, reqId)
|
ctx := context.WithValue(context.Background(), ctrld.ReqIdCtxKey{}, reqId)
|
||||||
ctrld.Log(ctx, mainLog.Load().Debug(), "%s received query: %s %s", fmtSrcToDest, dns.TypeToString[q.Qtype], domain)
|
ctrld.Log(ctx, mainLog.Load().Debug(), "%s received query: %s %s", fmtSrcToDest, dns.TypeToString[q.Qtype], domain)
|
||||||
upstreams, matched := p.upstreamFor(ctx, listenerNum, listenerConfig, remoteAddr, domain)
|
upstreams, matched := p.upstreamFor(ctx, listenerNum, listenerConfig, remoteAddr, ci.Mac, domain)
|
||||||
var answer *dns.Msg
|
var answer *dns.Msg
|
||||||
if !matched && listenerConfig.Restricted {
|
if !matched && listenerConfig.Restricted {
|
||||||
answer = new(dns.Msg)
|
answer = new(dns.Msg)
|
||||||
@@ -146,7 +146,7 @@ func (p *prog) serveDNS(listenerNum string) error {
|
|||||||
// Though domain policy has higher priority than network policy, it is still
|
// Though domain policy has higher priority than network policy, it is still
|
||||||
// processed later, because policy logging want to know whether a network rule
|
// processed later, because policy logging want to know whether a network rule
|
||||||
// is disregarded in favor of the domain level rule.
|
// is disregarded in favor of the domain level rule.
|
||||||
func (p *prog) upstreamFor(ctx context.Context, defaultUpstreamNum string, lc *ctrld.ListenerConfig, addr net.Addr, domain string) ([]string, bool) {
|
func (p *prog) upstreamFor(ctx context.Context, defaultUpstreamNum string, lc *ctrld.ListenerConfig, addr net.Addr, srcMac, domain string) ([]string, bool) {
|
||||||
upstreams := []string{upstreamPrefix + defaultUpstreamNum}
|
upstreams := []string{upstreamPrefix + defaultUpstreamNum}
|
||||||
matchedPolicy := "no policy"
|
matchedPolicy := "no policy"
|
||||||
matchedNetwork := "no network"
|
matchedNetwork := "no network"
|
||||||
@@ -202,6 +202,19 @@ networkRules:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macRules:
|
||||||
|
for _, rule := range lc.Policy.Macs {
|
||||||
|
for source, targets := range rule {
|
||||||
|
if source != "" && strings.EqualFold(source, srcMac) {
|
||||||
|
matchedPolicy = lc.Policy.Name
|
||||||
|
matchedNetwork = source
|
||||||
|
networkTargets = targets
|
||||||
|
matched = true
|
||||||
|
break macRules
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, rule := range lc.Policy.Rules {
|
for _, rule := range lc.Policy.Rules {
|
||||||
// There's only one entry per rule, config validation ensures this.
|
// There's only one entry per rule, config validation ensures this.
|
||||||
for source, targets := range rule {
|
for source, targets := range rule {
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ func Test_prog_upstreamFor(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
ip string
|
ip string
|
||||||
|
mac string
|
||||||
defaultUpstreamNum string
|
defaultUpstreamNum string
|
||||||
lc *ctrld.ListenerConfig
|
lc *ctrld.ListenerConfig
|
||||||
domain string
|
domain string
|
||||||
@@ -88,11 +89,14 @@ func Test_prog_upstreamFor(t *testing.T) {
|
|||||||
matched bool
|
matched bool
|
||||||
testLogMsg string
|
testLogMsg string
|
||||||
}{
|
}{
|
||||||
{"Policy map matches", "192.168.0.1:0", "0", prog.cfg.Listener["0"], "abc.xyz", []string{"upstream.1", "upstream.0"}, true, ""},
|
{"Policy map matches", "192.168.0.1:0", "", "0", prog.cfg.Listener["0"], "abc.xyz", []string{"upstream.1", "upstream.0"}, true, ""},
|
||||||
{"Policy split matches", "192.168.0.1:0", "0", prog.cfg.Listener["0"], "abc.ru", []string{"upstream.1"}, true, ""},
|
{"Policy split matches", "192.168.0.1:0", "", "0", prog.cfg.Listener["0"], "abc.ru", []string{"upstream.1"}, true, ""},
|
||||||
{"Policy map for other network matches", "192.168.1.2:0", "0", prog.cfg.Listener["0"], "abc.xyz", []string{"upstream.0"}, true, ""},
|
{"Policy map for other network matches", "192.168.1.2:0", "", "0", prog.cfg.Listener["0"], "abc.xyz", []string{"upstream.0"}, true, ""},
|
||||||
{"No policy map for listener", "192.168.1.2:0", "1", prog.cfg.Listener["1"], "abc.ru", []string{"upstream.1"}, false, ""},
|
{"No policy map for listener", "192.168.1.2:0", "", "1", prog.cfg.Listener["1"], "abc.ru", []string{"upstream.1"}, false, ""},
|
||||||
{"unenforced loging", "192.168.1.2:0", "0", prog.cfg.Listener["0"], "abc.ru", []string{"upstream.1"}, true, "My Policy, network.1 (unenforced), *.ru -> [upstream.1]"},
|
{"unenforced loging", "192.168.1.2:0", "", "0", prog.cfg.Listener["0"], "abc.ru", []string{"upstream.1"}, true, "My Policy, network.1 (unenforced), *.ru -> [upstream.1]"},
|
||||||
|
{"Policy Macs matches upper", "192.168.0.1:0", "14:45:A0:67:83:0A", "0", prog.cfg.Listener["0"], "abc.xyz", []string{"upstream.2"}, true, "14:45:a0:67:83:0a"},
|
||||||
|
{"Policy Macs matches lower", "192.168.0.1:0", "14:54:4a:8e:08:2d", "0", prog.cfg.Listener["0"], "abc.xyz", []string{"upstream.2"}, true, "14:54:4a:8e:08:2d"},
|
||||||
|
{"Policy Macs matches case-insensitive", "192.168.0.1:0", "14:54:4A:8E:08:2D", "0", prog.cfg.Listener["0"], "abc.xyz", []string{"upstream.2"}, true, "14:54:4a:8e:08:2d"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
@@ -111,7 +115,7 @@ func Test_prog_upstreamFor(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, addr)
|
require.NotNil(t, addr)
|
||||||
ctx := context.WithValue(context.Background(), ctrld.ReqIdCtxKey{}, requestID())
|
ctx := context.WithValue(context.Background(), ctrld.ReqIdCtxKey{}, requestID())
|
||||||
upstreams, matched := prog.upstreamFor(ctx, tc.defaultUpstreamNum, tc.lc, addr, tc.domain)
|
upstreams, matched := prog.upstreamFor(ctx, tc.defaultUpstreamNum, tc.lc, addr, tc.mac, tc.domain)
|
||||||
assert.Equal(t, tc.matched, matched)
|
assert.Equal(t, tc.matched, matched)
|
||||||
assert.Equal(t, tc.upstreams, upstreams)
|
assert.Equal(t, tc.upstreams, upstreams)
|
||||||
if tc.testLogMsg != "" {
|
if tc.testLogMsg != "" {
|
||||||
|
|||||||
@@ -253,6 +253,7 @@ type ListenerPolicyConfig struct {
|
|||||||
Name string `mapstructure:"name" toml:"name,omitempty"`
|
Name string `mapstructure:"name" toml:"name,omitempty"`
|
||||||
Networks []Rule `mapstructure:"networks" toml:"networks,omitempty,inline,multiline" validate:"dive,len=1"`
|
Networks []Rule `mapstructure:"networks" toml:"networks,omitempty,inline,multiline" validate:"dive,len=1"`
|
||||||
Rules []Rule `mapstructure:"rules" toml:"rules,omitempty,inline,multiline" validate:"dive,len=1"`
|
Rules []Rule `mapstructure:"rules" toml:"rules,omitempty,inline,multiline" validate:"dive,len=1"`
|
||||||
|
Macs []Rule `mapstructure:"macs" toml:"macs,omitempty,inline,multiline" validate:"dive,len=1"`
|
||||||
FailoverRcodes []string `mapstructure:"failover_rcodes" toml:"failover_rcodes,omitempty" validate:"dive,dnsrcode"`
|
FailoverRcodes []string `mapstructure:"failover_rcodes" toml:"failover_rcodes,omitempty" validate:"dive,dnsrcode"`
|
||||||
FailoverRcodeNumbers []int `mapstructure:"-" toml:"-"`
|
FailoverRcodeNumbers []int `mapstructure:"-" toml:"-"`
|
||||||
}
|
}
|
||||||
|
|||||||
+35
-2
@@ -386,7 +386,15 @@ If set to `true` makes the listener `REFUSE` DNS queries from all source IP addr
|
|||||||
Allows `ctrld` to set policy rules to determine which upstreams the requests will be forwarded to.
|
Allows `ctrld` to set policy rules to determine which upstreams the requests will be forwarded to.
|
||||||
If no `policy` is defined or the requests do not match any policy rules, it will be forwarded to corresponding upstream of the listener. For example, the request to `listener.0` will be forwarded to `upstream.0`.
|
If no `policy` is defined or the requests do not match any policy rules, it will be forwarded to corresponding upstream of the listener. For example, the request to `listener.0` will be forwarded to `upstream.0`.
|
||||||
|
|
||||||
The policy `rule` syntax is a simple `toml` inline table with exactly one key/value pair per rule. `key` is either the `network` or a domain. Value is the list of the upstreams. For example:
|
The policy `rule` syntax is a simple `toml` inline table with exactly one key/value pair per rule. `key` is either:
|
||||||
|
|
||||||
|
- Network.
|
||||||
|
- Domain.
|
||||||
|
- Mac Address.
|
||||||
|
|
||||||
|
Value is the list of the upstreams.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[listener.0.policy]
|
[listener.0.policy]
|
||||||
@@ -400,12 +408,18 @@ rules = [
|
|||||||
{"*.local" = ["upstream.1"]},
|
{"*.local" = ["upstream.1"]},
|
||||||
{"test.com" = ["upstream.2", "upstream.1"]},
|
{"test.com" = ["upstream.2", "upstream.1"]},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
macs = [
|
||||||
|
{"14:54:4a:8e:08:2d" = ["upstream.3"]},
|
||||||
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
Above policy will:
|
Above policy will:
|
||||||
- Forward requests on `listener.0` from `network.0` to `upstream.1`.
|
|
||||||
- Forward requests on `listener.0` for `.local` suffixed domains to `upstream.1`.
|
- Forward requests on `listener.0` for `.local` suffixed domains to `upstream.1`.
|
||||||
- Forward requests on `listener.0` for `test.com` to `upstream.2`. If timeout is reached, retry on `upstream.1`.
|
- Forward requests on `listener.0` for `test.com` to `upstream.2`. If timeout is reached, retry on `upstream.1`.
|
||||||
|
- Forward requests on `listener.0` from client with Mac `14:54:4a:8e:08:2d` to `upstream.3`.
|
||||||
|
- Forward requests on `listener.0` from `network.0` to `upstream.1`.
|
||||||
- All other requests on `listener.0` that do not match above conditions will be forwarded to `upstream.0`.
|
- All other requests on `listener.0` that do not match above conditions will be forwarded to `upstream.0`.
|
||||||
|
|
||||||
An empty upstream would not route the request to any defined upstreams, and use the OS default resolver.
|
An empty upstream would not route the request to any defined upstreams, and use the OS default resolver.
|
||||||
@@ -419,6 +433,18 @@ rules = [
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Note that the order of matching preference:
|
||||||
|
|
||||||
|
```
|
||||||
|
rules => macs => networks
|
||||||
|
```
|
||||||
|
|
||||||
|
And within each policy, the rules are processed from top to bottom.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
#### name
|
#### name
|
||||||
`name` is the name for the policy.
|
`name` is the name for the policy.
|
||||||
|
|
||||||
@@ -440,6 +466,13 @@ rules = [
|
|||||||
- Required: no
|
- Required: no
|
||||||
- Default: []
|
- Default: []
|
||||||
|
|
||||||
|
### macs:
|
||||||
|
`macs` is the list of mac rules within the policy. Mac address value is case-insensitive.
|
||||||
|
|
||||||
|
- Type: array of macs
|
||||||
|
- Required: no
|
||||||
|
- Default: []
|
||||||
|
|
||||||
### failover_rcodes
|
### failover_rcodes
|
||||||
For non success response, `failover_rcodes` allows the request to be forwarded to next upstream, if the response `RCODE` matches any value defined in `failover_rcodes`.
|
For non success response, `failover_rcodes` allows the request to be forwarded to next upstream, if the response `RCODE` matches any value defined in `failover_rcodes`.
|
||||||
|
|
||||||
|
|||||||
@@ -82,4 +82,8 @@ rules = [
|
|||||||
{"*.ru" = ["upstream.1"]},
|
{"*.ru" = ["upstream.1"]},
|
||||||
{"*.local.host" = ["upstream.2", "upstream.0"]},
|
{"*.local.host" = ["upstream.2", "upstream.0"]},
|
||||||
]
|
]
|
||||||
|
macs = [
|
||||||
|
{"14:45:A0:67:83:0A" = ["upstream.2"]},
|
||||||
|
{"14:54:4a:8e:08:2d" = ["upstream.2"]},
|
||||||
|
]
|
||||||
`
|
`
|
||||||
|
|||||||
Reference in New Issue
Block a user