Kroon Information Systems
Howto: iptables and MAC-based access control

Last updated 9 August 2023


In many ad-hoc networks it is relatively simple to gain access to the physical network. In order to find what IP ranges are required one merely needs to sniff a bit (tcpdump), or even better, set your network card to use dhcp. This allows practically anyone to obtain access to an ad-hoc network.

The question now arrises what do I mean with an ad-hoc network. Well, an ad-hoc network from my point of view is a network that is expanded by any and all means by anyone with physical access to the network in order to add more machines to the network. This is a very bad situation.

Whilst the concepts in this article has been developed due to a need that has arrisen from such an ad-hoc network the concepts should be equally applicable to other environments too where specific machines should be allowed certain accesses and others denied it. This is especially useful if users cannot be trusted to play by the rules (always).


MAC-based access control, or hardware address access control means to use the hardware address instead of the IP addresses to base one's firewall rules upon. In other words, we will not be using hostnames (that maps to IP addresses) to base our access control on.

There are two aproaches, the first I like to call "no unprivileged packets beyond this point" whilst I refer to the second as the "MAC check on service request". The first of these methods are less flexible but equally effective and is probably easier to use in cases where users are clasified into either allowed or disallowed whilst the second is much more flexible and allows for different sets of MACs for different services.

Initially I developed the first of these methods on request of our gateway admin who wanted to be a BOFH but didn't know how. Anyway, what we have now is a combination of his previously ineffective firewall, but with at least only certain users being able to abuse that. The second was developed when I myself needed some way to split that same group of users into those who are allowed to use me as a gateway, another group (that happens to include the first) of legitimate network users who is allowed to access services on my machine, and the rest (those who are not allowed to use my machine at all - except for dhcp).

In the "no unprivileged packets" method I basically implemented a pre-emptive strike, implementing a chain that only returns should the MAC be a valid legitimate user. The second implements a chain that accepts the packet should the MAC be legit, or else return. The return part may be argued, the alternative where the packet is dropped where it does not match may also be legit but is slightly less flexible (also less errorprone).

Building the MAC list

To keep things simple, I'm going to assume a list of 3 privileged users that are allowed to use services that cause internet traffic to be generated:


What I show below is a small part of the output for iptables -L -v -n, making use of the "MAC check on request" method:

Chain PRIV_MACS (2 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 ACCEPT     all  --  *      *             MAC 00:0C:F1:6C:CC:7D
    0     0 ACCEPT     all  --  *      *             MAC 00:E0:29:17:7E:78
    0     0 ACCEPT     all  --  *      *             MAC 00:A0:CC:D4:FE:A7

This shows what the chain should look like that does the MAC-check once a service has been requested. The following commands can be used to construct the above ruleset:

iptables -N PRIV_MACS
iptables -A PRIV_MACS -m mac --mac-source 00:0C:F1:6C:CC:7D -j ACCEPT 
iptables -A PRIV_MACS -m mac --mac-source 00:E0:29:17:7E:78 -j ACCEPT 
iptables -A PRIV_MACS -m mac --mac-source 00:A0:CC:D4:FE:A7 -j ACCEPT

In order to use the "no unprivileged packets" method one would change the ACCEPT to RETURN and also add the following command to the previous set:

iptables -A PRIV_MACS -j DROP

DENY can also be used but I like being a BOFH.

Using the MAC list

That should be sufficient to illustrate how to create a single chain with allowable MACs. How to use this is not so obvious. A typical ruleset would look like:

Chain INPUT (policy DROP 37324 packets, 3753K bytes)
 pkts bytes target     prot opt in     out     source               destination         
3491K  676M ACCEPT     all  --  *      *             state RELATED,ESTABLISHED 
27425 1737K ACCEPT     all  --  lo     *             
 3245  174K ACCEPT     tcp  --  eth0   *             tcp flags:0x16/0x02 multiport dports 22,80,53,139,445 
  250 15000 PRIV_MACS  tcp  --  eth0   *             tcp dpt:3128 flags:0x16/0x02 
59695 6496K ACCEPT     udp  --  eth0   *             multiport dports 53,67,123,137,138 
15803  957K ACCEPT     all  --  eth0   *             MAC 00:48:54:89:BC:1D 

Walking through these rules:

  1. A simple and effective rule to quickly match all traffic that has already been checked lower down. There is one issue with this which I cannot find a way around at all.
  2. All traffic on localhost should be allowed, there should not be any reason to firewall that.
  3. Handle the services that can be used by everyone. These would typically be local-only, no/low cost services. In this case ssh (which I would like to be able to use no matter on who's machine I'm working), http (I'm proud of my work, other people may view it), dns (yes, on tcp. Serving local ddns + caching for internet) and then samba (for sharing those darn Windows updates).
  4. I have a proxy set up, but only people that actually pay are allowed to use that. Thus only those who's MAC addresses are in my list is allowed to use port 3128 (aka squid).
  5. These are some udp services that is required such as dns, dhcp, ntp and samba
  6. My desktop machine from which all traffic is accepted. It is under my control after all.

Since I prefer the "check on request method" I'm only going to describe what would be done differently for the "no unprivileged packets" method.

Modifications to use the other scheme

The only thing that would really change are the order of the rules. One would need to first have all the non-MAC related stuff (ie, rule 4 needs to move to the end) and then one would call the PRIV_MACS rule before that.

To see why this is more restrictive than the method described above, consider what happens when you have two MAC lists that need different accesses. The method here would require that the more restrictive MAC list be a subset of the bigger list. Not always the case. Both methods allow for sub-groups by simply calling another MAC list - this should be the last call in the MAC list. The "no unprivileged packets" however does not allow for aggregation of two seperate MAC lists.