How to write XML rules for DAXFi

Davide Alberani <alberanid@libero.it>

September, 06 2002
Here you can learn how to write a XML file useful to describe a firewall rule.

1. Introduction

DAXFi is a Python package useful to configure and manage several different kinds of firewalls in a consistent way, without the need to worry about firewall-dependent issues. To stay firewall-independent, DAXFi defines a common way to describe firewalls rules: it uses a XML representation.

Fortunately, DAXFi doesn't require you to rewrite all your rules in XML from scratch: if you already have a firewall with your rules running, you can use the daxfidump script to get a set of XML files with your rules. Obviously, it's possible that you want to modify these rules, so keep reading this how-to.

2. XML headers, syntax and terminology

2.1 Prolog and Document Type Declaration

If you want to write a XML rule for DAXFi, in a file or directly in a Python script, you have to begin with the XML prolog:

<?xml version='1.0'?>

It must be at the very begin of the file or string (no white spaces, tabs or newlines are allowed before this line).

Now you can point, optionally, to a DTD with a line like:

<!DOCTYPE ruleset SYSTEM '/etc/daxfid/dtd.d/daxfifw_0.5.dtd'>

where the string after 'SYSTEM' must point to the DTD file and the string after 'DOCTYPE' ('ruleset', in this example) is the name of the main tag.

2.2 Syntax

A XML document looks like a SGML or HTML file: there are normal tags, empty tags and they can have attributes. Some things you must remember to write a valid XML:

2.3 DAXFi specific syntax

In the next sections, DAXFi specific tags and attributes are described; some notes for attribute values:

3. One or more rules?

Within a XML file or string, you may decide to put one or more rule definition.

If you want to have the same action applied to one or more rules, you can begin with an 'action tag' as described in the next chapter; example:


<action>
    <rule1 />
    <rule2 />
    ...
</action>

If you want to have different actions (applied to different rules), you have to use 'ruleset' as the main tag (wrapping many action tags) , as:


<ruleset>
    <action1>
        <rule1a />
        <rule1b />
        ...
    </action1>
    <action2>
        <rule2a />
        <rule2b />
        ...
    </action2>
    ...
</ruleset>

4. Define an action

Every rule or set of rules must be enclosed in what I call an 'action tag'; this is one of the valid actions enumerated below and are used to tell DAXFi what to do with this rule (e.g.: append this rule at the end of a chain, purge a chain, delete a given rule and so on).

Inside the action tag there must be one or more rules. The action is applied to these rules.

4.1 Action tags

Examples of valid action tags are:

<append>...</append>

And:

<insert rule-number='5'>...</insert>

4.2 Action tags attributes

5. The rule tag

In the 'rule' tag are included the attributes that belongs to every IP packets (e.g.: the source and destination addresses, the interface name, if we must consider only fragments, etc. etc.)

5.1 rule tag attributes

Examples; match entering packets from the 127.0.0.1/8 subnet on interface ppp0:


<rule direction='in' source-ip='127.0.0.1/8' interface='ppp0'>
    ...
</rule>

Match only the first fragment of fragmented outgoing packets from 127.0.0.1 directed to 4.5.6.0/24:


<rule direction='out' source-ip='127.0.0.1' destination-ip='4.5.6.0/24' fragment-only='yes'>
      ...
</rule>

6. Protocol

Inside a rule tag, you can put an empty tag with the name of one of the main protocols (TCP, UDP and ICMP) or a 'protocol' tag with a 'protocol' attribute. E.g., for matching only tcp packets:

<tcp />

For udp:

<udp />

And for icmp:

<icmp />

If you want to match against a different protocol:

<protocol protocol='igmp' />

6.1 Protocol tags attributes

Examples; match TCP packets from the 1234 port:

<tcp source-port='1234' />

Match UDP to the identd daemon:

<udp destination-port='auth' />

Match the first packets of a connection to any UDP except the range 100-200:

<udp syn-only='yes' destination-port='! 100:200' />

Match 'host-redirect' ICMP packets:

<icmp icmp-type='host-redirect' />

The same, using ICMP type/code:

<icmp icmp-type='5/1' />

7. Target

Now you want to tell DAXFi what to do with the matched packets. You can use an empty tag named 'accept', 'reject' or 'drop', or you can use a 'target' tag with a 'target' attribute. The target tag must be omitted writing a NAT rule.

Examples; accept the patched packets (the packets will pass through the firewall):

<accept />

Drop packets (they will be silently discarded):

<drop />

Reject packets (packets will be dropped, and an ICMP error code sent back):

<reject />

It's equivalent to:

<target target='reject' />

7.1 Target tags attributes

Example; reject packets sending back a 'network-prohibited' ICMP:

<reject reject-with='network-prohibited' />

8. NAT

With this empty tag, you set up a rule to do Network Address Translation.

Sometimes you need to mangle the source or destination of a packet; we define, with empty tags, four operation: with 'snat' you alter the source address of matched packets; you have to specify the new source IP address and optionally a port or range of ports. The 'dnat' tag is used when you need to redirect a packet directed to a given IP address to another one; you have to specify the new destination address and, optionally, a new port or range of ports. 'masq' is a special case of 'snat', useful with dial-up: you don't have to specify the new source IP since the one of the outgoing interface is used; optionally you can specify a port or range of ports. 'redirect' is a special case of 'dnat': the matched packets are sent to the local machine (IP 12.7.0.0.1); optionally it's possible to specify a new destination port. The generic 'nat' tag can also be used, and requires a 'nat' attribute to specify the type of NAT you need.

8.1 NAT tag attributes

Examples; TCP packets from 1.2.3.4 are NATed as they are coming from 5.6.7.8 on interface 'le1' with a new port, in the range 1-1024:


<rule source-ip='1.2.3.4' interface='le1'>
    <tcp />
    <snat to-address='5.6.7.8' to-port='1:1024' />
</rule>

Packets directed to IP address 7.7.7.7 are redirected to one IP in the range from 1.1.1.1 to 3.3.3.3:


<rule destination-ip'7.7.7.7'>
    <dnat to-address='1.1.1.1:3.3.3.3' />
</rule>

TCP packets from 1.2.3.0/24 tcp port 80 are redirected to the local port 8080:


<rule source-ip='1.2.3.0/24'>
    <tcp source-port='80' />
    <redirect to-port='8080' />
</rule>

Packets from 192.168.1.0/24 will be rewritten as they are coming from the address of the 'ppp0' interface:


<rule source-ip='192.16.1.0/24' interface='ppp0'>
    <masq />
</rule>

9. Log

Sometimes you need to leave a message in the system logs when a given packets is received.

A 'log' empty tag can be used.

9.1 Log tag attributes

Examples; log with priority 'info' and with facility 'local1':

<log facility='local1' priority='info' />

10. Limit

Actually, this empty tag is supported only by the iptables firewall and it's safely ignored by others. It set limits for matched packets.

10.1 Limit tag attributes

Example; match three packets in an hour, with a burst of 10:

<limit rate='3/h' burst='10' />

11. Processing instruction

Sometimes you need to give special instructions to the XML parser. You can add a processing instruction right below the XML prolog, with the syntax:

<?daxfi-process attributeName='attributeValue'?>

Valid attributes are:

Example; a set of rules to be executed only if the firewall is not iptables or ipfilter:


<?xml version='1.0'?>
<?daxfi-process not-for='iptables,ipfilter'?>
<ruleset>
    ...
</ruleset>

12. Parameter substitution

If you're using a dial-up connection normally you don't know what IP address will be assigned to you by the provider and the name of the network interface used by the pppd daemon. Using a DAXFi-based program you can leave in your XML files some placeholders and tell the program to substitute these strings with other data.

The Firewall class in the daxfi Python package takes as constructor parameter the 'substitutionDict' keyword, that must be a Python dictionary in the form {'placeHolder1': 'value1', 'placeHolder2': 'value2', ...}. Before any operation on XML files or strings, the Firewall object will replace the strings 'placeHolder1', 'placeHolder2' and so on with 'value1', 'value2', etc.

