Design proposal for a better ACL language

Daniel Franke dfoxfranke at
Fri Jun 10 19:02:47 UTC 2016

Remove the following existing configuration commands:

* discard
* restrict
* controlkey
* requestkey
* trustedkey

And replace them with a directive named 'rule', with the following
EBNF syntax:

rule = 'rule', {predicate}, disposition, [key]
predicate = ['not'], atom
atom = 'source', CIDR-BLOCK
     | 'destination' CIDR-BLOCK
     | 'srcport', range
     | 'dstport', range
     | 'type', type
     | 'mode', mode
     | 'version', range
     | 'assoc', assocstatus
     | 'hiskey', range
     | 'hiskey match'
     | 'authentic', boolean
     | 'avgrate', INTEGER
     | 'minrate', INTEGER
     | 'flake', [INTEGER]
range = INTEGER, ['-', INTEGER]
type = 'request' | 'response' | 'cryptonak' | 'kod', [STRING]
mode = 'clientserver' | 'symmetric' | 'broadcast' | 'query' | 'modify'
assocstatus = 'permanent' | 'ephemeral' | 'none'
boolean = 'true' | 'false' | 'yes' | 'no'
disposition = 'allow' | 'peer'
            | 'drop' | 'ignore' | 'unpeer'
            | 'kod', [STRING]
            | 'cryptonak'
key = 'mykey', INTEGER

Additionally, add the 'enablemodify' directive which takes no

Each time a packet arrives, it is first checked agaist one implicit
rule (given below), unless the 'enablemodify' has directive is given
Then it is checked against each 'rule' directive in the configuration
file, in order. If all the predicates in a rule directive match, the
packet is processed according to the specified disposition, and the
outgoing response (if any), is MACed using the specified key. If one
or more predicates do not match, the next rule is checked. If no rule
matches, the packet is then checked against some built-in implicit
rules (given below), which finally end with a catch-all 'rule deny'
directive. The user may effectively disable these implicit rules by
including a catch-all rule of his own so that they are never reached.

The predicates have the following meanings:

source CIDR-BLOCK: the request was sent from an IP in the given CIDR
block, which may be given as IPv4, IPv6, or v4-mapped IPv6.

destination CIDR-BLOCK: ditto but for the destination to which the
packet was sent (only interesting if ntpd is listening on multiple

srcport/dstport: the request was sent from or to the given port or
range of ports.

type: Whether the packet is a request, a (normal) response, a
crypto-NAK, or a KoD. Broadcast packets are considered responses.
Symmetric-mode packets that do not correspond to any existing
association are considered requests. Symmetric-mode packets that do
correspond to an existing association match both 'type request' and
'type response'. If the optional string argument is given for a kod,
it matches only KoDs with the given string in the refid field.

mode: Match on the mode of the packet
  clientserver: any mode 3 or mode 4 packet
  symmetric: any mode 1 or mode 2 packet
  broadcast: any mode 5 packet
  query: any mode 6 packet
  modify: a mode 6 packet which will modify the configuration if processed

version: Match on the packet's version number

assoc: Match on the status of ntpd's association with the sender:
permanent, ephemeral, or none.

hiskey: Match on the packet's keyid field. In the "hiskey match" form,
this implies "type response", and the keyid must match the one that
was used for the corresponding request.

authentic: Match on whether a MAC is present and valid

avgrate: Matches if the sender has been sending packets at an average
rate greater than or equal to 2^n seconds.

minrate: Matches if it has been less than 2^n seconds since we last
received a packet from this sender.

flake: Matches if a random number between 1 and 100 is greater than or
equal to the argument. If omitted, the argument defaults to 10.

The dispositions have the following meanings:

allow: Process the packet; do not change association status with the

peer: Process the packet; additionally, if the packet is
symmetric-active or broadcast, set up an ephemeral association with
the sender if no association currently exists.

deny: Take no action on the packet besides updating the MRU cache for
rate control

ignore: Take no action on the packet whatsoever

unpeer: Do not reply to the packet, and delete any association with
the sender

kod: Reply to the packet with a KoD with the given refid field. If not
specified, defaults to "RATE".

cryptonak: Reply to the packet with a crypto-NAK.

Basic sanity checks, such as validating the origin timestamp, are
always performed, prior to applying any 'rule' directives. No rule
directive will ever cause such packets to be accepted.
If a 'mykey' clause is given, it specifies the keyid used to MAC the
response. If not given, and the keyid in the request is recognized,
this same key will be used for the response. If the keyid is not
recognized, the response will not be MACed.

These are the implicit rules:

rule type response mode clientserver not assoc none allow
rule type response mode symmetric not assoc none allow
rule type kod mode clientserver not assoc none allow
rule type kod mode symmetric not assoc none allow
rule type request mode clientserver allow
rule source mode query allow
rule source [::1/128] mode query allow
rule deny

These rules mean: accept responses (including unauthenticated ones)
and honor KoDs from servers and symmetric peers with which we have an
existing association; serve responses to basic time requests from
clients; answer ntpq queries from localhost; and deny everything else.

Additionally, the following implicit rule is processed *prior* to the
rules in configuration file unless the 'enablemodify' directive is

rule mode modify deny

Note that giving 'enablemodify' is not by itself sufficient to allow
any modification requests to be processed, since these would fall
through to the 'rule deny' catch-all unless the user gives a rule
specifying that some should be accepted. enablemodify exists only as
an extra safeguard to protect the user from writing overbroad rules
which accept such requests by accident.

More information about the devel mailing list