文章目录
一、文件结构
├── 3rdParty
│ └── wpa_supplicant
│ ├── CONTRIBUTIONS
│ ├── COPYING
│ ├── README
│ └── src
│ └── drivers
│ └── nl80211_copy.h
├── build
│ ├── beamforming_on_connected.sh
│ ├── build.sh
│ └── Makefile
├── common
│ └── include
│ ├── dot11.h
│ ├── netlink
│ .............................(netlink相关头文件)
├── doc
└── src
├── lib
│ ├── libnl-3.so
│ ├── libnl-3.so.200
│ ├── libnl-3.so.200.20.0
│ ├── libnl-genl-3.so
│ ├── libnl-genl-3.so.200
│ ├── libnl-genl-3.so.200.20.0
│ ├── managed
│ │ ├── PrsManagedIface.cpp
│ │ └── PrsManagedIface.h
│ ├── PrsDbg.cpp
│ ├── PrsDbg.h
│ ├── PrsDeviceAccessIface.h
│ ├── PrsVendorCommand.cpp
│ ├── PrsVendorCommand.h
│ ├── PrsVendorEndian.cpp
│ ├── PrsVendorEndian.h
│ ├── PrsVendorEventFile.cpp
│ ├── PrsVendorEventFile.h
│ ├── PrsVendorInterface.h
│ ├── PrsVendorInterfaceLinux.cpp
│ ├── PrsVendorInterfaceLinux.h
│ ├── PrsVendorLib.cpp
│ └── PrsVendorLib.h
└── sample
└── linux
├── Sample.cpp
├── SampleEventListener.cpp
├── SampleEventListener.h
├── SampleFileWriterEventListener.cpp
├── SampleFileWriterEventListener.h
├── Sample.h
├── SampleJsonEventListener.cpp
├── SampleJsonEventListener.h
├── SampleSink.cpp
└── SampleSink.h
二、makefile 结构
PREFIX ?= /usr
SBINDIR ?= $(PREFIX)/sbin
MANDIR ?= $(PREFIX)/share/man
MKDIR ?= mkdir -p
INSTALL ?= install
CXXFLAGS += -std=c++11 -Wall -Wundef -Wno-trigraphs -fno-strict-aliasing -fno-common -Werror-implicit-function-declaration
CC = /opt/toolchain-aarch64_cortex-a53_gcc-5.2.0_musl-1.1.16/bin/aarch64-openwrt-linux-gcc
CXX = /opt/toolchain-aarch64_cortex-a53_gcc-5.2.0_musl-1.1.16/bin/aarch64-openwrt-linux-g++
AR = /opt/toolchain-aarch64_cortex-a53_gcc-5.2.0_musl-1.1.16/bin/aarch64-openwrt-linux-ar
LD = /opt/toolchain-aarch64_cortex-a53_gcc-5.2.0_musl-1.1.16/bin/aarch64-openwrt-linux-ld
AROPTS = cr
ifeq ($(V),1)
Q=
NQ=true
else
Q=@
NQ=echo
endif
CP = cp
INCLUDES=-I$(PRS_COMMON_DIR)/include -I$(SUPPLICANT_DIR)/src/drivers -I$(SRC_DIR)/lib -I$(SRC_DIR)/sample/linux
VPATH=$(SRC_DIR)/lib:$(SRC_DIR)/lib/managed:$(SRC_DIR)/sample/linux
LIBS += -lprsvendor -lpthread
LIBS += $(shell pkg-config --libs libnl-3.0 libnl-genl-3.0)
PRS_TYPES_FLAGS=-DLINUX_FWLOGS_POSTPROCESS
ifeq ($(DEBUG),1)
CXXFLAGS += -Og -g -D __DEBUG__=1 $(PRS_TYPES_FLAGS)
CFLAGS += -Og -g
OUTPUT_DIR=debug
DEBUG_FLAGS="DEBUG=1"
else
CXXFLAGS += -O3 $(PRS_TYPES_FLAGS)
CFLAGS += -O3
OUTPUT_DIR=release
DEBUG_FLAGS=
endif
REL_DIR = .
_OBJS_VENDOR_LIB = PrsDbg.o \
PrsVendorEndian.o \
PrsVendorCommand.o \
PrsVendorLib.o \
PrsManagedIface.o \
PrsVendorInterfaceLinux.o \
PrsVendorEventFile.o \
OBJS_VENDOR_LIB = $(patsubst %, $(OUTPUT_DIR)/%,$(_OBJS_VENDOR_LIB))
_OBJS_SAMPLE = Sample.o \
SampleEventListener.o \
SampleJsonEventListener.o \
SampleFileWriterEventListener.o \
SampleSink.o
OBJS_SAMPLE = $(patsubst %, $(OUTPUT_DIR)/%,$(_OBJS_SAMPLE))
VENDOR_LIB = libprsvendor
SAMPLE_APP = prs_vendor_app
all: dirs $(VENDOR_LIB).a $(SAMPLE_APP)
$(OUTPUT_DIR)/%.o: $(REL_DIR)/%.c
@$(NQ) ' CC ' $@
$(Q)$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
$(OUTPUT_DIR)/%.o: $(REL_DIR)/%.cpp
@$(NQ) ' CXX ' $@
$(Q)$(CXX) $(CXXFLAGS) $(INCLUDES) -c $< -o $@
$(VENDOR_LIB).a: dirs $(OBJS_VENDOR_LIB)
echo ' AR ' $(VENDOR_LIB).a
$(AR) $(AROPTS) $(OUTPUT_DIR)/$(VENDOR_LIB).a $(OBJS_VENDOR_LIB) $(SRC_DIR)/lib/libnl-3.so $(SRC_DIR)/lib/libnl-genl-3.so
$(CP) $(SRC_DIR)/lib/PrsVendorLib.h $(OUTPUT_DIR)/include
$(CP) $(SRC_DIR)/lib/libnl-3.so $(OUTPUT_DIR)/libnl-3.so
$(CP) $(SRC_DIR)/lib/libnl-genl-3.so $(OUTPUT_DIR)/libnl-genl-3.so
$(CP) -rf $(PRS_COMMON_DIR)/include $(OUTPUT_DIR)
$(SAMPLE_APP): dirs $(OBJS_SAMPLE) $(VENDOR_LIB).a
@$(NQ) ' LINK ' $(SAMPLE_APP)
$(CXX) $(LDFLAGS) $(OBJS_SAMPLE) -L$(OUTPUT_DIR) $(LIBS) -o $(OUTPUT_DIR)/$(SAMPLE_APP)
dirs:
$(MKDIR) $(OUTPUT_DIR)
$(MKDIR) $(OUTPUT_DIR)/include
clean:
$(Q)rm -rf debug release
其实我们关心的并不是这个工具的结构,而是这个工具的实现方法,是否有通用的软件工具开发方法?
主线流程
可以看出这个工具主要是两个用途:
- 向底层发送命令;
- 监听底层的事件。
我们主要分析 GetOpts() 和 g_run。
GetOpts() 用于解析用户命令,打包参数通过表驱动法查表封装任务到任务队列。
解析参数
bool GetOpts(int32_t a_argc, char* a_argv[], SPrsSampleAppOpts& a_opts)
{
int32_t argIndex = 1;
bool ok = true;
bool specificCommands = false;
void* parms = NULL;
bool help = false;
uint32_t eventId = 0;
a_opts.inputSelection = InputSelection_Interface;
a_opts.interfaceName = NULL;
a_opts.peerDevice = false;
a_opts.inputFileName = "";
a_opts.outputSelection = OutputSelection_Stdout;
a_opts.outputFileName = "";
a_opts.remoteJsonHost = "";
a_opts.remoteJsonPort = "";
a_opts.eventsWaitSeconds = 0;
a_opts.eventFilters = NULL;
while (argIndex < a_argc)
{
if (0 == strncmp(a_argv[argIndex], "-h", 2))
{
help = true; //帮助模式开启
break;
}
else if (0 == strncmp(a_argv[argIndex], "-c", 2) && (argIndex + 1) < a_argc)
{
cerr << "Note: Ignoring -c option (no longer needed)" << endl;
argIndex++;
}
else if (0 == strncmp(a_argv[argIndex], "-i", 2) && (argIndex + 1) < a_argc)
{
argIndex++;
a_opts.inputSelection = InputSelection_Interface;
a_opts.interfaceName = a_argv[argIndex];
}
else if (0 == strncmp(a_argv[argIndex], "-e", 2) && (argIndex + 1) < a_argc)
{
argIndex++;
......
}
else if (0 == strncmp(a_argv[argIndex], "-o", 2) && (argIndex + 1) < a_argc)
{
argIndex++;
......
}
else if (0 == strncmp(a_argv[argIndex], "-s", 2) && (argIndex + 1) < a_argc)
{
specificCommands = true; //命令模式
argIndex++;
if (!ProcessCmdParms(a_argc, a_argv, &argIndex, parms)) //查表执行命令
{
ok = false;
break;
}
}
else if (0 == strncmp(a_argv[argIndex], "-f", 2) && (argIndex + 1) < a_argc)
{
......
}
else
{
cerr << "Error: Unknown option "
<< a_argv[argIndex]
<< endl;
ok = false;
}
argIndex++;
}
if (help)
{
Usage(a_argv[0]); //打印用法
exit(EXIT_SUCCESS);
}
......
return ok;
}
省略了大部分不重要的代码,这个函数主要是分析用户执行时所带的参数,例如:
-s ,那么参数后面就要带命令。
-h ,直接就打印工具的使用方法了。
-i,参数后面要带指定的网络接口。
等等,我们继续分析命令模式。
表驱动法获取任务
ProcessCmdParms(a_argc, a_argv, &argIndex, parms)
a_argc 是 main 函数的参数个数,a_argv 是 main 函数的所有参数组成的字符串。
&argIndex 和 parms 有其他的用法,但是不是用在命令模式中,无视即可。
先来看一个用于封装任务的数据结构:
任务数据结构
typedef struct SPrsCmdDef
{
/// @brief Test command name
const char* cmdName;
/// @brief A test description
const char* cmdDesc;
/// @brief Indicates whether connection is required or not
bool cnxn; //这个无视即可
/// @brief 任务的回调函数
CmdHandler func;
/// @brief 解析一些特殊的参数
CmdHandlerParms getFuncParms;
/// @brief 命令的具体用法
CmdUsage usage;
/// @brief 运行函数所需的参数指针
void* parms;
} SPrsCmdDef;
typedef bool (*CmdHandler)(PrsCommandIface* a_comIface, SPrsCmdDef* a_cmdDef);
/// @brief 为任务函数解析一些特殊的参数
typedef bool (*CmdHandlerParms)(int a_argc, char* a_argv[], int* a_argIndex, void** a_cmdParms);
/// @brief 打印用法的函数指针
typedef void (*CmdUsage)(void);
ProcessCmdParms 源码:
bool ProcessCmdParms(
int32_t a_argc,
char* a_argv[],
int32_t* a_argIndex,
void* a_parms )
{
bool ok = true;
SPrsCmdDef* cmd = NULL;
if (NULL != (cmd = GetCommandByName(a_argv[*a_argIndex])))//查表获取命令的完整结构体
{
if (cmd->getFuncParms)
{
if (cmd->getFuncParms(a_argc, a_argv, a_argIndex, &a_parms))//解析命令的参数(-s之后的内容)
{
cmd->parms = a_parms;
}
else
{
if (cmd->usage)
{
cout << "Error: "
<< a_argv[*a_argIndex]
<< " expects parameters ";
cmd->usage();
cout << endl
<< endl;
}
else
cout << "Error: Failed to collect command parameters"
<< endl;
delete cmd;
cmd = NULL;
ok = false;
}
}
if (ok && cmd)
{
cout << "Added \""
<< cmd->cmdDesc
<< "\" command"
<< endl;
g_run.push(cmd);
}
}
else
{
cout << "Error: "
<< a_argv[*a_argIndex]
<< " command is not recognized."
<< endl
<< endl;
ok = false;
}
return ok;
}
GetCommandByName 原型
SPrsCmdDef* GetCommandByName(const char* a_cmdName)
{
int32_t cmdNo = 0;
SPrsCmdDef* cmd = NULL;
cmd = new SPrsCmdDef();
if (cmd)
{
for (cmdNo = 0; cmdNo < ePrsSampleCommands_Max; cmdNo++)
{
if (0 == strcmp(g_cmds[cmdNo].cmdName, a_cmdName))//通过命令的枚举编号获取命令在表中的位置
{
// 如果找得到命令,就把这个命令在表中的结构体复制给要执行的 cmd 变量
memcpy(cmd, &g_cmds[cmdNo], sizeof(SPrsCmdDef));
break;
}
}
if (cmdNo == ePrsSampleCommands_Max)
{
delete cmd;
cmd = NULL;
}
}
return cmd;
}
/* 命令的枚举,查表时使用 */
enum EPrsSampleCommands
{
/// @brief Add Block ACK command
ePrsSampleCommands_AddBlockAck = 0,
/// @brief Query Block ACK status command
ePrsSampleCommands_QueryBlockAckStatus,
/// @brief Delete Block ACK command
ePrsSampleCommands_DelBlockAck,
/// @brief Link measurement request command
ePrsSampleCommands_LinkMeasure,
/// @brief Query MIB(s) command
ePrsSampleCommands_QueryMib,
/// @brief Set MIB(s) command
ePrsSampleCommands_SetMib,
/// @brief Set aiming mode with no options
ePrsSampleCommands_SimpleAimMode,
/// @brief Set aiming mode with channel number
ePrsSampleCommands_ChannelAimMode,
/// @brief Set aiming mode given BSSID of AP and channel number
ePrsSampleCommands_BssidAimMode,
/// @brief Enable firmware logs
ePrsSampleCommands_EnableFwLogs,
/// @brief Set the power mode
ePrsSampleCommands_PowerMode,
/// @brief Issue firmare reset
ePrsSampleCommands_Reset,
/// @brief Initiate get crash logs from firmware
ePrsSampleCommands_GetCrashLogs,
/// @brief Enable DBSC feature
ePrsSampleCommands_DbscEnable,
/// @brief Disable DBSC feature
ePrsSampleCommands_DbscDisable,
/// @brief Notification filter commands
ePrsSampleCommands_NotificationFilter,
/// @brief 命令的数量
ePrsSampleCommands_Max
};
核心来了,表驱动法!!!
static const SPrsCmdDef g_cmds[ePrsSampleCommands_Max] =
{
// Connection based commands
{ "aba", "Add Block ACK", 1, DoAddBlockAck, GetPeerAddress, UsagePeerAddr, 0 },
{ "qba", "Query Block ACK status", 1, DoQueryBlockAckStatus, GetPeerAddress, UsagePeerAddr, 0 },
{ "dba", "Send del Block ACK", 1, DoDelBlockAck, GetPeerAddress, UsagePeerAddr, 0 },
{ "lm", "Link measurement request", 1, DoLinkMeasure, GetPeerAddress, UsagePeerAddr, 0 },
// Non-connection based commands
{ "qmib", "Query a range of MIBs", 0, DoQueryMib, GetQueryMibs, UsageQueryMibs, 0 },
{ "smib", "Set a range of MIBs", 0, DoSetMib, GetSetMibsAndVals, UsageSetMib, 0 },
{ "aim1", "Enable/Disable Aiming mode", 0, DoSimpleAimMode, GetSetting, UsageEnableAimingMode, 0 },
{ "aim2", "Set aiming mode on with channel", 0, DoChannelAimMode, GetSetting, UsageChannelNum, 0},
{ "aim3", "Set aiming mode on with BSSID/channel", 0, DoBssidAimMode, GetChannelAndBssid, UsageChannelAndBssid, 0 },
{ "fel", "Enable/Disable firmware logs", 0, DoEnableFwLogs, GetSetting, UsageEnable, 0 },
{ "pm", "Set power mode", 0, DoPowerMode, GetSetting, UsagePowerMode, 0 },
{ "rs", "Issue firmware reset", 0, DoReset, GetOptionalResetTime, UsageReset, 0 },
{ "fgl", "Trigger getting firmware crash logs", 0, DoGetCrashLogs, GetSetting, UsageCrashLogs, 0 },
{ "dbsc_en", "Enable DBSC", 0, DoDbscEnable, GetChannelAndBssid, UsageChannelAndBssid, 0 },
{ "dbsc_dis", "Disable DBSC", 0, DoDbscDisable, GetChannelAndBssid, UsageChannelAndBssid, 0 },
{ "notif", "Notification filter commands", 0, DoNotifFilter, GetNotifFilter, UsageNotifFilter, 0 }
};
每个命令的名称、说明、回调函数、获取参数的函数、普通参数做成了一个表。通过查找一个表的命令名称就可以找到对应的回调函数去执行。
任务队列
任务进队
ProcessCmdParms函数的下半段:
if (ok && cmd)
{
cout << "Added \""
<< cmd->cmdDesc
<< "\" command"
<< endl;
g_run.push(cmd);
...
g_run.push(cmd) 就是将一个任务结构体写进了队列,这个队列在全局中实现:
static queue< SPrsCmdDef* > g_run;
可以看到是通过 std 标准库来实现的,而且为了减少内存和提高翻问速度,队列里存放的是任务指针。
任务出队
现在回到主函数的一部分:
if (g_run.size())
{
// Command mode exclusive to event mode otherwise command
// output gets garbled and cannot be parsed.
while (rc && g_run.size())
{
cout << endl;
cmd = g_run.front(); //获取任务队列中最先入队的任务
g_run.pop(); //成员出队
if (!(rc = cmd->func(com, cmd)))//然后执行任务
......
else
......
}
软件框架上就是这样,简单的框架,复杂的任务。
业务处理
下面简单讲一下这个工具的业务处理,对比两个命令的执行流程来了解一下。
从这里也就可以看出这个软件最终都是向 cfg80211 也就无线网卡的驱动发送 netlink 消息。
驱动会将消息传递给固件,从而修改无线网卡的工作。
真正起作用的底层代码:
1、将命令包装成驱动可以识别的数据包
int Private_IssueCommand(
PrsNl80211VendorCommandHandle *a_handle,
unsigned int a_vendorId, unsigned int a_subCommand,
const void *a_pData, unsigned int a_dataLength,
SPrsVendorReplyBuffer *a_pReplyBuffer
)
{
struct nl_msg *msg = NULL;
int returnCode = -1;
do
{
const int hdrlen = 0;
const int flags = 0;
const uint8_t version = 0;
msg = nlmsg_alloc();
if (!msg) {
PRS_ERROR("nlmsg_alloc failed\n");
returnCode = -NLE_NOMEM;
break;
}
if (!genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, a_handle->nl80211_id, hdrlen, flags, NL80211_CMD_VENDOR, version)) {
PRS_ERROR("genlmsg_put failed\n");
returnCode = -NLE_NOMEM;
break;
}
if ((returnCode = nla_put_u32(msg, NL80211_ATTR_IFINDEX, a_handle->ifindex)) != 0) {
PRS_ERROR("nla_put_u32 NL80211_ATTR_IFINDEX failed\n");
break;
}
if ((returnCode = nla_put_u32(msg, NL80211_ATTR_VENDOR_ID, a_vendorId)) != 0) {
PRS_ERROR("nla_put_u32 NL80211_ATTR_VENDOR_ID failed\n");
break;
}
if ((returnCode = nla_put_u32(msg, NL80211_ATTR_VENDOR_SUBCMD, a_subCommand)) != 0) {
PRS_ERROR("nla_put_u32 NL80211_ATTR_VENDOR_SUBCMD failed\n");
break;
}
if ((returnCode = nla_put(msg, NL80211_ATTR_VENDOR_DATA, a_dataLength, a_pData)) != 0) {
PRS_ERROR("nla_put_u32 NL80211_ATTR_VENDOR_DATA failed\n");
break;
}
returnCode = Private_SendReceive(a_handle->cb, a_handle->sk, msg,
Private_VendorReplyHandler, a_pReplyBuffer
);
} while(0);
if (msg)
{
nlmsg_free(msg);
msg = NULL;
}
return returnCode;
}
2、netlink 收发函数
static int Private_SendReceive(
struct nl_cb *a_pOriginalCallback,
struct nl_sock *a_sk,
struct nl_msg *msg,
nl_recvmsg_msg_cb_t a_replyCallback,
void *a_pReplyCallbackArg
)
{
struct nl_cb *cb = NULL;
int returnCode = -1;
do
{
cb = nl_cb_clone(a_pOriginalCallback);
if (!cb) {
PRS_ERROR("nl_cb_clone failed\n");
returnCode = -NLE_NOMEM;
break;
}
returnCode = nl_send_auto_complete(a_sk, msg);
if (returnCode < 0) {
PRS_ERROR("nl_send_auto_complete failed with %d\n", returnCode);
break;
}
nl_cb_err(cb, NL_CB_CUSTOM, Private_ErrorHandler, &returnCode);
/* Last message in a series of multi part messages received */
nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, Private_FinishHandler, &returnCode);
/* Message is an acknowledge */
nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, Private_AckHandler, &returnCode);
/* Message is valid */
nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, a_replyCallback, a_pReplyCallbackArg);
returnCode = 1; /* This will be set by the handlers */
while (returnCode > 0)
{
int res = nl_recvmsgs(a_sk, cb);
if (res < 0) {
PRS_ERROR("nl_recvmsgs failed with %d\n", res);
returnCode = res;
break;
}
}
} while(0);
if (cb)
{
nl_cb_put(cb);
cb = NULL;
}
return returnCode;
}
3、收到消息时使用供应商专属的解析函数
int Private_VendorReplyHandler(struct nl_msg *msg, void *arg)
{
struct nlattr *tb[NL80211_ATTR_MAX + 1];
struct nlattr *nl_vendor_reply, *nl;
struct genlmsghdr *gnlh = (struct genlmsghdr *)nlmsg_data(nlmsg_hdr(msg));
SPrsVendorReplyBuffer *buf = (SPrsVendorReplyBuffer *)arg;
static const uint8_t sc_SuccessCodeBuffer[4] = {0x00, 0x00, 0x00, 0x00};
int rem;
if (!buf)
return NL_SKIP; /* Skip this message. */
// Create attribute index based on a stream of attributes. This will populate
// `tb` by parsing all attributes from `gnlh`.
nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL);
nl_vendor_reply = tb[NL80211_ATTR_VENDOR_DATA];
if (!nl_vendor_reply){
PRS_ERROR("No vendor data found in reply");
return NL_SKIP;
}
nla_for_each_nested_typecast(nl, nl_vendor_reply, rem) {
if (!Private_Buffer_Append(buf, nla_data(nl), nla_len(nl))) {
if (nla_len(nl) == 4 && buf->capacity == 0 && memcmp(nla_data(nl), sc_SuccessCodeBuffer, 4) == 0)
{
// All commands seem to return return a 4 byte response!
}
else
{
PRS_ERROR("buffer_append error: data=0x%p len=%d buffer capacity=%zu size=%zu",
nla_data(nl), nla_len(nl), buf->capacity, buf->size);
}
return NL_SKIP;
}
}
return NL_SKIP;
}