2

I use the command below to capture the bypass packet on interface ens160 :

tcpdump -i ens160 -w test.pcap

During this time, I use commands below to check if the promiscuous mode is enabled on ens160 :

ifconfig ens160 | grep -i promisc 
ip a show ens160 | grep -i promisc
netstat -i | grep ens160

All output of those command point to promiscuous mode is not enabled on ens160 , but I can still see the packets not belongs to ens160 on test.pcap.

I know that tcpdump default works on promiscuous mode, but during this time the promiscuous mode of ens160 is disabled.

Why can I capture all incoming packet even the promiscuous mode is not enabled ?

1 Answer 1

5

TL;DR

On modern Linux, the interface uses the promiscuity counter to know when its operational state should be promiscuous ( > 0 ) or not ( = 0 ). The PROMISC interface property flag is just one way among others to increase the promiscuity counter by 1.

tcpdump didn't change the interface's PROMISC flag, but did request to receive promiscuous traffic on the raw packet socket on this interface, automatically increasing the promiscuity counter and thus setting the interface in promiscuous mode.

ifconfig, which (on Linux) is obsolete cannot display the promiscuity counter. Nor will netstat -i.

Use instead:

ip -details link show dev ens160

or with JSON and jq:

ip -d -j link show dev ens160 | jq '.[].promiscuity'

It can also be tracked in event mode (but without JSON) among other link changes with:

ip -d monitor link dev ens160

Lengthy answer

On Linux ifconfig is an obsolete command, it should be replaced with ip link and/or ip addr everywhere.

Here it does matter, because the interface property PROMISC that can be set for example with obsolete ifconfig ens160 promisc or with ip link set ens160 promisc on is not changed by tcpdump and is not the one that matters. ifconfig cannot display newer features only available to ip link: here the promiscuity counter.

This answer relied on strace to discover what different could have happened.


Using obsolete ifconfig ens160 promisc which relies on the ioctl(2)-based API, superseded on Linux, but still available for backward compatibility, strace shows:

...
ioctl(4, SIOCGIFFLAGS, {ifr_name="ens160", ifr_flags=IFF_UP|IFF_BROADCAST|IFF_RUNNING|IFF_MULTICAST}) = 0
ioctl(4, SIOCSIFFLAGS, {ifr_name="ens160", ifr_flags=IFF_UP|IFF_BROADCAST|IFF_RUNNING|IFF_PROMISC|IFF_MULTICAST}) = 0
...

Using ip link set dev ens160 promisc on with the superseding (rt)netlink(7) socket API (the single line was split fo readability (and ARPHRD_NETROM is probably a decoding error by strace from the value 0)):

...
sendmsg(3, {msg_name={sa_family=AF_NETLINK, nl_pid=0, nl_groups=00000000},
            msg_namelen=12,
            msg_iov=[{
                iov_base={
                    {len=32, type=RTM_NEWLINK, flags=NLM_F_REQUEST|NLM_F_ACK, seq=1684784897, pid=0},
                    {ifi_family=AF_UNSPEC, ifi_type=ARPHRD_NETROM,
                     ifi_index=if_nametoindex("ens160"),
                     ifi_flags=IFF_PROMISC, ifi_change=0x100}},
                iov_len=32}],
            msg_iovlen=1, msg_controllen=0, msg_flags=0}, 0) = 32
...

both use two different APIs to enable ens160's interface property IFF_PROMISC.

tcpdump itself uses promiscuous mode by default (For the following explanation, I didn't write "enable the interface property" but "uses promiscuous mode"). To prevent it to do it, one should use the -p option:

-p
--no-promiscuous-mode

Don't put the interface into promiscuous mode. Note that the interface might be in promiscuous mode for some other reason; hence, -p cannot be used as an abbreviation for ether host {local-hw-addr} or ether broadcast.

It does so, not by setting the interface property to promiscuous, but by requesting to receive promiscuous traffic on the packet socket it's using on this interface:

...
socket(AF_PACKET, SOCK_RAW, htons(0 /* ETH_P_??? */)) = 4
...
bind(4, {
         sa_family=AF_PACKET,
         sll_protocol=htons(0 /* ETH_P_??? */),
         sll_ifindex=if_nametoindex("ens160"),
         sll_hatype=ARPHRD_NETROM,
         sll_pkttype=PACKET_HOST, sll_halen=0
     }, 20) = 0
getsockopt(4, SOL_SOCKET, SO_ERROR, [0], [4]) = 0
setsockopt(4, SOL_PACKET, PACKET_ADD_MEMBERSHIP, {
               mr_ifindex=if_nametoindex("ens160"),
               mr_type=PACKET_MR_PROMISC,
               mr_alen=0, mr_address=ca:de:8a:4a:1c:38},
           }, 16) = 0
...

The difference is how Linux handles this: it counts each resource usage of the promiscuous mode, and as long as there's one of these resources, the interface operational state is promiscuous. Once the counter drops to zero, the interface operational state is not promiscuous anymore. The reconfiguration itself (triggering actual hardware reconfiguration on actual hardware NIC) happens only when changing from 0 to >0 or from >0 to 0.

What can use such resource?

  • obviously setting the interface property PROMISC
  • setting the interface as bridge port
  • requesting, just like tcpdump did, to receive all/promiscuous traffic on a raw packet socket opened on the interface. This is a stackable use. Each concurrent user will increase the counter by 1.
  • creating MACVLAN interfaces linked to the interface
  • other cases?...

Each of these uses adds to the operational counter when the interface is up. This counter is not available with the older ioctl API, but only with the newer netlink API where new development happens. ifconfig can't display it, while ip -details link can display it. Without -details the property is not shown, because it's rarely needed for anything useful.


Example on a virtual veth interface test0:

# ip link add name test0 up type veth
# ip -details link show dev test0
14: test0@veth0: <NO-CARRIER,BROADCAST,MULTICAST,UP,M-DOWN> mtu 1500 qdisc noqueue state LOWERLAYERDOWN mode DEFAULT group default qlen 1000
    link/ether ca:de:8a:4a:1c:38 brd ff:ff:ff:ff:ff:ff promiscuity 0  allmulti 0 minmtu 68 maxmtu 65535 
    veth addrgenmode eui64 numtxqueues 4 numrxqueues 4 gso_max_size 65536 gso_max_segs 65535 tso_max_size 524280 tso_max_segs 65535 gro_max_size 65536 

where it's seen as: promiscuity 0.

Or using JSON output and jq:

# ip -details -json link show dev test0 | jq '.[].promiscuity'
0

And now:

# ip link set dev test0 promisc on
# ip -d -j link show dev test0 | jq '.[].promiscuity'
1
# ip link add name br0 type bridge
# ip link set dev test0 master br0
# ip -d -j link show dev test0 | jq '.[].promiscuity'
2
# tcpdump -n -i test0 src 192.0.2.2 and dst 198.51.100.2 &
[1] 128657
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on test0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
# ip -d -j link show dev test0 | jq '.[].promiscuity'
3
# tcpdump -n -i test0 src 192.0.2.2 and dst 198.51.100.2 &
[2] 128670
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on test0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
# ip -d -j link show dev test0 | jq '.[].promiscuity'
4

So there are now 4 resource use counts: 2 from kernel and 2 from userland raw packet sockets.

As long as the interface has promiscuity > 0, it will be configured in promiscuous mode. The advantage over a simple flag is that it provides resource accounting, so nothing has to guess if it should reset the interface to non-promiscuous after it's done, or in case it's interrupted before it had the occasion to do cleanup (eg: kill -9 %1), requires the administrator to ponder if the interface should not be in promiscuous mode anymore or should stay (eg: it's a bridge port).

You must log in to answer this question.

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