jantman / python-wifi-survey-heatmap

A Python application for Linux machines to perform WiFi site surveys and present the results as a heatmap overlayed on a floorplan
GNU Affero General Public License v3.0
375 stars 88 forks source link

BSSID option intermittently not working #19

Closed jantman closed 2 years ago

jantman commented 2 years ago

The --bssid option seems to be broken. When connected to a single specific BSSID, sometimes it will throw a warning about being associated to a completely unrelated BSSID, and sometimes it will not. This appears to be intermittent, almost like a race condition, and I haven't yet been able to figure out the issue... beyond the fact that if you specify --bssid with the BSSID that you're currently (and should) be connected to, it will intermittently show a warning about being connected to a different BSSID even though you're not...

This appears to be a regression introduced by @DL6ER 's #4 migration from iwscan/iwlist to libnl.

jantman commented 2 years ago

I'm still not entirely sure what's going on here, but it looks like the code is also running a scan, and the BSSID detection might occasionally be reporting the BSSID of a completely unrelated AP?

jantman commented 2 years ago

Ok... so, tl;dr: the current code appears to be for scanning only, not finding the currently-associated BSSID. I'm not sure why that's the case, I guess an issue in #4. Looking at e.g. the source code for iw we can see that the nl80211 code in this project is all using phy-based functions, but to find the currently associated BSSID, we need to look at the dev.

My C is really rusty (it was never good to begin with), but I'll see if I can get this figured out.

jantman commented 2 years ago

Ok, I've made a lot of progress on this. After a whole lot of research, I found that pyroute2 makes use of nl80211 and includes some instructions and a decoder script for using strace to capture Netlink socket calls. I had to recompile a very old version of strace for my system (which needed a bunch of hacks to get it to build), but I've captured and decoded the actual send/receive data generated by iw dev <wireless device> link which gives the actual, correct information on whether the device is currently associated to an AP, and if so, the SSID and BSSID.

I'm now starting work on writing Python code using nl80211 to make those same calls.

jantman commented 2 years ago

Note to myself about strace...

I really need to also strace the python process, to compare its communication with iw.

To compile strace 4.12 on modern Arch Linux:

  1. wget https://strace.io/files/4.12/strace-4.12.tar.xz && tar -xvf strace-4.12.tar.xz && cd strace-4.12
  2. As root: cd /usr/include/linux/ && cp btrfs.h btrfs.h.orig && vim btrfs.h and then delete all of struct btrfs_ioctl_defrag_range_args
  3. Apply a patch like https://sourceforge.net/p/strace/mailman/message/35918931/ to the strace source code (in linux/x86_64/arch_sigreturn.c change struct ucontext to ucontext_t).
  4. ./configure && make
  5. Copy the resulting binary at ./strace to somewhere you can find/use it as strace-4.12
  6. As root: mv /usr/include/linux/btrfs.h.orig /usr/include/linux/btrfs.h

You can then wherever/strace-4.12 -e trace=network -x -s 16384 COMMAND where COMMAND is something like iw dev wlp59s0 link or python some-test-script.py

jantman commented 2 years ago

To decode the above output, we can redirect the stdout and stderr of the strace command to a file and then run the following script (which relies on pyroute2) passing the path to the output file as an argument:

#!/usr/bin/python
'''
Decoder for netlink data.

Capture the data using strace <= 4.12 like:

    strace-4.12 -e trace=network -x -s 16384 python main.py 2>&1 | tee strace.out

Then decode the data like:

    python decoder.py strace.out > strace.decoded
'''

import sys
from pprint import pformat
from pyroute2.common import hexdump, load_dump
from pyroute2.netlink.nl80211 import nl80211cmd
import re
from io import StringIO

SEND_RE = re.compile(r'^sendto\(\d+, "([^"]+)".*')
SEND_IOV_RE = re.compile(r'^sendmsg\(.+msg_iov\(\d\)=\[\{"([^"]+)".*')
RCV_RE = re.compile(r'^recvmsg\(.+msg_iov\(\d\)=\[\{"([^"]+)".*')

def decoded(s):
    s += '\n'
    i = StringIO(s)
    msg = nl80211cmd(load_dump(i))
    msg.decode()
    return msg

def main():
    with open(sys.argv[1], 'r') as fh:
        for line in fh.readlines():
            line = line.strip()
            for rex in [SEND_RE, RCV_RE, SEND_IOV_RE]:
                m = rex.match(line)
                if not m:
                    continue
                print(line)
                dec = decoded(m.group(1))
                for x in pformat(dec).split('\n'):
                    print(f'# {x}')
                print('######################')
                break
            else:
                print(f'# {str(line)}')

if __name__ == "__main__":
    main()