Skip to content

The honk dialect of dae configuration ​

honk reads a dialect of dae configuration syntax. Where the same text has different meanings in honk and dae, honk's documented behavior is the contract. The dae column covers only its grammar (dae_config.g4), not value conversion.

Lexical rules ​

Inputdae grammarhonkCases
# inside a bare value, log_file: /tmp/a#b, use_host: /tmp/a#b, or group(hk#suffix)# is a safe character inside a bare literalGlued # is data, with legacy-glued-hash at the formerly truncated boundary. Scalar paths retain /tmp/a#b; the subgroup name is hk#suffix. Insert whitespace before an intended comment.dialect-lexical-01-scalar-hash, dialect-lexical-01-host-hash, dialect-lexical-01-group-hash
# glued to an outbound name in a routing rule, domain(x) -> proxy#cone bare literal, proxy#cLiteral target proxy#c, with legacy-glued-hash; normal target validation still applies. Write -> proxy # comment for a comment.dialect-lexical-02-outbound-hash
/* … */block comment, skippedNo block-comment syntax. A statement beginning with /* receives unsupported-comment and is ignored; following physical lines still participate in configuration. Use # on each intended comment line.dialect-lexical-03-block-comment
[key: value] after a declaration, filter: name(x) [add_latency: -500ms]accepted as an annotationNot recognised; the filter is reported as unparseable and ignored. honk has no per-node latency bias.dialect-lexical-04-annotation
An apostrophe inside a bare scalar, log_file: /tmp/don't, or an opened traffic argument quotelexer error for the bare apostrophe/tmp/don't is literal. Ordinary quotes open only at token head or immediately after unquoted (, , or : (so key:'value' is the same entry as key: 'value'); root include additionally recognizes adjacent quoted paths. A traffic quote that does not close on its physical line fails with located unterminated-quote; close the quote rather than relying on brace recovery.dialect-lexical-05-apostrophe, dialect-lexical-05-unterminated, dialect-lexical-05-quote-after-colon
Braces inside quotes, secret: 'a}b', or commentsdataQuoted braces and token-head-comment braces are data. Compact {} inside matcher arguments or a User-Agent suffix remains data; separate unquoted { and } remain structural. Entry-tail compatibility after a glued # does not hide separated structural braces. Changed legacy comment-brace interpretation emits legacy-comment-brace; put the real closing brace outside the comment.dialect-lexical-06-quoted-brace, dialect-lexical-06-comment-brace, dialect-lexical-06-compact-argument, dialect-lexical-06-ua-brace
Block opener without a space, global{ … }whitespace is skipped, so this is a blockblock-delimiter-spacing error, whether inline or line-final. Separate block braces from the header with whitespace. Compact empty blocks such as global {} remain accepted; adjacent declarations such as group { a {} b {} } remain separate blocks.dialect-lexical-07-glued-opener, dialect-lexical-07-compact-empty, dialect-lexical-07-adjacent-groups
An extra }, or an empty filean extra } is rejected; empty or comment-only input is acceptedExtra closers are ignored with unmatched-close; an entry or standalone dae document without a block is rejected. Empty or comment-only included .dae fragments contribute no sections. Extra-close compatibility will be reviewed after the first PR2 release.dialect-lexical-08-extra-closer, dialect-lexical-08-empty, dialect-lexical-08-comment-only
Group names, 123 {, 香港 {, Hong Kong {a block name is one IDThe raw header before separated { is the dynamic group name. Unknown top-level names are diagnosed and skipped, not interpreted as plugins.dialect-lexical-09-numeric, dialect-lexical-09-unicode, dialect-lexical-09-spaces
Two declarations on one line, global { log_level: debug log_file: x }whitespace-separated declarationsOne declaration: log_level is debug log_file: x. One key: value per line; one-line blocks hold one statement.dialect-lexical-10-same-line
The opening brace on the next line, global newline {whitespace, newlines included, is skippedOrdinary split headers fail. Only top-level include retains a split opener, including intervening blank/comment lines, with located legacy-include-opener. Put { on the include header line; compatibility will be reviewed after the first PR2 release.dialect-lexical-11-split-root, dialect-lexical-11-split-include

Scalars and lists ​

Inputdae grammarhonkCases
A bare value with colons, bind: 127.0.0.1:53not one literal; must be quotedAccepted bare: the value is everything after the first :.dialect-scalars-01-bare-colons
A call-shaped value, client_subnet: auto(9.9.9.9)function expressionThe text auto(9.9.9.9) is the value; the setting parses it itself.dialect-scalars-02-call-value
Quoted list items, lan_interface: 'eth0', 'eth1'two literalsSplit the list first, then remove one enclosing quote pair per item: eth0, eth1. Changed legacy quoting emits legacy-list-quoting; native_api lists, which had no legacy reading, never do. Whole-quoted comma aggregates remain compatible for check-target lists, with legacy-quoted-list; prefer individually quoted or bare items.dialect-scalars-03-list, dialect-scalars-03-aggregate
Booleans t, y, f, nbare literalsLenient settings (case-insensitive): true, yes, 1, on are true; false, f, no, n, 0, off, t, y are false. The four single-letter shorthands emit legacy-bool-shorthand; unknown spellings are false with a diagnostic. Prefer true or false. global.nfqueue_enable and legacy experimental.udp_nfqueue.enabled remain strict and reject shorthands.dialect-scalars-04-t, dialect-scalars-04-y, dialect-scalars-04-f, dialect-scalars-04-n, dialect-scalars-04-strict
Marks, so_mark_from_dae: 0x10 and so_mark_from_dae: 10bare literalsBoth are read as hexadecimal: 16 and 16. An unparseable mark falls back to 0 with a diagnostic.dialect-scalars-05-0x10, dialect-scalars-05-10, dialect-scalars-05-zz
Fractional seconds in a millisecond setting, check_tolerance: 1.5sbare literal1500 ms. Unparseable millisecond durations (check_tolerance, sniffing_timeout) fall back to the setting's default with a diagnostic; unparseable second durations (check_interval, subscription interval) fall back to 0 with a diagnostic.dialect-scalars-06-1-5s, dialect-scalars-06-never, dialect-scalars-06-seconds
fixed_domain_ttl entries x: 60#note, y: '60', z: 60 ignored60#note and '60' are literals, 60 ignored is two tokensExactly one decimal scalar is required. y stores 60 with legacy-ttl-quoting; x is omitted with invalid-ttl, and z with trailing-value. Use 60 or quoted 60; put explanations after #.dialect-scalars-07-hash, dialect-scalars-07-quoted, dialect-scalars-07-trailing
An unknown policy, policy: mystery(...)any function expressionFalls back to selector with a diagnostic; the argument text is never echoed.dialect-scalars-08-unknown-policy

Node and subscription entries ​

Inputdae grammarhonkCases
A tag on a share link or subscriptionID ':' literal; a bare literal cannot contain :, so links are quotedFor a bare head, the text before the first : is the tag unless that colon starts ://; for a quoted head, only a : right after the closing quote declares a tag, so a colon in an unquoted User-Agent or comment suffix is suffix data. Links may be bare; tags and links may be quoted. In subscription only, a wholly quoted value with a tag inside ('paid:https://…') is split after unquoting, and an entry without a tag is named after its URL host; in node a quoted value is the whole link ('paid:socks5://…' is an unknown scheme).dialect-entries-01-tagged, dialect-entries-01-bare, dialect-entries-01-quoted-tag, dialect-entries-01-embedded-subtag, dialect-entries-01-embedded-nodetag, dialect-entries-01-tagless
A trailing comment on an entry linecomment when # starts a tokenA # after a space or tab ends the line. Inside a bare value, glued # is data (…#hk1, ?filter='hk'#token, /tmp/a#b). After a closing node/link quote or one balanced subscription (UA) suffix, a glued # truncates the entry tail with legacy-glued-hash, but is not a lexical comment: a separated brace after it still changes block structure. Put whitespace before an intended comment. Other trailing text skips the entry with trailing-entry-text or legacy-ua-boundary.dialect-entries-02-comment, dialect-entries-02-glued, dialect-entries-02-trailing
'url'(User-Agent) after a subscription linknot in the grammarhonk extension: the parenthesised text is the User-Agent; a # after a space inside unquoted parentheses ends the line, so quote such a User-Agent.dialect-entries-03-user-agent, dialect-entries-03-quoted-agent, dialect-entries-03-unquoted-hash
A subscription block, paid: { followed by url: …, ua: …, interval: … on their own lines and }ID ':' '{' is not a declarationhonk extension: the block form of a subscription; its settings are one per line like any block.dialect-entries-04-block
A block after a quoted subscription link, paid: 'url' { followed by ua: …, interval: …, cache: … or route: … on their own lines and }ID ':' literal ends the declarationhonk extension: per-entry options; omitted ones come from assets.subscription and assets.route. route replaces download_detour of the older block form, and an entry that sets both fails with conflicting-subscription-route.
A bare tag with a space before the colon in node, edge : 'socks5://…'tag edgeWhitespace around the colon is normalized: the node is retained with tag edge and an entry-tag-normalized informational diagnostic. edge: … and quoted tags remain accepted.dialect-entries-05-spaced-tag
vless_mode in a VLESS share link or flattened serialized VLESS nodelegacy VLESS mode selectorRemoved, not an accepted alias. Share links must use independent udp=, packetEncoding=, and mux= queries. A flattened VLESS node rejects any vless_mode presence, including null, and uses network, packet_encoding, and multiplex instead.
vless_mode: legacy on a flattened serialized non-VLESS nodelegacy VLESS field outside VLESSCompatibility placeholder emitted and consumed by the wire adapter only; it does not select behavior. It is the sole non-VLESS serialization exception, not documented dae or share-link syntax.

Routing ​

Inputdae grammarhonkCases
Bare prefix matchers, !geosite:cn -> proxy, domain:example.com -> proxynot function calls; rejectedAccepted as matchers (geosite, geoip, domain, suffix, keyword, regex, full prefixes).dialect-routing-01-geosite-prefix, dialect-routing-01-domain-prefix
Two arrows, domain(x) -> proxy->backuprejectedThe outbound is literal proxy->backup, with legacy-arrow-target. Retained for compatibility; normal target validation applies.dialect-routing-02-two-arrows
-> proxy( must )call with parameter mustNon-direct targets still require the exact suffix (must); proxy( must ) is an outbound named proxy( must ). Direct targets parse their option list, so direct( must ) is terminal.dialect-routing-03-spaced-must, direct option tests
Whitespace before the parentheses, dport (443) -> proxyacceptedLocated unknown-traffic-predicate error. Remove whitespace before matcher parentheses.dialect-routing-04-spaced-call
An unknown matcher in a conjunction, dport(443) && domian(x) -> directaccepted by the grammar; validation is outside itLocated unknown-traffic-predicate error, also for negated terms. Correct the matcher; no term is silently discarded.dialect-routing-05-unknown, dialect-routing-05-negated
Arguments with several colons, dip(2001:db8::/32), mac(00:11:22:33:44:55)not literals; quote themKept whole: only a recognised prefix: (geosite:, geoip:, domain:, suffix:, keyword:, regex:, full:) is split at its colon.dialect-routing-06-ipv6, dialect-routing-06-mac
direct(mark: 10) / direct(mark: 0x10) / direct(mark: 010)function parameter; dae's typed decoder uses Go base-0 ParseUint, yielding 10 / 16 / 8Decimal or explicit 0x/0X hexadecimal only, yielding 10 / 16 / 10. Binary/octal prefixes, underscores, signs, overflow and reserved bits 0xc0000000 reject. This intentionally differs from both dae base-0 decoding and honk's hexadecimal-first global mark scalar above.direct mark tests
direct(mark: 512, must) / direct(must, mark: 512) and marked fallback:option order does not matterBoth orders are accepted for direct rules and fallback, including whitespace. Unknown/duplicate direct options reject safely; only direct targets support marks. The final merged fallback's whole action, including mark and must, applies after every ordinary rule.parser tests, include tests

The upstream typed mark and option behavior is defined by routing.ParseOutbound. Runtime low-30-bit policy masks, global defaults and restart requirements are in routing marks and socket marks.

DNS ​

Inputdae grammarhonkCases
Unquoted // in a request or response rulenot a comment// is data, never a comment. Trailing slash-comment text such as -> asis // note omits the malformed rule with legacy-slash-comment; quoted slashes remain literal. Replace DNS trailing // comments with #.dialect-dns-01-slash-comment, dialect-dns-01-quoted-slash
# in a request or response rule, qname(a#b) -> reject # notea#b is a literal, # note a commentOnly an unquoted token-head # starts a comment, after a space or tab. The pattern remains a#b and the action is reject; changed old reading emits legacy-dns-hash. Keep literal glued hashes; separate comments with whitespace outside quotes.dialect-dns-02-hash
-> in a request or response ruleone arrowThe rule splits at the first unquoted ->; further arrows stay in the action with legacy-arrow-target. The whole action is lowercased and must match a declared upstream exactly: MixedCase selects mixedcase, not a declaration named MixedCase. This action/reference namespace is unchanged.dialect-dns-03-two-arrows, dialect-dns-03-mixed-action
A matcher call split across lines, qname( newline a.example) -> rejectwhitespace, newlines included, is skippedDNS never assembles lines. Each incomplete nonblank line is omitted with located incomplete-dns-rule, so this example produces two warnings. Put the DNS call and action on one line. An unterminated quote has one lexical diagnostic, not an additional incomplete-line warning; recovery requires every open block to close outside quoted data.dialect-dns-04-split-call
An upstream with an outbound, u: 'udp://1.1.1.1:53' -> proxy or u: 'udp://1.1.1.1:53' outbound: proxythe arrow form is rejected (a declaration cannot carry ->); the outbound: proxy form is two adjacent declarationshonk extension: both forms make upstream u dial through outbound proxy; both suffixes are read only outside quoted URI data.dialect-dns-05-arrow-detour, dialect-dns-05-key-detour
Text after a matcher call, dport(443)junk -> proxy, qname(a.example)junk -> rejectrejected: the arrow must follow the callTraffic rejects the configuration with located trailing-matcher-text; DNS warns and omits the whole rule. Remove trailing matcher text.dialect-dns-06-traffic-junk, dialect-dns-06-dns-junk
A trailing comment on an upstream line, v: 'udp://8.8.8.8:53' # notecommentAn unquoted token-head # starts a comment, so the address is 8.8.8.8:53; the changed interpretation emits legacy-upstream-comment. Use ordinary trailing # comments; quotes protect address data.dialect-dns-07-upstream-comment
-> or outbound: inside a quoted upstream URLdataOnly unquoted descriptor suffixes select detours; 'https://dns.example/q?x=outbound:proxy#frag' stays one URI. A changed old split emits legacy-upstream-separator; put the detour outside URL quotes.dialect-dns-08-quoted-outbound, dialect-dns-08-quoted-arrow
qtype(...)function argumentsNames A, AAAA, CNAME, MX, TXT, NS, PTR, SOA, SRV, HTTPS, SVCB, ANY, * (case-insensitive) or decimal u16; unknown names emit invalid-qtype and omit the whole rule, including mixed or negated lists. Correct unknown names or use their numeric code. Explicit qtype() remains match-nothing; qtype('a,aaaa') still selects both types with legacy-quoted-list. Prefer bare or individually quoted items.dialect-dns-09-names, dialect-dns-09-1-28, dialect-dns-09-typo, dialect-dns-09-aggregate, dialect-dns-09-empty

Groups ​

Inputdae grammarhonkCases
group('hk,jp')one literalTwo subgroup tags, split on , and |.dialect-groups-01-aggregate, dialect-groups-01-pipe
filter: group()call without parametersRetained as an explicit empty contribution through serde and refresh, with empty-subgroup. Remove the filter for all nodes; explicit group() now selects none.dialect-groups-02-empty

Sections and includes ​

Inputdae grammarhonkCases
Unknown content in experimentalany declarationUnknown outer settings and unsupported legacy udp_nfqueue content, including nested blocks, are errors. Unknown scalar keys inside clash_api and cache_file receive unknown-key and are ignored; this forward-compatibility boundary does not weaken NFQUEUE validation.dialect-sections-01-outer-key, dialect-sections-01-nfqueue-block, dialect-sections-01-child-key
Unknown nested blocks in global, dns, routing, group settings, or recognized experimental childrenany expressionOne unknown-block warning at the header; skip the complete balanced subtree, including DNS child contexts. Descendant settings and rules no longer leak into the parent. Move known settings to their documented block level.dialect-sections-02-global-wrapper, dialect-sections-02-dns-wrapper, dialect-sections-02-routing-wrapper, dialect-sections-02-group-wrapper
Node/subscription wrappers and nested subscription-setting wrappersnot a separate wrapper featureRetained bounded compatibility traversal, with legacy-wrapper at each wrapper. Arbitrary names at group root are real groups, not wrappers. A subscription header with a quoted URL, tag: 'url' { … }, is an entry with options, not a wrapper: entry-shaped children inside it are ignored with unknown-key. Wrapper compatibility will be reviewed after the first PR2 release.dialect-sections-03-node-wrapper, dialect-sections-03-sub-wrapper
Unknown root statements and scalar keysarbitrary identifiersWarn and omit after structure recognition; unknown root blocks are skipped as complete subtrees. Names of groups, entry tags, upstreams and TTL owners are dynamic names, not unknown schema keys. Traffic lines without arrows are diagnosed and omitted unless they declare a fallback.dialect-sections-04-root-statement, dialect-sections-04-unknown-key, dialect-sections-04-unknown-root, dialect-sections-04-traffic-statement
An assets { … } rootnot a dae sectionhonk extension: download defaults for geodata, the external UI and subscriptions; see the assets reference. The five experimental download keys it replaces remain accepted, each occurrence with a legacy-assets-key warning.
include { … }a block like any otherPatterns expand only on file loading, with the ordering and confinement rules in the configuration guide; string parsing checks structure without opening files. Adjacent quoted paths such as 'first.dae''quoted # name.dae' retain quoted hashes and braces during initial token acquisition, in both modes. Every quote must close on its physical line. Only token-head # starts a comment; absolute.dae#note is a literal pattern with legacy-include-hash, and normal .dae filtering still applies.dialect-sections-05-include, dialect-sections-05-adjacent, dialect-sections-05-hash

Source: honk upstream, licensed under GPL-3.0-only.