pythonpython-3.xlinuxsocketswpa-supplicant

Writing an external program to interface with wpa_supplicant


I need to interact directly with wpa_supplicant from Python. As I understand it one can connect to wpa_supplicant using Unix sockets and wpa_supplicant control interface (https://w1.fi/wpa_supplicant/devel/ctrl_iface_page.html). I wrote a simple program that sends a PING command:

import socket

CTRL_SOCKETS = "/home/victor/Research/wpa_supplicant_python/supplicant_conf"
INTERFACE = "wlx84c9b281aa80"
SOCKETFILE = "{}/{}".format(CTRL_SOCKETS, INTERFACE)

s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
s.connect(SOCKETFILE)
s.send(b'PING')
while 1:
    data = s.recv(1024)
    if data:
        print(repr(data))

But when I run it, wpa_supplicant reports an error:

wlx84c9b281aa80: ctrl_iface sendto failed: 107 - Transport endpoint is not connected

Could someone please provide an example, how you would do a 'scan' and then print 'scan_results'.


Solution

  • Apparently, the type of socket that wpa_supplicant uses (UNIX datagram) does not provide any way for the server to reply. There are a few ways to get around that. wpa_supplicant in particular seems to support replies through a separate socket (found at a path appended at the end of each message).

    Weirdly enough, this seems to be a relatively common practice in Linux: /dev/log seems to work in the same way.

    Here's a program that does what you asked for:

    import socket, os
    from time import sleep
    
    def sendAndReceive(outmsg, csock, ssock_filename):
      '''Sends outmsg to wpa_supplicant and returns the reply'''
    
      # the return socket object can be used to send the data
      # as long as the address is provided
      csock.sendto(str.encode(outmsg), ssock_filename)
    
      (bytes, address) = csock.recvfrom(4096)
      inmsg = bytes.decode('utf-8')
    
      return inmsg
    
    wpasock_file = '/var/run/wpa_supplicant/wlp3s0'
    retsock_file = '/tmp/return_socket'
    
    if os.path.exists(retsock_file):
      os.remove(retsock_file)
    retsock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
    retsock.bind(retsock_file)
    
    replyToScan = sendAndReceive('SCAN', retsock, wpasock_file)
    print(f'SCAN: {replyToScan}')
    
    sleep(5)
    
    replyToScanResults = sendAndReceive('SCAN_RESULTS', retsock, wpasock_file)
    print(f'SCAN_RESULTS: {replyToScanResults}')
    
    retsock.close()
    os.remove(retsock_file)