I'm trying to implement a way to prevent network scans from my notebook. One of the things I want is to allow arp request to specific hosts, like my gateway.
I added some rules using arptables and they seem to work (at first)

arptables -A OUTPUT -d -j DROP
arptables -A INPUT  -s -j DROP

This is actually blocking arp requests to this host. If I run:

tcpdump -n port not 22 and host (target host)

and run:

arp -d; ping -c 1; arp -n (notebook)

tcpdump shows no incoming packets on the target and arp -n on the notebook show (incomplete)

But if I run nmap -sS on my notebook I get on the target host:

22:21:12.548519 ARP, Request who-has tell, length 46
22:21:12.548655 ARP, Reply is-at xx:xx:xx:xx:xx:xx, length 28
22:21:12.728499 ARP, Request who-has tell, length 46
22:21:12.728538 ARP, Reply is-at xx:xx:xx:xx:xx:xx, length 28

but an arp -n on the notebook still shows incomplete, but the nmap detects the host.
I also tried using nftables and ebtables with no success.

How can I prevent nmap to send arp request and finding the host?


1 Answer 1


I'll complete OP's setup: address on eth0 and a gateway (not really needed) If the setup uses Wifi rather than actual Ethernet, the first method (bridge) won't be available without additional efforts (probably easy if Access Point, very difficult to impossible if not AP).

  • nmap uses a packet socket (type AF_PACKET) to craft ARP requests, rather than using the kernel's network stack dealing with ARP cache and resolution. arping behave similarly (and will be used instead to simplify examples).

  • tcpdump also uses AF_PACKET to capture.

  • By contrast even other special tools such as ping, when they merely use AF_INET, SOCK_DGRAM, IPPROTO_IP or AF_INET, SOCK_RAW, IPPROTO_ICMP rather than AF_PACKET, will be filtered by iptables.

Their methods can be verified using strace -e trace=%network (as root user) on these commands.

As presented in Packet flow in Netfilter and General Networking:

Packet flow in Netfilter and General Networking

AF_PACKET happens before (at ingress) or after (at egress) most of Netfilter's subsystems, ebtables (bridge), arptables or iptables and their equivalent nftables tables: the firewall is bypassed. tcpdump (or nmap) is able to read incoming packets because it captures them before the firewall, nmap is able to send ARP packets because it injects them after the firewall.

So with a standard setup, any packet generated by nmap or any other tool using AF_PACKET can't be filtered with arptables (or iptables).

There are ways to overcome this.

Old method: using a bridge and ebtables

It's bridging, thus in most cases not compatible with Wifi.

For ebtables (or nftables using bridge family) it's usually not a problem: when the ARP or IP packet that couldn't be filtered is turned into an Ethernet frame it re-enters the network stack at an other layer. Now it's within the network stack and will be affected by all the facilities there, including bridge firewall rules created with ebtables (or nftables with a bridge family). Using a bridge thus allows to overcome the firewall bypass.

Create a bridge, set eth0 as bridge port and move the addresses and routes on br0 (of course this should be done by reconfiguring the adequate network tool in use and/or not done remotely because of the temporary loss of connectivity):

ip link add name br0 up type bridge
ip link set dev eth0 master br0
ip addr flush dev eth0
ip addr add brd + dev br0
ip route add default via # not needed for this problem

Then transform the arptables rules into ebtables rules. They'll still use INPUT and OUTPUT because these are the chains between the routing stack and (for lack of better term) the bridge stack.

ebtables -A OUTPUT -p ARP --arp-ip-dst -j DROP
ebtables -A INPUT -p ARP --arp-ip-src -j DROP

One roughly equivalent nftables ruleset could be (to load with nft -f somerulefile.nft):

add table bridge t    # for idempotence
delete table bridge t # for idempotence

table bridge t {
    chain out {
        type filter hook output priority 0; policy accept;
        arp daddr ip drop

    chain in {
        type filter hook input priority 0; policy accept;
        arp saddr ip drop

(Additional filtering to limit the affected interface should probably be added.)

With one of these rules in place, running concurrently two tcpdump, one on br0 one on eth0 like this:

tcpdump -l -n -e -s0 -i br0 arp &
tcpdump -l -n -e -s0 -i eth0 arp &

will show emission on br0 but not on eth0 anymore: the ARP packet which couldn't be blocked when injected is effectively blocked by the bridge layer. If the rules are removed, both interfaces will show traffic. Likewise for the reverse test from remote: the packet will be captured on eth0 but won't reach br0: blocked.

New method: nftables with netdev family and egress hook

: requires Linux kernel >= 5.16 which provides the egress hook, and nftables >= 1.0.1 to use it. ingress has been available since kernel 4.2.

As there's no bridge involved, there's no change of network layout needed, and this will work the same for Ethernet or for Wifi.

In particular this commit presents use cases:

netfilter: Introduce egress hook

Support classifying packets with netfilter on egress to satisfy user requirements such as:

  • outbound security policies for containers (Laura)
  • filtering and mangling intra-node Direct Server Return (DSR) traffic on a load balancer (Laura)
  • filtering locally generated traffic coming in through AF_PACKET, such as local ARP traffic generated for clustering purposes or DHCP
    (Laura; the AF_PACKET plumbing is contained in a follow-up commit)


nftables provides access to additional Netfilter hooks (not described in previous schematic) in the netdev family working at the interface level: ingress and egress. These hooks are near AF_PACKET for ingress and egress (staying fuzzy because implementations details with regard to ingress/egress and capture/injection have some subtleties): egress is able to affect packets injected at AF_PACKET.

Base chains in netdev family tables must be linked to (an) interface(s). Using OP's initial setup, the previous nftables ruleset can be rewritten using the netdev family's syntax like this:

add table netdev t    # for idempotence
delete table netdev t # for idempotence

table netdev t {
    chain out {
        type filter hook egress device eth0 priority 0; policy accept;
        arp daddr ip drop

    chain in {
        type filter hook ingress device eth0 priority 0; policy accept;
        arp saddr ip drop

tcpdump won't capture any injected ARP at egress: they were dropped before. Capture at ingress still happens first: tools relying on AF_PACKET (starting with tcpdump) can still capture them (but the firewall will drop them right after).

Misc: There's also tc which is able to filter AF_PACKET sockets (if the tool doesn't use the option PACKET_QDISC_BYPASS). tc is more difficult to handle. Here's a Q/A with an answer of mine (at the time I wrote it my understanding and overall explanation was less acurate) having a simple example without filter.

  • Note: when the packet is dropped at injection (new method) (rather than later in old method), nmap will be made aware of it (eg: WARNING: eth_send of ARP packet returned -1 rather than expected 42 (errno=105: No buffer space available)). There would be a method still relying on egress to prevent it noticing. If needed just ask. But anyway the person with root access can always figure out what is going on given enough effort.
    – A.B
    Mar 2 at 9:26

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .