Python Sockets: Enabling Promiscuous Mode in Linux

2020-02-08 03:32发布

问题:

We know that Python Allows enabling promiscuous mode under Windows through

s.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)

However, The RCVALL_* and SIO_* is only available in windows. Using C socket api, in Linux, one can use :

ethreq.ifr_flags |= IFF_PROMISC;
ioctl(sock, SIOCSIFFLAGS, &ethreq);

or through,

setsockopt(sock, SOL_PACKET, PACKET_ADD_MEMBERSHIP, PACKET_MR_PROMISC)

Is there any option in python socket API that allows us to set promiscuous mode in Linux?

回答1:

Use an AF_NETLINK socket to issue a request to turn on IFF_PROMISC. Python can construct AF_NETLINK sockets on Linux:

>>> from socket import AF_NETLINK, SOCK_DGRAM, socket 
>>> s = socket(AF_NETLINK, SOCK_DGRAM)
>>>

See the example at the end of the netlink(7) manual page for an example of how to issue a netlink request. You can use ctypes (or even struct) to construct the serialized nlmsghdr message to send over the netlink socket. You may also need it to call sendmsg and recvmsg, since Python still doesn't expose these APIs. Alternatively, there are some third-party modules available which expose these two APIs.

Alternatively, you can go the old school route of using ioctl, which sadly turns out to be rather simpler.

First define the ifreq structure using ctypes:

import ctypes

class ifreq(ctypes.Structure):
    _fields_ = [("ifr_ifrn", ctypes.c_char * 16),
                ("ifr_flags", ctypes.c_short)]

Then make a socket to use with the ioctl call:

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

Then copy a couple constant values out of /usr/include since they're not exposed by Python:

IFF_PROMISC = 0x100
SIOCGIFFLAGS = 0x8913
SIOCSIFFLAGS = 0x8914

Create an instance of the ifreq struct and populate it to have the desired effect:

ifr = ifreq()
ifr.ifr_ifrn = "eth4"

Populate the ifr_flags field with an ioctl call so that you don't clobber whatever flags are already set on the interface:

import fcntl

fcntl.ioctl(s.fileno(), SIOCGIFFLAGS, ifr) # G for Get

Add the promiscuous flag:

ifr.ifr_flags |= IFF_PROMISC

And set the flags on the interface:

fcntl.ioctl(s.fileno(), SIOCSIFFLAGS, ifr) # S for Set

To remove the flag, mask it off and set again:

ifr.ifr_flags &= ~IFF_PROMISC
fcntl.ioctl(s.fileno(), SIOCSIFFLAGS, ifr)


回答2:

There is another way I thought of. Maybe not as elegant but seems to work fine.

In linux (with root permissions), one can use :

# ifconfig eth0 promisc
# ifconfig eth0 -promisc

To enable/ disable promisc mode on your interface (eth0 in this case).

So, in python (with root permissions) one could use :

import os
ret =  os.system("ifconfig eth0 promisc")
if ret == 0:
     <Do something>

Comments are welcome on this way of doing it.