From 10dd15b4cfe8530aee63ba39c5dfcd99f082e8fb Mon Sep 17 00:00:00 2001 From: Piotr Gawlowicz Date: Thu, 11 Apr 2019 15:20:49 +0200 Subject: [PATCH 1/4] python support --- python/csi_decoder.py | 321 ++++++++++++++++++++++++++++++++++++++++ python/netlink.py | 49 ++++++ python/parse_log.py | 266 +++++++++++++++++++++++++++++++++ python/rx_csi.py | 68 +++++++++ python/send_frames.py | 64 ++++++++ python/setup_rx.sh | 12 ++ python/setup_tx.sh | 12 ++ python/tcpdump_sniff.sh | 3 + python/utils.py | 72 +++++++++ 9 files changed, 867 insertions(+) create mode 100644 python/csi_decoder.py create mode 100644 python/netlink.py create mode 100755 python/parse_log.py create mode 100755 python/rx_csi.py create mode 100755 python/send_frames.py create mode 100755 python/setup_rx.sh create mode 100755 python/setup_tx.sh create mode 100755 python/tcpdump_sniff.sh create mode 100644 python/utils.py diff --git a/python/csi_decoder.py b/python/csi_decoder.py new file mode 100644 index 00000000..af7a960b --- /dev/null +++ b/python/csi_decoder.py @@ -0,0 +1,321 @@ +import numpy as np + +__author__ = "Piotr Gawlowicz" +__copyright__ = "Copyright (c) 2019 Technische Universität Berlin" +__version__ = "0.1.0" +__email__ = "gawlowicz@tkn.tu-berlin.de" + + +BUF_SIZE = 4096 +triangle = np.array([1, 3, 6]) # What perm should sum to for 1,2,3 antennas + +DTYPE_LENGTH_TLV = np.dtype([ + ("length", np.uint16), +]).newbyteorder('<') + +DTYPE_CSI_HEADER_TLV = np.dtype([ + ("code", np.uint8), + ("timestamp_low", np.uint32), + ("bfee_count", np.uint16), + ("reserved1", np.uint16), + ("Nrx", np.uint8), + ("Ntx", np.uint8), + ("rssiA", np.uint8), + ("rssiB", np.uint8), + ("rssiC", np.uint8), + ("noise", np.int8), + ("agc", np.uint8), + ("antenna_sel", np.uint8), + ("len", np.uint16), + ("fake_rate_n_flags", np.uint8), +]).newbyteorder('<') + +DTYPE_CSI_DATA_TLV = np.dtype(np.ubyte).newbyteorder('<') + + +def get_total_rss(rssi_a, rssi_b, rssi_c, agc): + # Calculates the Received Signal Strength (RSS) in dBm from + # Careful here: rssis could be zero + rssi_mag = 0 + if rssi_a != 0: + rssi_mag = rssi_mag + np.power(10.0, rssi_a/10) + + if rssi_b != 0: + rssi_mag = rssi_mag + np.power(10.0, rssi_b/10) + + if rssi_c != 0: + rssi_mag = rssi_mag + np.power(10.0, rssi_c/10) + + ret = 10*np.log10(rssi_mag) - 44 - agc; + return ret + + +class CsiEntry(object): + """docstring for CsiEntry""" + def __init__(self): + super(CsiEntry, self).__init__() + self.correct = True + self.code = None + self.bfee_count = None + self.Nrx = None + self.Ntx = None + self.rssiA = None + self.rssiB = None + self.rssiC = None + self.noise = None + self.agc = None + self.antenna_sel = None + self.length = None + self.rate = None + self.rssiA_db = None + self.rssiB_db = None + self.rssiC_db = None + self.csi = None + self.perm = None + self.csi_pwr = None + self.rssi_pwr_db = None + self.rssi_pwr = None + self.scale = None + self.noise_db = None + self.quant_error_pwr = None + self.total_noise_pwr = None + + def __str__(self): + myString = "CSI Entry:\n" + myString += "\t Correct: " + str(self.correct) + "\n" + if not self.correct: + return myString + + myString += "\t Code: " + str(self.code) + "\n" + myString += "\t bfee_count: " + str(self.bfee_count) + "\n" + myString += "\t Ntx: " + str(self.Ntx) + "\n" + myString += "\t Nrx: " + str(self.Nrx) + "\n" + myString += "\t MCS Rate: " + str(self.rate) + "\n" + myString += "\t Rssi A [dB]: " + str(self.rssiA_db) + "\n" + myString += "\t Rssi B [dB]: " + str(self.rssiB_db) + "\n" + myString += "\t Rssi C [dB]: " + str(self.rssiC_db) + "\n" + myString += "\t Total Rssi [dB]: " + str(np.round(self.rssi_pwr_db,2)) + "\n" + myString += "\t Agc: " + str(self.agc) + "\n" + myString += "\t Antenna Sel: " + str(self.antenna_sel) + "\n" + myString += "\t Thermal Noise [dB]: " + str(self.noise_db) + "\n" + myString += "\t Quantization Noise [dB]: " + str(np.round(self.quant_error_pwr,2)) + "\n" + myString += "\t Total Noise [dB]: " + str(np.round(self.total_noise_pwr,2)) + "\n" + myString += "\t Permutation vector: " + str(self.perm) + "\n" + myString += "\t CSI matrix shape: " + str(self.csi.shape) + "\n" + return myString + + +class CsiDecoder(object): + """docstring for CsiDecoder""" + def __init__(self): + super(CsiDecoder).__init__() + + @classmethod + def decode(cls, rawData, debug=False): + csiEntry = CsiEntry() + + lengthRawData = np.copy(rawData[:DTYPE_LENGTH_TLV.itemsize]) + length = np.frombuffer(lengthRawData, dtype=DTYPE_LENGTH_TLV) + length = length["length"][0] + + if debug: + print("") + print("***New CSI message***") + print("NETLINK msg length: ", length) + + if (length == 0): + print("Error: got entry size=0") + csiEntry.correct = False + return csiEntry + + elif (length > BUF_SIZE): + print("Error: got entry size {} > BUF_SIZE={}".format(length, BUF_SIZE)) + csiEntry.correct = False + return csiEntry + + # Read in the entry + startIdx = 4 # length is only uint16 but there is 4 byte aligment + csiRawData = rawData[startIdx:] + header = np.frombuffer(csiRawData[0:DTYPE_CSI_HEADER_TLV.itemsize], dtype=DTYPE_CSI_HEADER_TLV) + csiData = np.frombuffer(csiRawData[DTYPE_CSI_HEADER_TLV.itemsize:], dtype=DTYPE_CSI_DATA_TLV) + + code = header["code"][0] + bfee_count = header["bfee_count"][0] + Nrx = header["Nrx"][0] + Ntx = header["Ntx"][0] + rssiA = header["rssiA"][0] + rssiB = header["rssiB"][0] + rssiC = header["rssiC"][0] + noise = header["noise"][0] + agc = header["agc"][0] + antenna_sel = header["antenna_sel"][0] + length = header["len"][0] + rate = header["fake_rate_n_flags"][0] + + rssiA_db = rssiA - 44 - agc + rssiB_db = rssiB - 44 - agc + rssiC_db = rssiC - 44 - agc + + if debug: + print("Msg header: ", header) + print("code: ", code) + print("bfee_count: ", bfee_count) + print("Ntx: ", Ntx) + print("Nrx: ", Nrx) + print("rssiA: ", rssiA_db) + print("rssiB: ", rssiB_db) + print("rssiC: ", rssiC_db) + print("noise: ", noise) + print("agc : ", agc) + print("antenna_sel : ", antenna_sel) + print("length : ", length) + print("rate: ", rate) + + csiEntry.code = code + csiEntry.bfee_count = bfee_count + csiEntry.Nrx = Nrx + csiEntry.Ntx = Ntx + csiEntry.rssiA = rssiA + csiEntry.rssiB = rssiB + csiEntry.rssiC = rssiC + csiEntry.noise = noise + csiEntry.agc = agc + csiEntry.antenna_sel = antenna_sel + csiEntry.length = length + csiEntry.rate = rate + csiEntry.rssiA_db = rssiA_db + csiEntry.rssiB_db = rssiB_db + csiEntry.rssiC_db = rssiC_db + + calc_len = (30 * (Nrx * Ntx * 8 * 2 + 3) + 7) / 8 + calc_len = np.int(calc_len) + # Check that length matches what it should */ + if (length != calc_len): + print("Wrong CSI matrix size.") + csiEntry.correct = False + return csiEntry + + csiMatrixShape = (Ntx, Nrx, 30) + csiMatrix = np.zeros(shape=csiMatrixShape, dtype=np.complex64) + permShape = (3) + permMatrix = np.zeros(shape=permShape, dtype=np.int) + + # Compute CSI from all this crap + counter = 0 + index = 0 + remainder = 0 + for i in range(30): + index += 3 + remainder = index % 8; + rxAntId = -1 + for j in range(Ntx * Nrx): + idx = np.int(index / 8) + tmpReal = (csiData[idx] >> remainder) | (csiData[idx+1] << (8-remainder)) + tmpReal = np.int8(tmpReal) + tmpImag = (csiData[idx+1] >> remainder) | (csiData[idx+2] << (8-remainder)) + tmpImag = np.int8(tmpImag) + complexCsi = tmpReal + tmpImag * 1j + + txAntId = j % Ntx + if txAntId == 0: + rxAntId += 1 + + csiMatrix[txAntId, rxAntId, i] = complexCsi + index += 16 + counter += 1 + + # Compute the permutation array + permMatrix[0] = ((antenna_sel) & 0x3) + 1 + permMatrix[1] = ((antenna_sel >> 2) & 0x3) + 1; + permMatrix[2] = ((antenna_sel >> 4) & 0x3) + 1; + + if debug: + print("csiMatrix ", csiMatrix.shape) + print("perm", permMatrix) + + if Nrx == 1: + pass # No permuting needed for only 1 antenna + + elif np.sum(permMatrix[:Nrx]) != triangle[Nrx-1]: # matrix does not contain default values + print('WARN ONCE: Found CSI with Nrx={} and invalid perm'.format(Nrx)) + print("rx perm", permMatrix) + print("triangle", triangle) + + else: + permIdxs = permMatrix - 1 + tmpMatrix = np.copy(csiMatrix) + csiMatrix[:,permIdxs[:Nrx],:] = tmpMatrix[:,:,:]; + + # scale CSI + # Calculate the scale factor between normalized CSI and RSSI (mW) + csi_sq = np.multiply(csiMatrix, np.conj(csiMatrix)) + csi_pwr = np.sum(csi_sq) + csi_pwr = np.real(csi_pwr) + + rssi_pwr_db = get_total_rss(rssiA, rssiB, rssiC, agc) + rssi_pwr = np.power(10, rssi_pwr_db/10) + + if debug: + print("csi_pwr: ", csi_pwr) + print("ant_power: ", rssiA, rssiB, rssiC, agc) + print("rssi_pwr [db]: ",rssi_pwr_db) + print("rssi_pwr [linear]: ",rssi_pwr) + + # Scale CSI -> Signal power : rssi_pwr / (mean of csi_pwr) + scale = rssi_pwr / (csi_pwr / 30); + + if debug: + print("scale: ", scale) + + # Thermal noise might be undefined if the trace was + # captured in monitor mode. + # ... If so, set it to -92 + noise_db = noise + if (noise == -127): + noise_db = -92 + + noise_db = np.float(noise_db) + thermal_noise_pwr = np.power(10.0, noise_db/10); + + ''' + Quantization error: the coefficients in the matrices are + 8-bit signed numbers, max 127/-128 to min 0/1. Given that Intel + only uses a 6-bit ADC, I expect every entry to be off by about + +/- 1 (total across real & complex parts) per entry. + + The total power is then 1^2 = 1 per entry, and there are + Nrx*Ntx entries per carrier. We only want one carrier's worth of + error, since we only computed one carrier's worth of signal above. + ''' + quant_error_pwr = scale * (Nrx * Ntx) + # Total noise and error power + total_noise_pwr = thermal_noise_pwr + quant_error_pwr; + + if debug: + print("noise_db: ", noise_db) + print("thermal_noise_pwr: ", thermal_noise_pwr) + print("total_noise_pwr: ", total_noise_pwr) + + # Ret now has units of sqrt(SNR) just like H in textbooks + ret = csiMatrix * np.sqrt(scale / total_noise_pwr); + if Ntx == 2: + ret = ret * np.sqrt(2); + elif Ntx == 3: + # Note: this should be sqrt(3)~ 4.77 dB. But, 4.5 dB is how + # Intel (and some other chip makers) approximate a factor of 3 + # You may need to change this if your card does the right thing. + ret = ret * np.sqrt(np.power(10, 4.5/10)); + + csiMatrix = ret + + csiEntry.csi = csiMatrix + csiEntry.perm = permMatrix + csiEntry.csi_pwr = csi_pwr + csiEntry.rssi_pwr = rssi_pwr + csiEntry.rssi_pwr_db = rssi_pwr_db + csiEntry.scale = scale + csiEntry.noise_db = noise_db + csiEntry.quant_error_pwr = 10*np.log10(quant_error_pwr) + csiEntry.total_noise_pwr = 10*np.log10(total_noise_pwr) + + return csiEntry diff --git a/python/netlink.py b/python/netlink.py new file mode 100644 index 00000000..ce136b7f --- /dev/null +++ b/python/netlink.py @@ -0,0 +1,49 @@ +import os +import socket +import struct + +__author__ = "Piotr Gawlowicz" +__copyright__ = "Copyright (c) 2019 Technische Universität Berlin" +__version__ = "0.1.0" +__email__ = "gawlowicz@tkn.tu-berlin.de" + +# iwl_nl.h +IWL_NL_BUFFER_SIZE = 4096 + +# connector_users.h +CN_NETLINK_USERS = 11 # Highest index + 1 + +# iwl_connector.h +CN_IDX_IWLAGN = (CN_NETLINK_USERS + 0xf) +CN_VAL_IWLAGN = 0x1 + +# define type +if getattr(socket, "NETLINK_CONNECTOR", None) is None: + socket.NETLINK_CONNECTOR = 11 + +def open_netlink_socket(): + # Setup the socket + sock = socket.socket(socket.AF_NETLINK, socket.SOCK_DGRAM, socket.NETLINK_CONNECTOR) + + # Now bind the socket + sock.bind((os.getpid(), CN_IDX_IWLAGN)) + + # 270 is SOL_NETLINK and 1 is NETLINK_ADD_MEMBERSHIP + sock.setsockopt(270, 1, CN_IDX_IWLAGN) + return sock + + +def close_netlink_socket(sock): + sock.close() + + +def recv_from_netlink_socket(sock): + data = sock.recv(IWL_NL_BUFFER_SIZE) + + # decode netlink header + msg_len, msg_type, flags, seq, pid = struct.unpack("=LHHLL", data[:16]) + #print(msg_len, msg_type, flags, seq, pid) + + # get payload + payload = data[32:] + return payload diff --git a/python/parse_log.py b/python/parse_log.py new file mode 100755 index 00000000..7c2ac61b --- /dev/null +++ b/python/parse_log.py @@ -0,0 +1,266 @@ +#!/usr/bin/env python3 + +import logging +import argparse +import numpy as np +import matplotlib as mpl +import matplotlib.pyplot as plt + +__author__ = "Piotr Gawlowicz" +__copyright__ = "Copyright (c) 2019 Technische Universität Berlin" +__version__ = "0.1.0" +__email__ = "gawlowicz@tkn.tu-berlin.de" + + +BUF_SIZE = 4096 +triangle = np.array([1, 3, 6]) # What perm should sum to for 1,2,3 antennas + +DTYPE_LENGTH_TLV = np.dtype([ + ("length", np.uint16), +]).newbyteorder('>') + +DTYPE_CSI_HEADER_TLV = np.dtype([ + ("code", np.uint8), + ("timestamp_low", np.uint32), + ("bfee_count", np.uint16), + ("reserved1", np.uint16), + ("Nrx", np.uint8), + ("Ntx", np.uint8), + ("rssiA", np.uint8), + ("rssiB", np.uint8), + ("rssiC", np.uint8), + ("noise", np.int8), + ("agc", np.uint8), + ("antenna_sel", np.uint8), + ("len", np.uint16), + ("fake_rate_n_flags", np.uint16), +]).newbyteorder('<') + +DTYPE_CSI_DATA_TLV = np.dtype(np.ubyte).newbyteorder('>') + + +def get_total_rss(rssi_a, rssi_b, rssi_c, agc): + # Calculates the Received Signal Strength (RSS) in dBm from + # Careful here: rssis could be zero + rssi_mag = 0 + if rssi_a != 0: + rssi_mag = rssi_mag + np.power(10.0, rssi_a/10) + + if rssi_b != 0: + rssi_mag = rssi_mag + np.power(10.0, rssi_b/10) + + if rssi_c != 0: + rssi_mag = rssi_mag + np.power(10.0, rssi_c/10) + + ret = 10*np.log10(rssi_mag) - 44 - agc; + return ret + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Select wireless interface') + parser.add_argument('--file', + type=str, + default=None, + help='Port on which LtFi receiver publishes recv msg.') + + args = parser.parse_args() + fileName = args.file + + fn = '../matlab/sample_data/log.all_csi.6.7.6' + + with open(fn, 'rb') as inFile: + while True: + # Read the next entry size + rawLength = inFile.read(DTYPE_LENGTH_TLV.itemsize) + if not rawLength: + break + + length = np.frombuffer(rawLength, dtype=DTYPE_LENGTH_TLV) + length = length["length"][0] + print("length: ", length) + + if (length == 0): + print("Error: got entry size=0") + exit(-1) + break + + elif (length > BUF_SIZE): + print("Error: got entry size {} > BUF_SIZE={}".format(length, BUF_SIZE)) + exit(-2); + break + + # Read in the entry + rawData = inFile.read(length) + + header = np.frombuffer(rawData[0:DTYPE_CSI_HEADER_TLV.itemsize], dtype=DTYPE_CSI_HEADER_TLV) + csiData = np.frombuffer(rawData[DTYPE_CSI_HEADER_TLV.itemsize:], dtype=DTYPE_CSI_DATA_TLV) + + print(header) + print(csiData.shape) + code = header["code"][0] + bfee_count = header["bfee_count"][0] + Nrx = header["Nrx"][0] + Ntx = header["Ntx"][0] + rssiA = header["rssiA"][0] + rssiB = header["rssiB"][0] + rssiC = header["rssiC"][0] + noise = header["noise"][0] + agc = header["agc"][0] + antenna_sel = header["antenna_sel"][0] + length = header["len"][0] + rate = header["fake_rate_n_flags"][0] + + rssiAdb = rssiA - 44 - agc; + rssiBdb = rssiB - 44 - agc; + rssiCdb = rssiC - 44 - agc; + + print("code: ", code) + print("bfee_count: ", bfee_count) + print("Nrx: ", Nrx) + print("Ntx: ", Ntx) + print("rssiA: ", rssiAdb) + print("rssiB: ", rssiBdb) + print("rssiC: ", rssiCdb) + print("noise: ", noise) + print("agc : ", agc) + print("antenna_sel : ", antenna_sel) + print("length : ", length) + print("rate: ", rate) + + calc_len = (30 * (Nrx * Ntx * 8 * 2 + 3) + 7) / 8 + calc_len = np.int(calc_len) + # Check that length matches what it should */ + if (length != calc_len): + print("Wrong CSI matrix size.") + + csiMatrixShape = (Ntx, Nrx, 30) + tmpMatrix = np.zeros(shape=(Ntx * Nrx * 30), dtype=np.complex64) + csiMatrix = np.zeros(shape=csiMatrixShape, dtype=np.complex64) + print("csiMatrix ", csiMatrix.shape) + + permShape = (3) + permMatrix = np.zeros(shape=permShape, dtype=np.int) + + # Compute CSI from all this crap + counter = 0 + index = 0 + remainder = 0 + for i in range(30): + index += 3 + remainder = index % 8; + rxAntId = -1 + for j in range(Ntx * Nrx): + idx = np.int(index / 8) + tmpReal = (csiData[idx] >> remainder) | (csiData[idx+1] << (8-remainder)) + tmpReal = np.int8(tmpReal) + tmpImag = (csiData[idx+1] >> remainder) | (csiData[idx+2] << (8-remainder)) + tmpImag = np.int8(tmpImag) + complexCsi = tmpReal + tmpImag * 1j + + txAntId = j % Ntx + if txAntId == 0: + rxAntId += 1 + #print(j, txAntId, rxAntId) + csiMatrix[txAntId, rxAntId, i] = complexCsi + #print(complexCsi) + #input("Press enter...") + index += 16 + counter += 1 + + # Compute the permutation array + permMatrix[0] = ((antenna_sel) & 0x3) + 1 + permMatrix[1] = ((antenna_sel >> 2) & 0x3) + 1; + permMatrix[2] = ((antenna_sel >> 4) & 0x3) + 1; + print("perm", permMatrix) + + if Nrx == 1: + continue # No permuting needed for only 1 antenna + + if np.sum(permMatrix) != triangle[Nrx-1]: + print('WARN ONCE: Found CSI with Nrx={} and invalid perm'.format(Nrx)) + + else: + permIdxs = permMatrix - 1 + tmpMatrix = np.copy(csiMatrix) + csiMatrix[:,permIdxs[:Nrx],:] = tmpMatrix[:,:,:]; + + #print(csiMatrix.shape) + #print(csiMatrix) + #input("Press enter...") + + # scale CSI + # Calculate the scale factor between normalized CSI and RSSI (mW) + csi_sq = np.multiply(csiMatrix, np.conj(csiMatrix)) + csi_pwr = np.sum(csi_sq) + csi_pwr = np.real(csi_pwr) + print("csi_pwr: ", csi_pwr) + + print("ant_power: ", rssiA, rssiB, rssiC, agc) + rssi_pwr = get_total_rss(rssiA, rssiB, rssiC, agc) + print("rssi_pwr: ",rssi_pwr) + rssi_pwr = np.power(10, rssi_pwr/10) + print("rssi_pwr: ",rssi_pwr) + + + # Scale CSI -> Signal power : rssi_pwr / (mean of csi_pwr) + scale = rssi_pwr / (csi_pwr / 30); + print("scale: ", scale) + + # Thermal noise might be undefined if the trace was + # captured in monitor mode. + # ... If so, set it to -92 + noise_db = noise + if (noise == -127): + noise_db = -92 + + noise_db = np.float(noise_db) + print("noise_db: ", noise_db) + + thermal_noise_pwr = np.power(10.0, noise_db/10); + print("thermal_noise_pwr: ", thermal_noise_pwr) + + ''' + Quantization error: the coefficients in the matrices are + 8-bit signed numbers, max 127/-128 to min 0/1. Given that Intel + only uses a 6-bit ADC, I expect every entry to be off by about + +/- 1 (total across real & complex parts) per entry. + + The total power is then 1^2 = 1 per entry, and there are + Nrx*Ntx entries per carrier. We only want one carrier's worth of + error, since we only computed one carrier's worth of signal above. + ''' + quant_error_pwr = scale * (Nrx * Ntx) + # Total noise and error power + total_noise_pwr = thermal_noise_pwr + quant_error_pwr; + print("total_noise_pwr: ", total_noise_pwr) + + # Ret now has units of sqrt(SNR) just like H in textbooks + ret = csiMatrix * np.sqrt(scale / total_noise_pwr); + if Ntx == 2: + ret = ret * np.sqrt(2); + elif Ntx == 3: + # Note: this should be sqrt(3)~ 4.77 dB. But, 4.5 dB is how + # Intel (and some other chip makers) approximate a factor of 3 + # You may need to change this if your card does the right thing. + ret = ret * np.sqrt(np.power(10, 4.5/10)); + + csiMatrix = ret + + if 1: + fig, ax = plt.subplots(figsize=(10,5)) + plt.grid(True, linestyle='--') + plt.title('CSI') + #plt.plot(range(len(ret)), 10*np.log10(np.abs(ret))) + for i in range(Ntx): + for j in range(Nrx): + linestyle = '-' + if i == 1: + linestyle = ':' + if i == 2: + linestyle = '--' + plt.plot(range(30), 20*np.log10(np.abs(csiMatrix[i][j])), label="Antenna {}->{}".format(i,j), linestyle=linestyle) + plt.xlabel('Subcarrier index') + plt.ylabel('SNR [dB]') + plt.legend(prop={'size': 12}) + plt.savefig('csi_tmp.png', bbox_inches='tight') + plt.show() diff --git a/python/rx_csi.py b/python/rx_csi.py new file mode 100755 index 00000000..ec415198 --- /dev/null +++ b/python/rx_csi.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 + +import argparse +import numpy as np +from netlink import open_netlink_socket +from netlink import close_netlink_socket +from netlink import recv_from_netlink_socket +from csi_decoder import CsiEntry, CsiDecoder +from utils import configure_rx_chains + +__author__ = "Piotr Gawlowicz" +__copyright__ = "Copyright (c) 2019 Technische Universität Berlin" +__version__ = "0.1.0" +__email__ = "gawlowicz@tkn.tu-berlin.de" + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Select wireless interface') + parser.add_argument('--rxchains', + type=str, + default="ABC", + help='Which RX chains should be activated, e.g. ABC') + parser.add_argument('--plot', + type=bool, + default=False, + help='Plot CSI to PNG file') + + args = parser.parse_args() + rxChains = args.rxchains + plot = args.plot + + configure_rx_chains(rxChains) + + sock = open_netlink_socket() + while True: + try: + payload = recv_from_netlink_socket(sock) + csiEntry = CsiDecoder.decode(payload) + print(csiEntry) + + if plot and csiEntry.correct: + import matplotlib as mpl + import matplotlib.pyplot as plt + + plot = False # plot only once + fig, ax = plt.subplots(figsize=(10,5)) + plt.grid(True, linestyle='--') + plt.title('CSI') + for i in range(csiEntry.Ntx): + for j in range(csiEntry.Nrx): + linestyle = '-' + if i == 1: + linestyle = ':' + if i == 2: + linestyle = '--' + # add +0.001 to prevent negative + plt.plot(range(30), 20*np.log10(np.abs(csiEntry.csi[i][j] + 0.001)), label="Antenna {}->{}".format(i,j), linestyle=linestyle) + plt.xlabel('Subcarrier index') + plt.ylabel('SNR [dB]') + plt.legend(prop={'size': 12}) + plt.savefig('csi_example.png', bbox_inches='tight') + plt.show() + + except KeyboardInterrupt: + print("Ctrl+C -> Exit") + break + + close_netlink_socket(sock) diff --git a/python/send_frames.py b/python/send_frames.py new file mode 100755 index 00000000..eb44896b --- /dev/null +++ b/python/send_frames.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 + +import argparse +from scapy.all import * +from utils import configure_tx_chains + +__author__ = "Piotr Gawlowicz" +__copyright__ = "Copyright (c) 2019 Technische Universität Berlin" +__version__ = "0.1.0" +__email__ = "gawlowicz@tkn.tu-berlin.de" + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Select wireless interface') + parser.add_argument('--txchains', + type=str, + default="ABC", + help='Which RX chains should be activated, e.g. ABC') + parser.add_argument('--streamNum', + type=int, + default=1, + help='Number of TX streams') + parser.add_argument('--mcs', + type=int, + default=0, + help='MCS index') + parser.add_argument('--size', + type=int, + default=100, + help='Frame size') + parser.add_argument('--count', + type=int, + default=1, + help='Number of frames to send') + parser.add_argument('--interval', + type=float, + default=1, + help='Frame sending interval [s]') + + args = parser.parse_args() + txchains = args.txchains + streamNum = args.streamNum + mcs = args.mcs + size = args.size + count = args.count + interval = args.interval + + configure_tx_chains(txchains, streamNum, mcs) + + rt = RadioTap() + dot11 = Dot11(addr1="00:16:ea:12:34:56", + addr2="00:16:ea:12:34:56", + addr3="ff:ff:ff:ff:ff:ff") + + DOT11_SUBTYPE_QOS_DATA = 0x28 + recv_mac = "00:16:ea:12:34:56" + src_mac = "00:16:ea:12:34:56" + dst_mac = "ff:ff:ff:ff:ff:ff" + ds=0x01 + dot11 = Dot11(type="Data", subtype=DOT11_SUBTYPE_QOS_DATA, addr1=recv_mac, addr2=src_mac, addr3=dst_mac, SC=0x3060, FCfield=ds) + payload = Raw(RandString(size=size)) + frame = rt / dot11 / payload + + sendp(frame, iface="mon0", inter=interval, count=count) diff --git a/python/setup_rx.sh b/python/setup_rx.sh new file mode 100755 index 00000000..dfe608d5 --- /dev/null +++ b/python/setup_rx.sh @@ -0,0 +1,12 @@ +#!/usr/bin/sudo /bin/bash +modprobe -r iwlwifi mac80211 cfg80211 +modprobe iwlwifi connector_log=0x1 +# Setup monitor mode, loop until it works +iwconfig wlan0 mode monitor 2>/dev/null 1>/dev/null +while [ $? -ne 0 ] +do + iwconfig wlan0 mode monitor 2>/dev/null 1>/dev/null +done +ifconfig wlan0 up +iw wlan0 set channel $1 $2 +#ifconfig wlan0 up diff --git a/python/setup_tx.sh b/python/setup_tx.sh new file mode 100755 index 00000000..faa0b63b --- /dev/null +++ b/python/setup_tx.sh @@ -0,0 +1,12 @@ +#!/usr/bin/sudo /bin/bash +modprobe -r iwlwifi mac80211 cfg80211 +modprobe iwlwifi debug=0x40000 +ifconfig wlan0 2>/dev/null 1>/dev/null +while [ $? -ne 0 ] +do + ifconfig wlan0 2>/dev/null 1>/dev/null +done +iw dev wlan0 interface add mon0 type monitor +ifconfig mon0 up +iw mon0 set channel $1 $2 +#ifconfig mon0 up diff --git a/python/tcpdump_sniff.sh b/python/tcpdump_sniff.sh new file mode 100755 index 00000000..38d07fd1 --- /dev/null +++ b/python/tcpdump_sniff.sh @@ -0,0 +1,3 @@ +#!/usr/bin/sudo /bin/bash +iw wlan0 set monitor fcsfail +tcpdump -i wlan0 -s 50 --number -f 'wlan addr1 00:16:ea:12:34:56' diff --git a/python/utils.py b/python/utils.py new file mode 100644 index 00000000..43aa2237 --- /dev/null +++ b/python/utils.py @@ -0,0 +1,72 @@ +__author__ = "Piotr Gawlowicz" +__copyright__ = "Copyright (c) 2019 Technische Universität Berlin" +__version__ = "0.1.0" +__email__ = "gawlowicz@tkn.tu-berlin.de" + + +def configure_tx_chains(txChains, streamNum, mcsIdx): + txChains = txChains.lower() + + RATE_MCS_ANT_A_MSK = 0x04000 + RATE_MCS_ANT_B_MSK = 0x08000 + RATE_MCS_ANT_C_MSK = 0x10000 + RATE_MCS_HT_MSK = 0x00100 + + mask = 0x0 + usedAntNum = 0 + + if "a" in txChains: + mask |= RATE_MCS_ANT_A_MSK + usedAntNum += 1 + if "b" in txChains: + mask |= RATE_MCS_ANT_B_MSK + usedAntNum += 1 + if "c" in txChains: + mask |= RATE_MCS_ANT_C_MSK + usedAntNum += 1 + + mask |= RATE_MCS_HT_MSK + + if streamNum > usedAntNum: + print("Cannot use {} streams with {} antennas".format(streamNum, usedAntNum)) + print("Set stream num to {}".format(usedAntNum)) + streamNum = usedAntNum + + mcsMask = mcsIdx + if streamNum == 2: + mcsMask += 8 + elif streamNum == 3: + mcsMask += 16 + + mask |= mcsMask + + mask = "0x{:05x}".format(mask) + print("Set TX mask: ", mask) + + filePath = "/sys/kernel/debug/iwlwifi/0000:02:00.0/iwldvm/debug/monitor_tx_rate" + f = open(filePath, 'w') + f.write(mask) + f.close() + + +def configure_rx_chains(rxChains): + rxChains = rxChains.lower() + mask = 0x0 + aMask = 0x1 + bMask = 0x2 + cMask = 0x4 + + if "a" in rxChains: + mask |= aMask + if "b" in rxChains: + mask |= bMask + if "c" in rxChains: + mask |= cMask + + mask = "0x{:01x}".format(mask) + print("Set RX chain mask: ", mask) + + filePath = "/sys/kernel/debug/iwlwifi/0000:02:00.0/iwldvm/debug/rx_chains_msk" + f = open(filePath, 'w') + f.write(mask) + f.close() From 3a237a7b0c5a53e6bd37fd8085720545e8ff3ad0 Mon Sep 17 00:00:00 2001 From: Piotr Gawlowicz Date: Thu, 11 Apr 2019 15:29:10 +0200 Subject: [PATCH 2/4] add readme file --- python/readme.txt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 python/readme.txt diff --git a/python/readme.txt b/python/readme.txt new file mode 100644 index 00000000..90270416 --- /dev/null +++ b/python/readme.txt @@ -0,0 +1,16 @@ +1. Receiver: +1.1 Setup receiver: + sudo ./setup_rx.sh 5 HT20 + +1.2 Sniff packets using tcpdump: + sudo ./tcpdump_sniff.sh + +1.3 Receive CSI: + sudo ./rx_csi.py --rxchains "abc" + +2. Transmitter +2.1 Setup transmitter: + sudo ./setup_tx.sh 5 HT20 + +2.1 Send frames: + sudo ./send_frames.py --txchains "abc" --streamNum 1 --mcs 0 --count 10 --size 1000 \ No newline at end of file From 72f97f1c538d55bdfdba8d42d30b477d2d883e5e Mon Sep 17 00:00:00 2001 From: Piotr Gawlowicz Date: Mon, 15 Apr 2019 12:22:55 +0200 Subject: [PATCH 3/4] fix bug --- python/csi_decoder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/csi_decoder.py b/python/csi_decoder.py index af7a960b..2ae24e0f 100644 --- a/python/csi_decoder.py +++ b/python/csi_decoder.py @@ -27,7 +27,7 @@ ("agc", np.uint8), ("antenna_sel", np.uint8), ("len", np.uint16), - ("fake_rate_n_flags", np.uint8), + ("fake_rate_n_flags", np.uint16), ]).newbyteorder('<') DTYPE_CSI_DATA_TLV = np.dtype(np.ubyte).newbyteorder('<') From 04b8358b7ca62667b61127d8964dac5b214837d4 Mon Sep 17 00:00:00 2001 From: Piotr Gawlowicz Date: Mon, 15 Apr 2019 12:25:24 +0200 Subject: [PATCH 4/4] update readme --- python/readme.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/readme.txt b/python/readme.txt index 90270416..d180b617 100644 --- a/python/readme.txt +++ b/python/readme.txt @@ -13,4 +13,4 @@ sudo ./setup_tx.sh 5 HT20 2.1 Send frames: - sudo ./send_frames.py --txchains "abc" --streamNum 1 --mcs 0 --count 10 --size 1000 \ No newline at end of file + sudo ./send_frames.py --txchains "abc" --streamNum 1 --mcs 0 --count 10 --size 1000 --interval 0.1