COREDNS-VIEW(7)
๐
COREDNS-VIEW(7) โ the view plugin, explained by your favorite Unix bestie
okay so bestie, buckle up, because this is the plugin manual page for view โ and no, it's not the thing you do on TikTok, it's the thing that decides which server block gets your DNS request. iconic behavior, honestly.
โจ NAME โจ
view โ defines the conditions that must be met for a DNS request to be routed to the server block. that's it, that's the whole vibe check. ๐
๐ธ DESCRIPTION ๐ธ
so like, view defines an expression that has to evaluate to true for a DNS request to get routed to the server block. literally, if the expression isn't feeling it, the request goes elsewhere. this unlocks some highkey advanced server block routing situations โ the big one being split DNS. yes queen, different answers for different people. ๐
๐ฆ SYNTAX ๐ฆ
here's the shape of it, verbatim, don't @ me:
view NAME {
expr EXPRESSION
}
view NAMEโ the name of the view, used by metrics and exported as metadata for requests that match the view's expression. give it a cute name, it's basically the view's government name. ๐expr EXPRESSIONโ CoreDNS will only route incoming queries to the enclosing server block ifEXPRESSIONevaluates totrue. peep the Expressions section for all the available variables and functions. and plot twist: if you define multiple instances ofview, then all of theEXPRESSIONs must evaluate totruefor CoreDNS to route incoming queries to the enclosing server block. it's giving "group project where everyone has to show up." ๐ญ
for expression syntax and examples, keep scrolling to the Expressions and Examples sections. ๐ซถ
๐ SERVER BLOCK ORDERING ๐
this part matters, so pay attention, bestie:
Server blocks sharing the same zone and port are evaluated top to bottom. The first block whose view expression matches (or that has no view at all) handles the query.
โ ๏ธ An unfiltered catch-all block declared before a filtered block will shadow it, because the catch-all matches every query. like, it will simply eat its lunch. no notes. ๐
to get the expected split-DNS behavior, declare all filtered (view) blocks first and the unfiltered catch-all block last. order is the whole entire point here, periodt.
๐ EXAMPLES ๐
๐ CIDR-based split DNS routing
Implement CIDR based split DNS routing. This will return a different answer for test. depending on the client's IP address. It returns:
... test. 3600 IN A 1.1.1.1, for queries with a source address in127.0.0.0/24test. 3600 IN A 2.2.2.2, for queries with a source address in192.168.0.0/16test. 3600 IN AAAA 2001:0DB8::1, for queries with a source address in2001:0DB8::/32test. 3600 IN A 3.3.3.3, for all others
. {
view example1 {
expr incidr(client_ip(), '127.0.0.0/24')
}
hosts {
1.1.1.1 test
}
}
. {
view example2 {
expr incidr(client_ip(), '192.168.0.0/16')
}
hosts {
2.2.2.2 test
}
}
. {
view v6_example1 {
expr incidr(client_ip(), '2001:0DB8::/32')
}
hosts {
2001:0DB8::1 test
}
}
}
. {
hosts {
3.3.3.3 test
}
}
(yeah, there's a stray } in there โ that's literally how it's printed in the original, so it stays. not my business, not my problem. ๐)
โญ send all A and AAAA somewhere else
Send all A and AAAA requests to 10.0.0.6, and all other requests to 10.0.0.1.
. {
view example {
expr type() in ['A', 'AAAA']
}
forward . 10.0.0.6
}
. {
forward . 10.0.0.1
}
๐ง regex name matching, escaped like a boss
Send all requests for abc.*.example.com (where * can be any number of labels), to 10.0.0.2, and all other requests to 10.0.0.1.
๐ Note that the regex pattern is enclosed in single quotes, and backslashes are escaped with backslashes. yes, double backslashes. it's giving "escape the escape." ๐ฅ
. {
view example {
expr name() matches '^abc\\..*\\.example\\.com\\.$'
}
forward . 10.0.0.2
}
. {
forward . 10.0.0.1
}
๐ EXPRESSIONS ๐
To evaluate expressions, view uses the expr-lang/expr package ( https://github.com/expr-lang/expr ). slay, we love a dependency with a good README.
For example, an expression could look like:
(type() == 'A' && name() == 'example.com.') || client_ip() == '1.2.3.4'
All expressions should be written to evaluate to a boolean value. true or false, no in-between, no "it depends," no situationships. ๐
See https://github.com/expr-lang/expr/blob/master/docs/language-definition.md as a detailed reference for valid syntax. bookmarked, obviously.
๐ฎ AVAILABLE EXPRESSION FUNCTIONS ๐ฎ
In the context of the view plugin, expressions can reference DNS query information by using the utility functions defined below. so many little helpers, we're obsessed. ๐ซถ
๐ DNS QUERY FUNCTIONS ๐
bufsize() int: the EDNS0 buffer size advertised in the queryclass() string: class of the request (IN,CH, ...)client_ip() string: client's IP address; for IPv6 addresses these are enclosed in brackets:[::1]do() bool: the EDNS0 DO (DNSSEC OK) bit set in the queryid() int: query IDname() string: name of the request (the domain name requested ending with a dot):example.com.opcode() int: query OPCODEport() string: client's portproto() string: protocol used (tcporudp)server_ip() string: server's IP address; for IPv6 addresses these are enclosed in brackets:[::1]server_port() string: server's portsize() int: request size in bytestype() string: type of the request (A,AAAA,TXT, ...)
๐ง UTILITY FUNCTIONS ๐ง
incidr(ip string, cidr string) bool: returnstrueifipis withincidrmetadata(label string)โ returns the value for the metadata matchinglabel
๐ METADATA ๐
The view plugin will publish the following metadata, if the metadata plugin is also enabled. teamwork makes the dream work, etc. โจ
view/name: the name of the view handling the current request
okay that's the whole page, bestie. you're officially a view plugin girlie now. go forth and split that DNS. ๐๐
CoreDNS โ October 2026 edition. mwah, drink some water, and remember: filtered blocks first, catch-all last. ๐ซถ๐
๐ฆ where this page comes from
- opensuse-leap 16.0: coredns-extras 1.15.0-bp160.1.1 ยท Apache-2.0 AND MPL-2.0
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms