2011/12/16 - How ACLs work internally in haproxy - w@1wt.eu

An ACL is declared by the keyword "acl" followed by a name, followed by a
matching method, followed by one or multiple pattern values :

    acl internal  src  127.0.0.0/8 10.0.0.0/8 192.168.0.0/16

In the statement above, "internal" is the ACL's name (acl->name), "src" is the
ACL keyword defining the matching method (acl_expr->kw) and the IP addresses
are patterns of type acl_pattern to match against the source address.

The acl_pattern struct may define one single pattern, a range of values or a
tree of values to match against. The type of the patterns is implied by the
ACL keyword. For instance, the "src" keyword implies IPv4 patterns.

The line above constitutes an ACL expression (acl_expr). ACL expressions are
formed of a keyword, an optional argument for the keyword, and a list of
patterns (in fact, both a list and a root tree).

Dynamic values are extracted according to a fetch function defined by the ACL
keyword. This fetch function fills or updates a struct acl_test with all the
extracted information so that a match function can compare it against all the
patterns. The fetch function is called iteratively by the ACL engine until it
reports no more value. This makes sense for instance when checking IP addresses
found in HTTP headers, which can appear multiple times. The acl_test is kept
intact between calls and even holds a context so that the fetch function knows
where to start from for subsequent calls. The match function may also use the
context even though it was not designed for that purpose.

An ACL is defined only by its name and can be a series of ACL expressions. The
ACL is deemed true when any of its expressions is true. They are evaluated in
the declared order and can involve multiple matching methods.

So in summary :

  - an ACL is a series of tests to perform on a stream, any of which is enough
    to validate the result.

  - each test is defined by an expression associating a keyword and a series of
    patterns.

  - a keyword implies several things at once :
    - the type of the patterns and how to parse them
    - the method to fetch the required information from the stream
    - the method to match the fetched information against the patterns

  - a fetch function fills an acl_test struct which is passed to the match
    function defined by the keyword

  - the match function tries to match the value in the acl_test against the
    pattern list declared in the expression which involved its acl_keyword.


ACLs are used by conditional processing rules. A rule generally uses an "if" or
"unless" keyword followed by an ACL condition (acl_cond). This condition is a
series of term suites which are ORed together. Each term suite is a series of
terms which are ANDed together. Terms may be negated before being evaluated in
a suite. A term simply is a pointer to an ACL.

We could then represent a rule by the following BNF :

  rule = if-cond
       | unless-cond

  if-cond (struct acl_cond with ->pol = ACL_COND_IF)
      = "if" condition

  unless-cond (struct acl_cond with ->pol = ACL_COND_UNLESS)
      = "unless" condition

  condition
      = term-suite
      | term-suite "||" term-suite
      | term-suite "or" term-suite

  term-suite (struct acl_term_suite)
      = term
      | term term

  term = acl
       | "!" acl

