HLFL SYNTAX ----------------------------------------------------------------------------- hlfl are a list of statements. Each statement have the following syntax : statement ::= "protocol" ("local") "operator" ("remote") ["on"] [interfaces] keywords 'local' is the network you want to protect 'remote' is the network you want to be protected from Example : tcp (192.168.1.1) X (192.168.2.1) [fxp0,xl1] Means in bsd ipfw language : ipfw -f add deny tcp from 192.168.1.1 to 192.168.2.1 out via fxp0 ipfw -f add deny tcp from 192.168.2.1 to 192.168.1.1 in via fxp0 ipfw -f add deny tcp from 192.168.1.1 to 192.168.2.1 out via xl1 ipfw -f add deny tcp from 192.168.2.1 to 192.168.1.1 in via xl1 And this means in a human language : 'deny communication between 192.168.1.1 and 192.168.2.1' all (any) X (any) Means : ipfw -f add deny all from any to any List of operators : ----------------------------------------------------------------------------- 1. Symbolic operators ===================== -> : accept outgoing <- : accept incoming <-> : accept outgoing and incoming <=>> : accept outgoing and incoming if the communication was established <<=> : accept outgoing and incoming if the communication was established by the remote side X : deny incoming and outgoing X! : reject incoming and outgoing X-> : deny outgoing <-X : deny incoming X!-> : reject outgoing <-X! : reject incoming Note that <=>> and <<=> work for UDP on stateful firewalls. On stateless firewall, they work the same way as <-> for UDP. 2. Non-symbolic operators ========================= Symbolic operators may be a pain to read when writing huge and complex filtering rules, so another set of operators has been defined. Their syntax is: operator ::= "accept" | "deny" | "reject" ["from" | "to" | "and" | "established" | "log"] Examples : # Accept outgoing packets from 192.168.1.1 and going to # 192.168.2.1, and incoming packets coming from 192.168.2.1 going # to 192.168.2.2 (ie: the same as <->) tcp (192.168.1.1) accept (192.168.2.1) on interface0 # the above statement is the same as tcp (192.168.1.1) accept from and to (192.168.2.1) on interface0 # # Accept outgoing connections from 10.1.1.1 to 10.2.2.2, and # log them (the same as "log <=>>") # tcp (10.1.1.1) accept established to and log (10.2.2.2) (src) and (dst) format ------------------------------------------------------------------------- (src) and (dst) can be : (ip ports) ie : (192.168.1.1 1-1024) (192.168.2.1 21,22,80,49152-65535) Or : (ip ports | other_ip other_ports | ....) ie : (192.168.1.1|192.168.1.12|192.168.1.200) Several sources can be combined with several destinations : tcp (192.168.1.1|192.168.2.1|192.168.3.1) <=> (172.22.0.1|172.22.0.2|172.22.0.3) Means : 192.168.1.1 can communicate with 172.22.0.1, 172.22.0.2 and 172.22.0.3 192.168.2.1 can communicate with 172.22.0.1, 172.22.0.2 and 172.22.0.3 192.168.3.1 can communicate with 172.22.0.1, 172.22.0.2 and 172.22.0.3 The keyword 'nomix' can be added at the end of a line, in this case, tcp (192.168.1.1|192.168.2.1|192.168.3.1) <=> (172.22.0.1|172.22.0.2|172.22.0.3) nomix means : 192.168.1.1 can communicate with 172.22.0.1 192.168.2.1 can communicate with 172.22.0.2 192.168.3.1 can communicate with 172.22.0.3 tcp (192.168.1.1 80 | 192.168.2.1 21) X (172.22.0.1) is a valid statement. It prevents 172.22.0.1 to communicate with 192.168.1.1 on port 80 and 192.168.2.1 on port 21 Finally, tcp ((192.168.1.1|192.168.2.1) 80,21) X (172.22.0.1) is a valid statement which prevents 172.22.0.1 to communicate with 192.168.1.1 and 192.168.2.1 on ports 21 and 80 interfaces format ------------------------------------------------------------------------------- [interfaces] could be * empty * one interface * or an array of interfaces separated by commas ie: tcp (192.168.1.1) X (192.168.1.2) means that 192.168.1.1 and 192.168.1.2 are not allowed to communicate, whatever the interface. tcp (192.168.1.1) X (192.168.1.2) [eth0] means that 192.168.1.1 and 192.168.1.2 are not allowed to communicate, using interface eth0. tcp (192.168.1.1) <-> (192.168.1.2) [fxp1,fxp2] means that 192.168.1.1 and 192.168.1.2 are allowed to communicate, using interface fxp1 or fxp2. tcp (10.0.0.1) <-> (10.254.254.254) on Ethernet0 means that 10.0.0.1 and 10.254.254.254 are allowed to communicate using interface Ethernet0 Other valid statements : tcp (10.0.0.1) <-> (10.1.1.1) on [eth1, eth2, eth3] tcp (10.0.0.1) <-> (10.1.1.1) on (eth1,eth2,eth3) ICMP ------------------------------------------------------------------------------- The following icmp types are recognized : echo-request echo-reply destination-unreachable time-exceeded To have a working 'ping' : icmp (any echo-request) -> (any) icmp (any) <- (any echo-reply) Variables ------------------------------------------------------------------------------- It is possible to define variables to make the rules more readable, using the keyword 'define' define word value ie : define inside 192.168.1.0/24 define public_ports 1024-65535 (inside public_ports) <=>> (any) Include ------------------------------------------------------------------------------ It is possible to include a file using the 'include' instruction. Example : o in def.hlfl : define ftp 21 define ssh 22 define telnet 23 o in rules.hlfl : include def.hlfl tcp (any ssh) <<=> (any) Comments ------------------------------------------------------------------------------- There are three kinds of comments that are handled by hlfl : - comments starting by '%' will *not* be included in the generated file. That is : % % This rules are written in hlfl % tcp (any) -> (any) Will produce, in ipchains format : ipchains -A output -s 0/0 -d 0/0 -j ACCEPT - comments starting by '#' will be included in the generated file, except when cisco rules are selected. That is : % % This rules are written in hlfl % # allow all the outgoing tcp tcp (any) -> (any) Will produce : # allow all the outgoing tcp ipchains -A output -s 0/0 -d 0/0 -j ACCEPT - Comments starting by '!' will be included as COMMANDS in the generated file. This reduces portabilty, since your rules will depend of the underlying firewall, but this is convieniant in some cases (typically, if you play with forwarding, masquerading, and so on...) !ipchains -A forward -s 0/0 -d 0/0 -i eth0 -j ACCEPT tcp (any) -> (any) Will produce : ipchains -A forward -s 0/0 -d 0/0 -i eth0 -j ACCEPT ipchains -A output -s 0/0 -d 0/0 -j ACCEPT Note that if you select the ipfw output, you'll get this nonnense : ipchains -A forward -s 0/0 -d 0/0 -i eth0 -j ACCEPT ipfw add allow tcp from any to any out So you can define conditional inclusion. That is : ! if(ipchains) $ipchains -A forward -s 0/0 -d 0/0 -j eth0 -j ACCEPT In that case, this rule will only be printed if you are generating ipchains output. The 'else' statement is supported, so the following is valid : ! if(ipchains) $ipchains -A forward -s 0/0 -d 0/0 -i eth0 -j MASQ ! else echo "MASQUERADING NOT IMPLEMENTED" ; exit Real-life examples ------------------------------------------------------------------------------- See sample_1.hlfl and sample_2.hlfl