As example, the daxfid script will search for '%REMOTE_IP', '%LOCAL_IP' and '%INTERFACE', replacing these strings with the remote IP, the local assigned IP and the interface of the connection.

13. Examples

Accept any incoming TCP packet from the network 192.196.1.0/24, to the port 80.
<?xml version='1.0'?>

<append>
  <rule source-ip='192.196.1.0/24' direction='in'>
    <tcp destination-port='80' />
    <accept />
  </rule>
</append>
iptables iptables -A INPUT -p 6 -j ACCEPT --destination-port 80 --source 192.196.1.0/255.255.255.0
ipchains ipchains -A input -j ACCEPT --destination-port 80 -p 6 --source 192.196.1.0/255.255.255.0
ipfwadm ipfwadm -a accept -I -S 192.196.1.0/255.255.255.0 -D 0.0.0.0/0.0.0.0 80 -P tcp
ipfilter pass in quick proto 6 from 192.196.1.0/255.255.255.0 to 0.0.0.0/0.0.0.0 port = 80

Example of more than one rule in a single file. Note the use of the boolean 'fragment-only' option in the second rule.
<?xml version='1.0'?>

<ruleset>
  <append>
    <rule direction='in'
          source-ip='192.168.1.0/24'>
      <tcp />
      <accept />
    </rule>

    <rule direction='out'
          fragment-only='yes'
          destination-ip='192.168.1.0/24'>
      <tcp />
      <accept />
    </rule>

  </append>

  <flush>
    <rule direction='out' />
  </flush>

</ruleset>
iptables iptables -A INPUT -p 6 -j ACCEPT --source 192.168.1.0/255.255.255.0
iptables -A OUTPUT -p 6 -j ACCEPT --destination 192.168.1.0/255.255.255.0 --fragment
iptables -F OUTPUT
ipchains ipchains -A input -j ACCEPT -p 6 --source 192.168.1.0/255.255.255.0
ipchains -A output -j ACCEPT -p 6 --destination 192.168.1.0/255.255.255.0 --fragment
ipchains -F output
ipfwadm ipfwadm -a accept -I -S 192.168.1.0/255.255.255.0 -P tcp
# This rule can't be generated for this firewall.
ipfwadm -f -O
ipfilter pass in quick proto 6 from 192.168.1.0/255.255.255.0 to 0.0.0.0/0.0.0.0
pass out quick proto 6 from 0.0.0.0/0.0.0.0 to 192.168.1.0/255.255.255.0 with frag
# Execute the command: ipf -Fo

Insert a rule in the third position of the 'input chain'.
<?xml version='1.0'?>

<insert rule-number='3'>
  <rule source-ip='192.196.1.0/24'
        destination-ip='1.2.3.4'
        interface='ppp0'
        direction='in'>
    <accept />
  </rule>
</insert>
iptables iptables -I INPUT 3 -j ACCEPT --in-interface ppp0 --destination 1.2.3.4/255.255.255.255 --source 192.196.1.0/255.255.255.0
ipchains ipchains -I input 3 -j ACCEPT --interface ppp0 --destination 1.2.3.4/255.255.255.255 --source 192.196.1.0/255.255.255.0
ipfwadm ipfwadm -i accept -I -W ppp0 -D 1.2.3.4/255.255.255.255 -S 192.196.1.0/255.255.255.0
ipfilter @3 pass in quick on ppp0 from 192.196.1.0/255.255.255.0 to 1.2.3.4/255.255.255.255

Flush the 'output chain'.
<?xml version='1.0'?>

<flush>
  <rule direction='out' />
</flush>
iptables iptables -F OUTPUT
ipchains ipchains -F output
ipfwadm ipfwadm -f -O
ipfilter # Execute the command: ipf -Fo

This rule replaces the one in the second position of the 'input chain'.
<?xml version='1.0'?>

<replace rule-number='2'>
  <rule source-ip='1.2.3.4' direction='in'>
    <udp />
    <accept />
  </rule>
</replace>
iptables iptables -R INPUT 2 -p 17 -j ACCEPT --source 1.2.3.4/255.255.255.255
ipchains ipchains -R input 2 -j ACCEPT -p 17 --source 1.2.3.4/255.255.255.255
ipfwadm # This rule can't be generated for this firewall.
ipfilter # This rule can't be generated for this firewall.

Delete the seventh rule in the 'output chain'.
<?xml version='1.0'?>

<delete rule-number='7'>
  <rule direction='out' />
</delete>
iptables iptables -D OUTPUT 7
ipchains ipchains -D output 7
ipfwadm # This rule can't be generated for this firewall.
ipfilter # This rule can't be generated for this firewall.

Remove the defined rule.
<?xml version='1.0'?>

<delete>
  <rule direction='out' source-ip='192.168.2.0/24'>
    <tcp />
    <accept />
  </rule>
</delete>
iptables iptables -D OUTPUT -p 6 -j ACCEPT --source 192.168.2.0/255.255.255.0
ipchains ipchains -D output -j ACCEPT -p 6 --source 192.168.2.0/255.255.255.0
ipfwadm ipfwadm -d accept -O -S 192.168.2.0/255.255.255.0 -P tcp
ipfilter # Remove the rule: "pass out quick proto 6 from 192.168.2.0/255.255.255.0 to 0.0.0.0/0.0.0.0"

Some rules using icmp.
<?xml version='1.0'?>

<append>
  <rule direction='in'>
    <tcp syn-only='no' source-port='!123:239' />
    <reject reject-with='network-prohibited' />
  </rule>

  <rule direction='in'>
    <icmp icmp-type='host-redirect'/>
    <drop />
  </rule>

  <rule direction='in'>
    <icmp icmp-type='3/2'/>
    <drop />
  </rule>

</append>
iptables iptables -A INPUT -p 6 -j REJECT --source-port ! 123:239 ! --syn --reject-with icmp-net-prohibited
iptables -A INPUT -p 1 -j DROP --icmp-type 5/1
iptables -A INPUT -p 1 -j DROP --icmp-type 3/2
ipchains ipchains -A input -j REJECT --source-port ! 123:239 -p 6 ! --syn
ipchains -A input -j DENY --source 0.0.0.0/0.0.0.0 5 --destination 0.0.0.0/0.0.0.0 1 -p 1
ipchains -A input -j DENY --source 0.0.0.0/0.0.0.0 3 --destination 0.0.0.0/0.0.0.0 2 -p 1
ipfwadm # This rule can't be generated for this firewall.
ipfwadm -a deny -I -S 0.0.0.0/0.0.0.0 5 -P icmp
ipfwadm -a deny -I -S 0.0.0.0/0.0.0.0 3 -P icmp
ipfilter block return-icmp(9) in quick proto 6 from 0.0.0.0/0.0.0.0 port 123 <> 239 to 0.0.0.0/0.0.0.0 flags SA
block in quick proto 1 from 0.0.0.0/0.0.0.0 to 0.0.0.0/0.0.0.0 icmp-type 5 code 1
block in quick proto 1 from 0.0.0.0/0.0.0.0 to 0.0.0.0/0.0.0.0 icmp-type 3 code 2

Any packet from IPs in '192.168.3.0/24' on interface 'le1' have the source IP modified to '5.6.7.8' and a new source port in the range between 1 and 1024.
<?xml version='1.0'?>

<append>
  <rule direction='in' source-ip='192.168.3.0/24' interface='le1'>
    <snat to-address='5.6.7.8' to-port='1:1024' />
  </rule>
</append>
iptables iptables -A POSTROUTING -p 6 -t nat -j SNAT --to-source 5.6.7.8:1-1024 --out-interface le1 --source 192.168.3.0/255.255.255.0
ipchains # This rule can't be generated for this firewall.
ipfwadm # This rule can't be generated for this firewall.
ipfilter map le1 from 192.168.3.0/255.255.255.0 to 0.0.0.0/0.0.0.0 -> 5.6.7.8/255.255.255.255 portmap tcp 1:1024

Masquerade every packet from '192.16.1.0/24' on 'ppp0' interface as they are coming from the local machine.
<?xml version='1.0'?>

