Client rules

edit

The same list can apply different rules to different clients: block more for the children's devices, or exempt a server. The engine does not know the clients: pass what you know about the one that sent the query, in a DnsClientInfo, to Evaluate.

DnsClientInfo propertyUsed by
Address$client with an IP address or a network
Name$client with a name
Tags$ctag

A rule scoped on clients does not match when the client information it needs is missing: with engine.Evaluate(domain), $client and $ctag rules never apply.

By address or network

$client takes IP addresses and networks in CIDR notation:

var ruleSet = new DnsFilterRuleSet();
ruleSet.AddFromList("||example.com^$client=192.168.1.0/24", DnsFilterListFormat.AdBlock);

var engine = new DnsFilterEngine(ruleSet);

var client = new DnsClientInfo { Address = IPAddress.Parse("192.168.1.50") };
Assert.Equal(DnsFilterAction.Block, engine.Evaluate("example.com", DnsFilterQueryType.A, client).Action);

var otherClient = new DnsClientInfo { Address = IPAddress.Parse("10.0.0.1") };
Assert.False(engine.Evaluate("example.com", DnsFilterQueryType.A, otherClient).IsMatched);

By name

$client also takes names. The engine compares them with DnsClientInfo.Name; it does not resolve them. Quote a name that contains spaces or separators, and separate the values with |:

var ruleSet = new DnsFilterRuleSet();
ruleSet.AddFromList("||video.example.com^$client='Kids tablet'|10.0.0.0/8", DnsFilterListFormat.AdBlock);
var engine = new DnsFilterEngine(ruleSet);

// The engine does not resolve names: pass the name you know for the client
var tablet = new DnsClientInfo { Name = "Kids tablet", Address = IPAddress.Parse("192.168.1.30") };
Assert.Equal(DnsFilterAction.Block, engine.Evaluate("video.example.com", DnsFilterQueryType.A, tablet).Action);

var vpnClient = new DnsClientInfo { Address = IPAddress.Parse("10.1.2.3") };
Assert.Equal(DnsFilterAction.Block, engine.Evaluate("video.example.com", DnsFilterQueryType.A, vpnClient).Action);

// Without client information, a rule scoped on clients does not match
Assert.False(engine.Evaluate("video.example.com").IsMatched);

Tags

$ctag matches the tags of the client. Tags are strings you choose, such as device_phone, os_android or user_child, and a client can have several:

var ruleSet = new DnsFilterRuleSet();
ruleSet.AddFromList("||example.com^$ctag=device_phone", DnsFilterListFormat.AdBlock);

var engine = new DnsFilterEngine(ruleSet);

var phoneClient = new DnsClientInfo { Tags = ["device_phone", "os_android"] };
Assert.Equal(DnsFilterAction.Block, engine.Evaluate("example.com", DnsFilterQueryType.A, phoneClient).Action);

var pcClient = new DnsClientInfo { Tags = ["device_pc"] };
Assert.False(engine.Evaluate("example.com", DnsFilterQueryType.A, pcClient).IsMatched);

Exclusions

~ before a value excludes it, in $client, $ctag and $dnstype. The rule then applies to every client, tag or type except those:

var ruleSet = new DnsFilterRuleSet();
ruleSet.AddFromList("""
    ||ipv6-only.example.com^$dnstype=~AAAA
    ||games.example.com^$ctag=~user_admin
    ||office.example.com^$client=~192.168.1.10
    """, DnsFilterListFormat.AdBlock);
var engine = new DnsFilterEngine(ruleSet);

// ~ excludes a query type, a tag or a client
Assert.Equal(DnsFilterAction.Block, engine.Evaluate("ipv6-only.example.com", DnsFilterQueryType.A).Action);
Assert.False(engine.Evaluate("ipv6-only.example.com", DnsFilterQueryType.AAAA).IsMatched);

var child = new DnsClientInfo { Tags = ["user_child"] };
var admin = new DnsClientInfo { Tags = ["user_admin"] };
Assert.Equal(DnsFilterAction.Block, engine.Evaluate("games.example.com", DnsFilterQueryType.A, child).Action);
Assert.False(engine.Evaluate("games.example.com", DnsFilterQueryType.A, admin).IsMatched);

var laptop = new DnsClientInfo { Address = IPAddress.Parse("192.168.1.20") };
var boss = new DnsClientInfo { Address = IPAddress.Parse("192.168.1.10") };
Assert.Equal(DnsFilterAction.Block, engine.Evaluate("office.example.com", DnsFilterQueryType.A, laptop).Action);
Assert.False(engine.Evaluate("office.example.com", DnsFilterQueryType.A, boss).IsMatched);