Rule syntax

edit

The AdBlock format is the syntax of DNS filter lists for ad blockers: a pattern, optionally preceded by @@ for an exception, and optionally followed by $ and comma-separated modifiers.

Patterns

var ruleSet = new DnsFilterRuleSet();
ruleSet.AddFromList("""
    ! A comment
    ||ads.example.com^
    |exact.example.com^
    ||ads-*.example.net^
    /^track[0-9]+\.example\.org$/
    """, DnsFilterListFormat.AdBlock);
var engine = new DnsFilterEngine(ruleSet);

// ||: the domain and its subdomains
Assert.Equal(DnsFilterAction.Block, engine.Evaluate("ads.example.com").Action);
Assert.Equal(DnsFilterAction.Block, engine.Evaluate("cdn.ads.example.com").Action);
Assert.False(engine.Evaluate("badads.example.com").IsMatched);

// A single |: only the domain itself
Assert.Equal(DnsFilterAction.Block, engine.Evaluate("exact.example.com").Action);
Assert.False(engine.Evaluate("www.exact.example.com").IsMatched);

// * matches any characters
Assert.Equal(DnsFilterAction.Block, engine.Evaluate("ads-eu.example.net").Action);

// /regex/ is matched against the whole name
Assert.Equal(DnsFilterAction.Block, engine.Evaluate("track42.example.org").Action);
Assert.False(engine.Evaluate("tracker.example.org").IsMatched);
PatternMatches
||example.com^example.com and all its subdomains, not badexample.com
|example.com^, example.comexample.com only
||ads-*.example.com^* stands for any characters: ads-eu.example.com, x.ads-1.example.com
/^ads[0-9]+\.example\.com$/Names that match the regular expression
@@ + any patternAn exception: the matching names are allowed. See Priorities.
  • The final ^ and | are optional: they mark the end of the name.
  • Rules anchored on a domain are looked up in a hash table, whatever the size of the list. Wildcard rules that end with a domain (||ads-*.example.com^) use the same index; only regular expressions and wildcards without a fixed suffix are tried on every query, after a quick check of the literal text they need.
  • Regular expressions are .NET regular expressions, case-insensitive, run with the non-backtracking engine when they allow it, so a hostile pattern cannot stall the engine. They match anywhere in the name: anchor them with ^ and $. Prefer domain rules when you can: they are faster and cannot match more than you mean.

Priorities

When several rules match, the engine picks one, by priority, from highest to lowest:

  1. An $important exception (@@...$important)
  2. An $important block
  3. An exception (@@...)
  4. A normal block
var ruleSet = new DnsFilterRuleSet();
ruleSet.AddFromList("""
    ||example.com^
    @@||www.example.com^
    ||cdn.example.com^$important
    @@||safe.cdn.example.com^$important
    """, DnsFilterListFormat.AdBlock);
var engine = new DnsFilterEngine(ruleSet);

// An exception (@@) beats a normal block
Assert.Equal(DnsFilterAction.Allow, engine.Evaluate("www.example.com").Action);

// $important beats an exception...
Assert.Equal(DnsFilterAction.Block, engine.Evaluate("cdn.example.com").Action);

// ...unless the exception is $important too
var result = engine.Evaluate("safe.cdn.example.com");
Assert.Equal(DnsFilterAction.Allow, result.Action);
Assert.Equal("@@||safe.cdn.example.com^$important", result.MatchingRule?.OriginalText);

Within a level, a rule with $dnsrewrite wins over a plain block, then the more specific rule wins. MatchingRule is the rule that decided, which helps to understand why a domain is blocked.

Disable a rule of another list

$badfilter disables the rule that has the same text without the modifier. Use it in your own list to cancel a rule of a list you do not control, without editing that list:

var ruleSet = new DnsFilterRuleSet();

// A list you do not control
ruleSet.AddFromList("||cdn.example.com^", DnsFilterListFormat.AdBlock);

// Your own list: $badfilter disables the rule that has the same text
ruleSet.AddFromList("||cdn.example.com^$badfilter", DnsFilterListFormat.AdBlock);

var engine = new DnsFilterEngine(ruleSet);
Assert.False(engine.Evaluate("cdn.example.com").IsMatched);

The texts must match, modifiers included: ||cdn.example.com^$badfilter disables ||cdn.example.com^, not ||cdn.example.com^$important. An exception is the other way to unblock a domain: it keeps the rule, and can be overridden by $important.

Block a domain except some subdomains

$denyallow lists domains, separated by |, that the rule does not apply to:

var ruleSet = new DnsFilterRuleSet();
ruleSet.AddFromList("||example.com^$denyallow=www.example.com|api.example.com", DnsFilterListFormat.AdBlock);
var engine = new DnsFilterEngine(ruleSet);

Assert.Equal(DnsFilterAction.Block, engine.Evaluate("ads.example.com").Action);

// The domains of $denyallow, and their subdomains, are not blocked by this rule
Assert.False(engine.Evaluate("www.example.com").IsMatched);
Assert.False(engine.Evaluate("v2.api.example.com").IsMatched);

Filter by query type

$dnstype limits a rule to some query types, separated by |, or excludes types with ~:

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

var engine = new DnsFilterEngine(ruleSet);

Assert.Equal(DnsFilterAction.Block, engine.Evaluate("example.com", DnsFilterQueryType.AAAA).Action);
Assert.False(engine.Evaluate("example.com", DnsFilterQueryType.A).IsMatched);
  • $dnstype=A|AAAA applies to A and AAAA queries only; $dnstype=~HTTPS to every type except HTTPS.
  • Types are names (AAAA) or numbers (65, TYPE65), so types without a name in DnsFilterQueryType work too: pass the query type as a number cast to DnsFilterQueryType to Evaluate.

Supported modifiers

ModifierEffect
$importantRaises the priority of the rule. See Priorities.
$badfilterDisables the rule with the same text.
$denyallow=a.com|b.comExcludes domains from the rule.
$dnstype=A|~AAAALimits the rule to query types.
$client=...Limits the rule to clients. See Client rules.
$ctag=...Limits the rule to tagged clients. See Client rules.
$dnsrewrite=...Answers instead of blocking. See Rewrites.

A rule with any other modifier, such as the web modifiers $third-party or $script, is skipped and reported. See See the lines that were skipped.