mininet-wifi无线自组网实现原理及代码分析
mn_wifi\examples\double_xxx_xxxx.py
组网图:
无线自组网实现原理:
对每个node节点(所有的节点的基类都是Node)启动一个shell进程,记住进程的pid。通过导入的mac80211_hwsim模块,创建若干个(就是每个node的wlans参数的总和)无线网络设备(这里包括了phy和dev,phy表示无线网络设备,dev表示接口,他们是一一对应的关系),把每个网络设备加入到名称为"某个进程id"的网络名字空间里面去。把接口改一下名称(比如ap1-wlan1改成ap1-mp1)。用hostapd这个程序把每个无线接口配置好,启动这个无线热点。addlink的时候,如果是station就使用iwconfig命令把当前的station加入到某个热点。如果是ap并且是mesh组网的话,使用类似于“iw dev ap1-mp2 mesh join mesh-ssid freq 2432”这样的命令把这个热点加入到mesh网络里面去。然后在controller.start和ap.start的时候,在对应的shell里面把相关的进程拉起来。这个就完成了整个网络的搭建过程。
代码分析:
前提,大部分和mininet-wifi自带的mesh组网的代码的流程相同。下面只讲差异点,mininet-wifi自带mesh组网代码分析见下面的连接:
https://www.cnblogs.com/matthew-2013/p/13043617.html
net.addParameters(c1, False, node_mode='master', **defaults)
疑问:为什么这里的wlans=1?控制器的输入参数里面没有wlans参数啊,1是从哪里来的呢?
node.func.append('none')
node.phyID.append(0)
在node.params['wlan']里面添加上'c1-wlan1'。————这里只是给node分配wlan的名称,并没有实际创建或者分配wlan的接口。
node.params['ip'] = ['']
node.params['mac'] = ['']
把node.params 中的 'antennaGain=5.0', 'antennaHeight=1.0', 'txpower=14', 'channel=1', 'mode=g', 'freq=2.412' 参数,设置为默认值。
self.add_range_param(node, **params) // 把range参数初始化为[0,0]
if node_mode == 'master':
node.params['associatedStations'] = []
node.params['stationsInRange'] = {}
node.params['mac'] = []
node.params['mac'].append('')
设置 node.params['ssid'] 为输入的参数。注意:无线自组网的ssid有可能是这样的 ['','2']。
node.params['ssid']=['new-ssid']
net.configureWifiNodes()
nodes =这里包括3个ap和1个controllor
module(nodes, self.n_radios, self.alt_module, **params) // nodes=ap+station;n_radios=这个表示当前的网络里面总的无线网卡的数量,当前是7;alt_module=none,
module::__init__
module::start
h = subprocess.check_output // subprocess模块用来创建新的进程,连接到其stdin、stdout、stderr管道并获取它们的返回码;在子进程执行命令,以字符串形式返回执行结果的输出。如果子进程退出码 不是0,抛出subprocess.CalledProcessError异常,异常的output字段包含错误输出
cmd = ps -aux | grep -ic hostapd // 统计hostapd进程的数量
process = Popen(stdout=PIPE, *popenargs, **kwargs) // PIPE=-1, popenargs = {tuple: 1} ps -aux | grep -ic 'hostapd', kwargs = {dict: 1} {'shell': True}
但是这个好像也没有执行这个命令。
output, unused_err = process.communicate() // 在这里开启一个子进程,执行上述命令的。ps -aux | grep -ic 'hostapd' == 统计hostapd进程的数量。
if h >= 2: // 这里的h等于2.
os.system('pkill -f \'hostapd\'') // kill掉 hostapd 相关进程。这里为什么要关闭hostapd进程呢?
physicalWlans = module::get_physical_wlan()
wlans = subprocess.check_output("iw dev 2>&1 | grep Interface | awk '{print $2}'", shell=True)).split("\n")
iw dev 2>&1 ————该命令列出了如下接口。
awk '{print $2}' 表示把上述内如输出到 标准错误 里面去。
wlans = [''] ;// 这种组网模式下,获取的接口是空的,为什么呢?
返回wlans // 这里返回的也是空的。
self.load_module(n_radios, nodes, alt_module, **params) // 初始化wifi模块 n_radios=6,nodes=2个station+2个ap
output_ = os.system('modprobe mac80211_hwsim radios=0 >/dev/null 2>&1') // 导入 mac80211_hwsim 模块,这个就是wifi模块。
self.__create_hwsim_mgmt_devices(n_radios, nodes, **params)
cmd = 'find /sys/kernel/debug/ieee80211 -name hwsim | cut -d/ -f 6 | sort' // 列出 phy。
phys = subprocess.check_output(cmd, shell=True).split("\n") // 执行cmd命令
返回[''] // 返回的phy为空,为什么?
对phys进行遍历
phy.startswith(self.prefix) prefix=='mn00s' Python startswith() 方法用于检查字符串是否是以指定子字符串开头,如果是则返回 True,否则返回 False
for i in range(0, n_radios): 当前的n_radios=7
分别执行如下命令,
cmd = ['hwsim_mgmt', '-c', '-n', 'mn00s00'] // 这些命令是使用导入的模块创建名称为'mn00s00'的mac80211_hwsim 设备。这些命令执行之后,在/sys/kernel/debug/ieee80211/目录下可以看到'mn00s00'这些目录都被创建了。注意,这里是dev和phy是同时创建好了的,并且是一一对应关系;phy应该是表示无线网络设备,而dev表示无线网络接口。
...
cmd = ['hwsim_mgmt', '-c', '-n', 'mn00s06']
module::get_phy
phy = subprocess.check_output("find /sys/kernel/debug/ieee80211 -name hwsim | cut -d/ -f 6 | sort"
返回 ['mn00s00' ~ 'mn00s06'],共计7个phy。
self.assign_iface(nodes, physicalWlans, phys, **params) # 这个函数的作用启用该无线网络设备,并分配网络名字空间。
nodes=3个ap和1个controller
physicalWlans = [] // 上面获取的physicalWlans为空。
wlan_list = self.get_wlan_iface(physicalWlans)// 查询到 wlan0~wlan6 这7个接口
下发 iw dev 2>&1 | grep Interface | awk '{print $2} 命令查询所有的接口
返回 [wlan0~wlan6 ]共7个接口。
for node in nodes: // 对nodes进行遍历
for wlan in range(0, len(node.params['wlan'])): // 对当前节点的wlan参数进行遍历。
node.phyID[wlan] =0 // 把该节点的物理id分别分配成0、1、2…
无论是ap还是controller都做如下操作:
rfkill = subprocess.check_output(rfkill list | grep mn00s00 | awk '{print $1}'| tr -d ":") // rfkill list #列出所有可用的wifi设备,返回的是rfkill list的前面的哪个序号。
mn00s00前面的索引号是7,所以返回的是 ['7','']
os.system('rfkill unblock %s' % rfkill[0]);// rfkill unblock启用索引号为7的wifi设备。
os.system('iw phy %s set netns %s' % (phys[0], node.pid)) // 把这个无线设备放入不同的网络名字空间。这个命令执行后,phy和dev在root的shell就查询不到了,应该是加入到node的网络名字空间了。应该从对应的node的shell里面查询才能查询的到了。
node.cmd('ip link set %s down' % wlan_list[0]) // 关闭wlan0这个无线网络接口。
node.cmd('ip link set %s name %s' % (wlan_list[0], node.params['wlan'][wlan])) // 改名,把wlan0改成ap1-wlan1;wlan1改成ap1-wlan2;wlan2改成ap2-wlan1;wlan3改成ap2-wlan2;
self.configureWirelessLink() // 这里只对station的无线接口进行配置,所以这个组网不涉及
createVirtualIfaces // 对stations创建虚接口,这个也不涉及
AccessPoint(self.aps, self.driver, self.link)
aps = 3个ap+1个controller; driver = {str} 'nl80211'; link = {type} <class 'mn_wifi.link.wmediumd'>
AccessPoint::__init__
AccessPoint::configure
for ap in aps: // 对node进行遍历,这里包括了ap和controller
for wlan in range(len(ap.params['wlan'])): // 对node的wlan进行遍历
cls.configAP(ap, wlan) // 配置ap的wlan参数
TCLinkWirelessAP::__init__
WirelessLinkAP.__init__
params1[ 'port' ] = node1.newPort() // 这里只是分配一个int类型的端口号。
intf1 = IntfWireless::__init__() // 创建一个名称叫 'ap1-wlan1'的接口(接口这个类只是mininet的一个类),给这个接口分配一个int类型的端口号。
node.addIntf(self, port=port)
// 把端口1和接口'ap1-wlan1'建立映射关系
cls.setIPMAC(node, wlan)
// 使用命令 ip addr show ap1-wlan1 mac地址:02:00:00:00:02:00
checkNetworkManager(cls, mac) // add mac address into /etc/NetworkManager/NetworkManager.conf
restartNetworkManager()
nm_is_running = os.system('service network-manager status 2>&1 | grep -ic running >/dev/null 2>&1') // 查询network-manager服务的状态,这里没有做什么。
for ap in aps: // 对node进行遍历,这里包括了ap和controller
for wlan in range(len(ap.params['wlan'])): // 对node的wlan进行遍历
cls.setConfig(ap, aps, wlan, link)
cls.setHostapdConfig(ap, wlan, aplist, link)
cls.APConfigFile(cmd, ap, wlan)
ap = {UserAP} ap1; wlan = {int} 0
cmd = {str} 'echo \'interface=ap1-wlan1\ndriver=nl80211\nssid=ssid91\nwds_sta=1\nhw_mode=g\nchannel=1\nctrl_interface=/var/run/hostapd\nctrl_interface_group=0'
content = 'echo \'interface=ap1-wlan1\ndriver=nl80211\nssid=ssid91\nwds_sta=1\nhw_mode=g\nchannel=1\nctrl_interface=/var/run/hostapd\nctrl_interface_group=0' > mn10379_ap1-wlan1.apconf'
ap.cmd(content) // 把 上面的语句写入 mn10379_ap1-wlan1.apconf 文件
cmd = hostapd -B mn10379_ap1-wlan1.apconf // 启动 hostapd; hostapd是一个带加密功能的无线接入点程序,通过这个配置配置这个无线接入点。
ap.cmd(cmd) // 执行上面的命令
self.configureWmediumd() //当前配置的无线模式是干扰模式,也就是可以人为插入噪音的。
mob.wmediumd_mode = 3
MyWmediumd::__init__ () // 这里的参数有噪音门限等。这个类是自己实现的类。
MyWmediumd::configureWmediumd 下面是对ap和controller都操作了的
设置 wmediumd 的位置和发射功率
start_wmediumd(intfrefs, wmediumd.links, wmediumd.positions,fading_coefficient, noise_threshold,wmediumd.txpowers, isnodeaps, propagation_model,maclist)
w_starter::start
w_starter::initialize
cmdline = ['wmediumd', '-l', '4', '-s', '-c', '/tmp/mn_wmd_config_p_sV91.cfg', '-x', '/home/zhangmeng/mininet-wifi/mn_wifi/data/signal_table_ieee80211']
执行这个命令
w_server.connect()
启动一个socket,并连接到 '/var/run/wmediumd.sock' 上面去。
for node in nodes:
for wlan in range(0, len(node.params['wlan'])):
node.params['range'][wlan] = node.getRange(intf=intf) // 设置node的range参数。
node.setTxPower(node.params['txpower'][wlan],intf=node.params['wlan'][wlan],setParam=setParam) // 根据node的 txpower 参数信息,设置节点的发射功率。
cmd = iw dev ap1-wlan1 set txpower fixed 1400
执行上面的命令,设置发射功率
node.setAntennaGain(node.params['antennaGain'][wlan],intf=node.params['wlan'][wlan],setParam=setParam) // 根据node的 antennaGain 参数信息,设置节点的天线增益
setGainWmediumd
w_server.update_gain(w_gain(self.wmIface[wlan], int(gain_)))
ret = w_server.send_gain_update(gain)
cls.sock.send(cls.__create_gain_update_request(gain))