<append>
  <rule source-ip='192.16.1.0/24' interface='ppp0'>
    <masq />
  </rule>
</append>
iptables iptables -A POSTROUTING -t nat -j MASQUERADE --out-interface ppp0 --source 192.16.1.0/255.255.255.0
ipchains ipchains -A forward -j MASQ --interface ppp0 --source 192.16.1.0/255.255.255.0
ipfwadm ipfwadm -a masquerade -F -W ppp0 -S 192.16.1.0/255.255.255.0
ipfilter map ppp0 from 192.16.1.0/255.255.255.0 to 0.0.0.0/0.0.0.0 -> 0/32

Any TCP packet from IPs in '1.2.3.0/24' on interface 'le2', to port '80' are redirected to the local machine, port 8080.
<?xml version='1.0'?>

<append>
  <rule source-ip='1.2.3.0/24' interface='le2'>
    <tcp source-port='80' />
    <redirect to-port='8080' />
  </rule>
</append>
iptables iptables -A OUTPUT -p 6 -t nat -j REDIRECT --to-ports 8080 --source-port 80 --out-interface le2 --source 1.2.3.0/255.255.255.0
ipchains ipchains -A input -j REDIRECT 8080 --source-port 80 -p 6 --interface le2 --source 1.2.3.0/255.255.255.0
ipfwadm ipfwadm -a accept -I -r 8080 -S 1.2.3.0/255.255.255.0 80 -P tcp -W le2
ipfilter rdr le2 from 1.2.3.0/255.255.255.0 port = 80 to 0.0.0.0/0.0.0.0 -> 127.0.0.1 port 8080 tcp

Write a log for matching packets.
<?xml version='1.0'?>

<append>
  <rule direction='in' interface='ppp0' destination-ip='127.0.0.1/8'>
    <drop />
    <log priority='warn' facility='local2' />
  </rule>
</append>
iptables iptables -A INPUT -j LOG --log-level warn --in-interface ppp0 --destination 127.0.0.0/255.0.0.0
iptables -A INPUT -j DROP --in-interface ppp0 --destination 127.0.0.0/255.0.0.0
ipchains ipchains -A input -j DENY -l --interface ppp0 --destination 127.0.0.0/255.0.0.0
ipfwadm ipfwadm -a deny -I -W ppp0 -D 127.0.0.0/255.0.0.0
ipfilter block in log level local2.warn quick on ppp0 from 0.0.0.0/0.0.0.0 to 127.0.0.0/255.0.0.0

Packets incoming on interface 'ppp0' to address '127.0.0.1/8 are accepted only if part of an already established connection.
<?xml version='1.0'?>

<append>
  <rule direction='in'>
    <tcp state='related'/>
    <accept />
  </rule>
</append>
iptables iptables -A INPUT -p 6 -m state -j ACCEPT --state ESTABLISHED,RELATED
ipchains # This rule can't be generated for this firewall.
ipfwadm # This rule can't be generated for this firewall.
ipfilter pass out quick proto 6 from 0.0.0.0/0.0.0.0 to 0.0.0.0/0.0.0.0 keep state

Use of processing instructions: the defined rule is valid only for firewalls that are not 'iptables' or 'ipfilter'.
<?xml version='1.0'?>

<?daxfi-process not-for='iptables,ipfilter'?>

<append>
  <rule direction='out' destination-ip='127.0.0.1' fragment-only='no'>
    <drop />
  </rule>
</append>
iptables
ipchains ipchains -A output -j DENY --destination 127.0.0.1/255.255.255.255 ! --fragment
ipfwadm # This rule can't be generated for this firewall.
ipfilter

14. Copyright

Copyright 2001, 2002 Davide Alberani <alberanid@libero.it>

Redistribution and use in source (SGML DocBook) and 'compiled' forms (SGML, HTML, PDF, PostScript, RTF and so forth) with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code (SGML DocBook) must retain the above copyright notice, this list of conditions and the following disclaimer as the first lines of this file unmodified.
  2. Redistributions in compiled form (transformed to other DTDs, converted to PDF, PostScript, RTF and other formats) must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

Important: THIS DOCUMENTATION IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.