文章目录
Linux CSI Tool 完整使用说明(权威版 已成功测试)
本文涉及到的所有文件均在以下百度网盘链接的压缩包内,请大家自取
链接: https://pan.baidu.com/s/1I67ex4KwFnItlyFCoWlQoQ
提取码: ep8b
相关链接:
- 官方网站:https://dhalperi.github.io/linux-80211n-csitool/
- 参考网站:https://blog.csdn.net/u014645508/article/details/81359409、https://blog.csdn.net/u014645508/article/details/82993718
1. 硬件设备
- 电脑设备推荐ThinkPad X201,推荐购买链接:https://item.taobao.com/item.htm?_u=v3mfohc203a6&id=575205451871
- 网卡务必使用Intel 5300网卡,推荐购买链接(附赠天线):https://item.taobao.com/item.htm?spm=a1z09.2.0.0.48192e8dEszrp2&id=572509799776&_u=v3mfohc2b41c
2. 刷BIOS & 拆卸替换网卡
由于ThinkPad X201电脑的BIOS版本比较旧,因此我之前直接换装网卡之后在BIOS启动阶段就出错了,提示无法识别的硬件设备。因此我们需要在换装网卡之前利用Win PE系统刷一遍BIOS。
-
准备一个2G左右的U盘,准备PE系统的安装程序:WePE_32_V2.1.exe(压缩包内)
-
运行该程序,根据程序的指示将U盘刷成PE系统的启动盘。
-
准备 ThinkPad X201 的 BIOS 包:ThinkPad X201_6quj10us_SLIC21_no_whitelist.zip(压缩包内)
-
将BIOS包复制到U盘名为“微软PE工具箱”的分区内。
-
接下来将U盘插入ThinkPad X201内,启动电源,在出现初始化界面时按ThinkVantage按钮,然后出现选择菜单时按F12,在启动菜单中选择U盘。
-
等待进入U盘PE系统,在“微软PE工具箱”分区内解压之前下载的BIOS包,建议选择64位的文件夹(如果64bit刷BIOS失败了用32bit也行),然后双击“WinPhlash64.exe”运行,直接点击flash bios即可。
-
等待刷机完成后关闭电脑,接下来是拆卸和换装网卡步骤。
-
切断笔记本电源,拔出笔记本电池,切勿带电操作!
-
用十字螺丝刀拧开笔记本背面的螺丝(拧开带有键盘、触摸板图标的螺丝即可)
-
拧完螺丝后轻轻就可以拆开笔记本的键盘和触摸板。
-
原先的网卡在右下方的位置,我们需要用螺丝刀拧开螺丝后才能移除原有网卡,然后才能插入5300网卡。
-
由于一般天线是放在笔记本外侧的,所以需要拆除右下方的声卡板来为接线提供空间。
-
安装完毕后如下图所示:
3. 操作系统
操作系统推荐Ubuntu 14.06 LTS,内核版本为3.13,不过现在好像只能下载到4.4的内核版本(镜像链接:https://mirrors.zju.edu.cn/ubuntu-releases/)
我用的是3.13内核版本的Ubuntu 14.06,问题应该不大,因为后面要重新编译过内核。
怎么安装系统就不说了。相信大家都没有问题。
另外,如果你能在安装系统的界面成功检测到周围的WIFI并且能连接成功,那说明你的BIOS刷机成功了,系统“认识”这款网卡。
不过还是建议大家在连着有线网的情况下安装,包括后续的安装操作。
4. 内核编译、驱动、固件配置
操作系统安装完毕后,重启进入。刚进入系统时可能会提示你是否要升级到16.04,点击否就行了。包括提示要更新软件等都略过吧。
-
安装相关依赖
-
更新源
sudo apt-get update
-
下载安装依赖包
sudo apt-get -y install git-core kernel-package fakeroot build-essential ncurses-dev sudo apt-get -y install libnl-dev libssl-dev sudo apt-get -y install iw
-
-
下载、编译内核
-
准备 intel-5300-csi-github-master.tar.gz
压缩包内有:intel-5300-csi-github-master.tar.gz,如果觉得百度网盘下载速度慢,这里提供一位博主的:https://download.csdn.net/download/u014645508/10579514
-
解压、编译
按顺序一步一步在终端执行以下代码
cd ~ tar -xvf intel-5300-csi-github-master.tar.gz cd intel-5300-csi-github-master make oldconfig # 一直按回车 make menuconfig # 在弹出的窗口选择Save,再Exit,一定要save一遍,而不是直接退出。另外可能会因为终端窗口比较小无法显示完全而报错 make -j4 # 编译内核一直都比较慢,大概半小时到一小时 sudo make install modules_install # 安装kernel module,大约15分钟 sudo make install sudo make install modules_install # 再次安装内核模块(保险起见,一定要执行) sudo mkinitramfs -o /boot/initrd.img-`cat include/config/kernel.release` `cat include/config/kernel.release` make headers_install sudo mkdir /usr/src/linux-headers-`cat include/config/kernel.release` sudo cp -rf usr/include /usr/src/linux-headers-`cat include/config/kernel.release`/include
-
添加刚刚编译过的内核(4.2.0版本)至启动项
cd /etc/default sudo vi grub
注释这一行
GRUB_HIDDEN_TIMEOUT=0
-
更新grub
sudo update-grub
-
重启电脑,一定要在启动选项中选择4.2的内核版本进入。
-
-
替换固件
按顺序在终端执行以下代码
cd ~ git clone https://github.com/dhalperi/linux-80211n-csitool-supplementary.git for file in /lib/firmware/iwlwifi-5000-*.ucode; do sudo mv $file $file.orig; done sudo cp linux-80211n-csitool-supplementary/firmware/iwlwifi-5000-2.ucode.sigcomm2010 /lib/firmware/ sudo ln -s iwlwifi-5000-2.ucode.sigcomm2010 /lib/firmware/iwlwifi-5000-2.ucode
到此,5300网卡的驱动以及CSI收发包工具都已经配置完毕。接下来分别介绍发包和收包的操作。
5. 具体使用
5.1 共同准备
在终端按顺序执行以下代码
cd ~
sudo apt-get install libpcap-dev
git clone https://github.com/dhalperi/lorcon-old.git
cd lorcon-old
./configure
make
sudo make install
5.2 CSI发送端
-
编译发送代码
cd ~ cd linux-80211n-csitool-supplementary/injection/ make
-
执行初始化脚本 inject.sh(压缩包内:inject.sh)
在执行之前建议先查看无线网卡接口名称,一般情况下是 wlan0
iwconfig
如图所示:
执行脚本即参数配置:sudo bash ./inject.sh wlan0 64 HT20
参数解释:第一个参数是无线网卡接口名称,一般是wlan0,第二个参数是信道编号,建议64,第三个是OFDM下的HT20模式
inject.sh代码:
#!/usr/bin/sudo /bin/bash sudo service network-manager stop WLAN_INTERFACE=$1 SLEEP_TIME=2 modprobe iwlwifi debug=0x40000 if [ "$#" -ne 3 ]; then echo "Going to use default settings!" chn=64 bw=HT20 else chn=$2 bw=$3 fi sleep $SLEEP_TIME ifconfig $WLAN_INTERFACE 2>/dev/null 1>/dev/null while [ $? -ne 0 ] do ifconfig $WLAN_INTERFACE 2>/dev/null 1>/dev/null done sleep $SLEEP_TIME echo "Add monitor mon0....." iw dev $WLAN_INTERFACE interface add mon0 type monitor sleep $SLEEP_TIME echo "Bringing $WLAN_INTERFACE down....." ifconfig $WLAN_INTERFACE down while [ $? -ne 0 ] do ifconfig $WLAN_INTERFACE down done sleep $SLEEP_TIME echo "Bringing mon0 up....." ifconfig mon0 up while [ $? -ne 0 ] do ifconfig mon0 up done sleep $SLEEP_TIME echo "Set channel $chn $bw....." iw mon0 set channel $chn $bw
-
发送数据
echo 0x1c113 | sudo tee `sudo find /sys -name monitor_tx_rate` cd ~ cd linux-80211n-csitool-supplementary/injection/ sudo ./random_packets 1000000000 100 1 1000
第一行的参数配置如果有个人需求可以参考:CSI Tool安装使用讲解
不过个人建议就这样就行了,毕竟这个工具搭建很麻烦。
random_packets的参数解释:第一个参数是累计发包数量,第二个参数是包的大小,第三个参数1代表injection MAC,用1就可以了,最后一个参数代表每隔1000微秒发一次包,即一秒发1000个包。
5.3 CSI接收端
-
编译接收代码
cd ~ cd linux-80211n-csitool-supplementary/netlink/ make
-
执行初始化脚本 monitor.sh (压缩包内:monitor.sh)
注意:一定要采用这个脚本,其他博客上的脚本基本缺少了第2、3行内容,否则收不到包的!我尝试了好几次,才找到办法。如果发现此脚本运行后提示找不到对应的wlan接口也可尝试另外一个,感谢粉丝“qq_45884215”提供的资源
sudo bash ./monitor.sh wlan0 64 HT20
信道编号要和发送端的一样
monitor.sh代码
#!/usr/bin/sudo /bin/bash sudo modprobe -r iwlwifi mac80211 sudo modprobe iwlwifi connector_log=0x1 sudo service network-manager stop SLEEP_TIME=2 WLAN_INTERFACE=$1 if [ "$#" -ne 3 ]; then echo "Going to use default settings!" chn=64 bw=HT20 else chn=$2 bw=$3 fi echo "Bringing $WLAN_INTERFACE down......" ifconfig $WLANINTERFACE down while [ $? -ne 0 ] do ifconfig $WLAN_INTERFACE down done sleep $SLEEP_TIME echo "Set $WLAN_INTERFACE into monitor mode......" iwconfig $WLAN_INTERFACE mode monitor while [ $? -ne 0 ] do iwconfig $WLAN_INTERFACE mode monitor done sleep $SLEEP_TIME echo "Bringing $WLAN_INTERFACE up......" ifconfig $WLAN_INTERFACE up while [ $? -ne 0 ] do ifconfig $WLAN_INTERFACE up done sleep $SLEEP_TIME echo "Set channel $chn $bw..." iw $WLAN_INTERFACE set channel $chn $bw
monitor.sh 备用代码
-
执行收包程序
cd ~ cd linux-80211n-csitool-supplementary/netlink/ sudo ./log_to_file temp # temp是保存数据的文件名,强烈建议文件名改为dat后缀
由于原生的代码只能不停的收包,因此我编写了一个可以收包n秒以后自动停止的C代码(压缩包内:log_to_file.c):
使用方法如下:
cd ~ cd linux-80211n-csitool-supplementary/netlink/ sudo ./log_to_file temp 3
参数解释:temp是保存数据的文件名,3代表从检测到CSI包之后收3秒,然后退出程序。如果发送端每秒发送1000个包,那么在不丢包的情况下可以收到3000个包。如下图:
log_to_file.c 文件代码:
/* * (c) 2008-2011 Daniel Halperin <dhalperi@cs.washington.edu> */ #include "iwl_connector.h" #include <stdio.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <linux/netlink.h> #define MAX_PAYLOAD 2048 #define SLOW_MSG_CNT 100 int sock_fd = -1; // the socket FILE *out = NULL; void check_usage(int argc, char **argv); FILE *open_file(char *filename, char *spec); void caught_signal(int sig); void exit_program(int code); void exit_program_err(int code, char *func); void exit_program_with_alarm(int sig); int main(int argc, char **argv) { /* Local variables */ struct sockaddr_nl proc_addr, kern_addr; // addrs for recv, send, bind struct cn_msg *cmsg; char buf[4096]; int ret; unsigned short l, l2; int count = 0; /* Initialize signal*/ signal(SIGALRM, exit_program_with_alarm); /* Make sure usage is correct */ check_usage(argc, argv); /* Open and check log file */ out = open_file(argv[1], "w"); /* Setup the socket */ sock_fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR); if (sock_fd == -1) exit_program_err(-1, "socket"); /* Initialize the address structs */ memset(&proc_addr, 0, sizeof(struct sockaddr_nl)); proc_addr.nl_family = AF_NETLINK; proc_addr.nl_pid = getpid(); // this process' PID proc_addr.nl_groups = CN_IDX_IWLAGN; memset(&kern_addr, 0, sizeof(struct sockaddr_nl)); kern_addr.nl_family = AF_NETLINK; kern_addr.nl_pid = 0; // kernel kern_addr.nl_groups = CN_IDX_IWLAGN; /* Now bind the socket */ if (bind(sock_fd, (struct sockaddr *)&proc_addr, sizeof(struct sockaddr_nl)) == -1) exit_program_err(-1, "bind"); /* And subscribe to netlink group */ { int on = proc_addr.nl_groups; ret = setsockopt(sock_fd, 270, NETLINK_ADD_MEMBERSHIP, &on, sizeof(on)); if (ret) exit_program_err(-1, "setsockopt"); } /* Set up the "caught_signal" function as this program's sig handler */ signal(SIGINT, caught_signal); /* Poll socket forever waiting for a message */ while (1) { /* Receive from socket with infinite timeout */ ret = recv(sock_fd, buf, sizeof(buf), 0); if (ret == -1) exit_program_err(-1, "recv"); /* Pull out the message portion and print some stats */ cmsg = NLMSG_DATA(buf); if (count % SLOW_MSG_CNT == 0) printf("received %d bytes: counts: %d id: %d val: %d seq: %d clen: %d\n", cmsg->len, count, cmsg->id.idx, cmsg->id.val, cmsg->seq, cmsg->len); /* Log the data to file */ l = (unsigned short)cmsg->len; l2 = htons(l); fwrite(&l2, 1, sizeof(unsigned short), out); ret = fwrite(cmsg->data, 1, l, out); ++count; if (count == 1) { /* Set alarm */ alarm((*argv[2] - '0')); } if (ret != l) exit_program_err(1, "fwrite"); } exit_program(0); return 0; } void check_usage(int argc, char **argv) { if (argc != 3) { fprintf(stderr, "Usage: %s <output_file> <time>\n", argv[0]); exit_program(1); } } FILE *open_file(char *filename, char *spec) { FILE *fp = fopen(filename, spec); if (!fp) { perror("fopen"); exit_program(1); } return fp; } void caught_signal(int sig) { fprintf(stderr, "Caught signal %d\n", sig); exit_program(0); } void exit_program(int code) { if (out) { fclose(out); out = NULL; } if (sock_fd != -1) { close(sock_fd); sock_fd = -1; } exit(code); } void exit_program_err(int code, char *func) { perror(func); exit_program(code); } void exit_program_with_alarm(int sig) { exit_program(0); }
6. CSI数据处理(Matlab & Python)
Matlab和Python都可以对数据进行处理。个人推荐Matlab,更权威,Python的处理结果貌似和Matlab不一样。
这两种方式都可以将dat格式的原数据转化为csv格式。因为我采用的收发都是3根天线,每根天线都有30个子载波,一秒钟3000个包,所以最后csv的数据维度是3000 * (30 * 3 * 3)即3000 * 270的大小。
6.1 Matlab方式
-
下载Matlab处理dat文件的代码包。(压缩包内:01 dat_to_csi_mat.zip)
-
运行代码包内的data_to_csi.m脚本。
data_to_csi.m:
csi_file = read_bf_file('temp.dat'); row = size(csi_file, 1); temp = []; for k = 1:row csi = get_scaled_csi(csi_file{k}); csi = csi(:)'; for m = 1:length(csi) csi(m) = abs(csi(m)); end temp = [temp; csi]; end writematrix(temp, 'output.csv');
6.2 Python方式
直接运行以下Python代码,注意文件路径等参数修改为自己的
import numpy as np
import math
class Bfee:
def __init__(self):
pass
@staticmethod
def from_file(filename, model_name_encode="shift-JIS"):
with open(filename, "rb") as f:
from functools import reduce
# reduce(函数,list),将list中元素依次累加
array = bytes(reduce(lambda x, y: x+y, list(f)))
bfee = Bfee()
# vmd.current_index = 0
bfee.file_len = len(array)
bfee.dicts = []
bfee.all_csi = []
# vmd.timestamp_low0 = int.from_bytes(array[3:7], byteorder='little', signed=False)
# array = array[3:]
# %% Initialize variables
# ret = cell(ceil(len/95),1); # % Holds the return values - 1x1 CSI is 95 bytes big, so this should be upper bound
cur = 0 # % Current offset into file
count = 0 # % Number of records output
broken_perm = 0 # % Flag marking whether we've encountered a broken CSI yet
# % What perm should sum to for 1,2,3 antennas
triangle = [0, 1, 3]
while cur < (bfee.file_len - 3):
# % Read size and code
# % 将文件数据读取到维度为 sizeA 的数组 A 中,并将文件指针定位到最后读取的值之后。fread 按列顺序填充 A。
bfee.field_len = int.from_bytes(
array[cur:cur+2], byteorder='big', signed=False)
bfee.code = array[cur+2]
cur = cur+3
# there is CSI in field if code == 187,If unhandled code skip (seek over) the record and continue
if bfee.code == 187:
pass
else:
# % skip all other info
cur = cur + bfee.field_len - 1
continue
# get beamforming or phy data
if bfee.code == 187:
count = count + 1
bfee.timestamp_low = int.from_bytes(
array[cur:cur+4], byteorder='little', signed=False)
bfee.bfee_count = int.from_bytes(
array[cur+4:cur+6], byteorder='little', signed=False)
bfee.Nrx = array[cur+8]
bfee.Ntx = array[cur+9]
bfee.rssi_a = array[cur+10]
bfee.rssi_b = array[cur+11]
bfee.rssi_c = array[cur+12]
bfee.noise = array[cur+13] - 256
bfee.agc = array[cur+14]
bfee.antenna_sel = array[cur+15]
bfee.len = int.from_bytes(
array[cur+16:cur+18], byteorder='little', signed=False)
bfee.fake_rate_n_flags = int.from_bytes(
array[cur+18:cur+20], byteorder='little', signed=False)
bfee.calc_len = (
30 * (bfee.Nrx * bfee.Ntx * 8 * 2 + 3) + 6) / 8
bfee.csi = np.zeros(
shape=(30, bfee.Nrx, bfee.Ntx), dtype=np.dtype(np.complex))
bfee.perm = [1, 2, 3]
bfee.perm[0] = ((bfee.antenna_sel) & 0x3)
bfee.perm[1] = ((bfee.antenna_sel >> 2) & 0x3)
bfee.perm[2] = ((bfee.antenna_sel >> 4) & 0x3)
cur = cur + 20
# get payload
payload = array[cur:cur+bfee.len]
cur = cur + bfee.len
index = 0
# Check that length matches what it should
if (bfee.len != bfee.calc_len):
print("MIMOToolbox:read_bfee_new:size",
"Wrong beamforming matrix size.")
# Compute CSI from all this crap :
# import struct
for i in range(30):
index += 3
remainder = index % 8
for j in range(bfee.Nrx):
for k in range(bfee.Ntx):
real_bin = bytes([(payload[int(index / 8)] >> remainder) | (
payload[int(index/8+1)] << (8-remainder)) & 0b11111111])
real = int.from_bytes(
real_bin, byteorder='little', signed=True)
imag_bin = bytes([(payload[int(index / 8+1)] >> remainder) | (
payload[int(index/8+2)] << (8-remainder)) & 0b11111111])
imag = int.from_bytes(
imag_bin, byteorder='little', signed=True)
tmp = np.complex(float(real), float(imag))
bfee.csi[i, j, k] = tmp
index += 16
# % matrix does not contain default values
if sum(bfee.perm) != triangle[bfee.Nrx-1]:
print('WARN ONCE: Found CSI (', filename, ') with Nrx=',
bfee.Nrx, ' and invalid perm=[', bfee.perm, ']\n')
else:
temp_csi = np.zeros(
bfee.csi.shape, dtype=np.dtype(np.complex))
# bfee.csi[:,bfee.perm[0:bfee.Nrx],:] = bfee.csi[:,0:bfee.Nrx,:]
for r in range(bfee.Nrx):
temp_csi[:, bfee.perm[r], :] = bfee.csi[:, r, :]
bfee.csi = temp_csi
# 将类属性导出为dict,并返回
bfee_dict = {}
bfee_dict['timestamp_low'] = bfee.timestamp_low
bfee_dict['bfee_count'] = bfee.bfee_count
bfee_dict['Nrx'] = bfee.Nrx
bfee_dict['Ntx'] = bfee.Ntx
bfee_dict['rssi_a'] = bfee.rssi_a
bfee_dict['rssi_b'] = bfee.rssi_b
bfee_dict['rssi_c'] = bfee.rssi_c
bfee_dict['noise'] = bfee.noise
bfee_dict['agc'] = bfee.agc
bfee_dict['antenna_sel'] = bfee.antenna_sel
bfee_dict['perm'] = bfee.perm
bfee_dict['len'] = bfee.len
bfee_dict['fake_rate_n_flags'] = bfee.fake_rate_n_flags
bfee_dict['calc_len'] = bfee.calc_len
bfee_dict['csi'] = bfee.csi
bfee.dicts.append(bfee_dict)
bfee.all_csi.append(bfee.csi)
return bfee
def db(X, U):
R = 1
if 'power'.startswith(U):
assert X >= 0
else:
X = math.pow(abs(X), 2) / R
return (10 * math.log10(X) + 300) - 300
def dbinv(x):
return math.pow(10, x / 10)
def get_total_rss(csi_st):
# Careful here: rssis could be zero
rssi_mag = 0
if csi_st['rssi_a'] != 0:
rssi_mag = rssi_mag + dbinv(csi_st['rssi_a'])
if csi_st['rssi_b'] != 0:
rssi_mag = rssi_mag + dbinv(csi_st['rssi_b'])
if csi_st['rssi_c'] != 0:
rssi_mag = rssi_mag + dbinv(csi_st['rssi_c'])
return db(rssi_mag, 'power') - 44 - csi_st['agc']
def get_scale_csi(csi_st):
# Pull out csi
csi = csi_st['csi']
# print(csi.shape)
# print(csi)
# Calculate the scale factor between normalized CSI and RSSI (mW)
csi_sq = np.multiply(csi, np.conj(csi)).real
csi_pwr = np.sum(csi_sq, axis=0)
csi_pwr = csi_pwr.reshape(1, csi_pwr.shape[0], -1)
rssi_pwr = dbinv(get_total_rss(csi_st))
scale = rssi_pwr / (csi_pwr / 30)
if csi_st['noise'] == -127:
noise_db = -92
else:
noise_db = csi_st['noise']
thermal_noise_pwr = dbinv(noise_db)
quant_error_pwr = scale * (csi_st['Nrx'] * csi_st['Ntx'])
total_noise_pwr = thermal_noise_pwr + quant_error_pwr
ret = csi * np.sqrt(scale / total_noise_pwr)
if csi_st['Ntx'] == 2:
ret = ret * math.sqrt(2)
elif csi_st['Ntx'] == 3:
ret = ret * math.sqrt(dbinv(4.5))
return ret
written_data = ''
bfee = Bfee.from_file('temp.dat', model_name_encode="gb2312")
for k in range(len(bfee.all_csi)):
csi = get_scale_csi(bfee.dicts[k])
for a in range(30):
for b in range(3):
for c in range(3):
temp = abs(csi[a][b][c])
written_data += str(temp) + ','
written_data = written_data[: len(written_data) - 1] + '\n'
written_data = written_data[: len(written_data) - 1]
file = open('output.csv', mode='w')
file.write(written_data)
file.close()