blob: 71696c5b5bc9764447d16de53bc244668707fc6e [file] [log] [blame]
Sam Leffler6969d1d2010-03-15 16:07:11 -07001# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Paul Stewart310928c2010-09-07 11:54:11 -07005import logging, re, time
Paul Stewartc9628b32010-08-11 13:03:51 -07006from autotest_lib.client.common_lib import error
Paul Stewart2ee7fdf2011-05-19 16:29:23 -07007from autotest_lib.server import site_linux_system
Sam Leffler19bb0a72010-04-12 08:51:08 -07008
9def isLinuxRouter(router):
10 router_uname = router.run('uname').stdout
11 return re.search('Linux', router_uname)
12
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070013class LinuxRouter(site_linux_system.LinuxSystem):
Sam Leffler6969d1d2010-03-15 16:07:11 -070014 """
15 Linux/mac80211-style WiFi Router support for WiFiTest class.
16
17 This class implements test methods/steps that communicate with a
18 router implemented with Linux/mac80211. The router must
19 be pre-configured to enable ssh access and have a mac80211-based
20 wireless device. We also assume hostapd 0.7.x and iw are present
21 and any necessary modules are pre-loaded.
22 """
23
24
25 def __init__(self, host, params, defssid):
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070026 site_linux_system.LinuxSystem.__init__(self, host, params, "router")
27
28 self.bridgeif = params.get('bridgedev', "br-lan")
29 self.wiredif = params.get('wiredev', "eth0")
Nebojsa Sabovic138ff912010-04-06 15:47:42 -070030 self.cmd_brctl = "/usr/sbin/brctl"
31 self.cmd_hostapd = "/usr/sbin/hostapd"
32
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -070033 # Router host.
Sam Leffler6969d1d2010-03-15 16:07:11 -070034 self.router = host
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -070035
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -070036
37 # hostapd configuration persists throughout the test, subsequent
38 # 'config' commands only modify it.
Paul Stewart7cb1f062010-06-10 15:46:20 -070039 self.defssid = defssid
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -070040 self.hostapd = {
41 'configured': False,
Nebojsa Sabovic60ae1462010-05-07 16:14:45 -070042 'file': "/tmp/hostapd-test.conf",
Paul Stewartf854d2e2011-05-04 13:19:18 -070043 'log': "/tmp/hostapd-test.log",
44 'log_count': 0,
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -070045 'driver': "nl80211",
46 'conf': {
47 'ssid': defssid,
Nebojsa Sabovic4a7a5a12010-04-29 14:29:51 -070048 'bridge': self.bridgeif,
49 'hw_mode': 'g'
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -070050 }
51 }
Paul Stewartc2b3de82011-03-03 14:45:31 -080052 self.station = {
53 'configured': False,
54 'conf': {
55 'ssid': defssid,
Paul Stewartf05d7fd2011-04-06 16:19:37 -070056 },
57 'local_server_state': None,
58 'local_server': {
59 'address': '192.168.3.254/24',
60 'dhcp_range': (1, 128),
61 'dhcpd_conf': '/tmp/dhcpd.conf',
62 'lease_file': '/tmp/dhcpd.leases'
Paul Stewartc2b3de82011-03-03 14:45:31 -080063 }
64 }
Paul Stewartf05d7fd2011-04-06 16:19:37 -070065 self.station['local_server'].update(params.get('local_server', {}))
66
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -070067 # Kill hostapd if already running.
68 self.router.run("pkill hostapd >/dev/null 2>&1", ignore_status=True)
69
70 # Remove all bridges.
71 output = self.router.run("%s show" % self.cmd_brctl).stdout
72 test = re.compile("^(\S+).*")
73 for line in output.splitlines()[1:]:
74 m = test.match(line)
75 if m:
76 device = m.group(1)
77 self.router.run("%s link set %s down" % (self.cmd_ip, device))
78 self.router.run("%s delbr %s" % (self.cmd_brctl, device))
79
Nebojsa Sabovicbc245c62010-04-28 16:58:50 -070080 # Place us in the US by default
81 self.router.run("%s reg set US" % self.cmd_iw)
Sam Leffler6969d1d2010-03-15 16:07:11 -070082
Paul Stewartf05d7fd2011-04-06 16:19:37 -070083
Sam Leffler6969d1d2010-03-15 16:07:11 -070084 def create(self, params):
85 """ Create a wifi device of the specified type """
86 #
87 # AP mode is handled entirely by hostapd so we only
88 # have to setup others (mapping the bsd type to what
89 # iw wants)
90 #
91 # map from bsd types to iw types
Paul Stewartc2b3de82011-03-03 14:45:31 -080092 self.apmode = params['type'] in ("ap", "hostap")
93 if not self.apmode:
94 self.station['type'] = params['type']
Paul Stewart2ee7fdf2011-05-19 16:29:23 -070095 self.phytype = {
Sam Leffler6969d1d2010-03-15 16:07:11 -070096 "sta" : "managed",
97 "monitor" : "monitor",
98 "adhoc" : "adhoc",
99 "ibss" : "ibss",
Nebojsa Sabovic138ff912010-04-06 15:47:42 -0700100 "ap" : "managed", # NB: handled by hostapd
101 "hostap" : "managed", # NB: handled by hostapd
Sam Leffler6969d1d2010-03-15 16:07:11 -0700102 "mesh" : "mesh",
103 "wds" : "wds",
104 }[params['type']]
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700105
Sam Leffler6969d1d2010-03-15 16:07:11 -0700106
107 def destroy(self, params):
108 """ Destroy a previously created device """
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700109 # For linux, this is the same as deconfig.
110 self.deconfig(params)
111
Sam Leffler6969d1d2010-03-15 16:07:11 -0700112
113
Paul Stewartc2b3de82011-03-03 14:45:31 -0800114 def hostap_config(self, params):
Sam Leffler6969d1d2010-03-15 16:07:11 -0700115 """ Configure the AP per test requirements """
116
Paul Stewart45338d22010-10-21 10:57:02 -0700117 multi_interface = 'multi_interface' in params
118 if multi_interface:
119 params.pop('multi_interface')
Paul Stewartc2b3de82011-03-03 14:45:31 -0800120 elif self.hostapd['configured'] or self.station['configured']:
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700121 self.deconfig({})
122
Paul Stewartc2b3de82011-03-03 14:45:31 -0800123 # Construct the hostapd.conf file and start hostapd.
124 conf = self.hostapd['conf']
125 tx_power_params = {}
126 htcaps = set()
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700127
Paul Stewartc2b3de82011-03-03 14:45:31 -0800128 conf['driver'] = params.get('hostapd_driver',
129 self.hostapd['driver'])
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700130
Paul Stewartc2b3de82011-03-03 14:45:31 -0800131 for k, v in params.iteritems():
132 if k == 'ssid':
133 conf['ssid'] = v
134 elif k == 'ssid_suffix':
135 conf['ssid'] = self.defssid + v
136 elif k == 'channel':
137 freq = int(v)
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700138 self.hostapd['frequency'] = freq
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700139
Paul Stewartc2b3de82011-03-03 14:45:31 -0800140 # 2.4GHz
141 if freq <= 2484:
142 # Make sure hw_mode is set
143 if conf.get('hw_mode') == 'a':
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700144 conf['hw_mode'] = 'g'
Paul Stewartc2b3de82011-03-03 14:45:31 -0800145
146 # Freq = 5 * chan + 2407, except channel 14
147 if freq == 2484:
148 conf['channel'] = 14
149 else:
150 conf['channel'] = (freq - 2407) / 5
151 # 5GHz
Sam Leffler6969d1d2010-03-15 16:07:11 -0700152 else:
Paul Stewartc2b3de82011-03-03 14:45:31 -0800153 # Make sure hw_mode is set
154 conf['hw_mode'] = 'a'
155 # Freq = 5 * chan + 4000
156 if freq < 5000:
157 conf['channel'] = (freq - 4000) / 5
158 # Freq = 5 * chan + 5000
159 else:
160 conf['channel'] = (freq - 5000) / 5
Sam Leffler6969d1d2010-03-15 16:07:11 -0700161
Paul Stewartc2b3de82011-03-03 14:45:31 -0800162 elif k == 'country':
163 conf['country_code'] = v
164 elif k == 'dotd':
165 conf['ieee80211d'] = 1
166 elif k == '-dotd':
167 conf['ieee80211d'] = 0
168 elif k == 'mode':
169 if v == '11a':
170 conf['hw_mode'] = 'a'
171 elif v == '11g':
172 conf['hw_mode'] = 'g'
173 elif v == '11b':
174 conf['hw_mode'] = 'b'
175 elif v == '11n':
176 conf['ieee80211n'] = 1
177 elif k == 'bintval':
178 conf['beacon_int'] = v
179 elif k == 'dtimperiod':
180 conf['dtim_period'] = v
181 elif k == 'rtsthreshold':
182 conf['rts_threshold'] = v
183 elif k == 'fragthreshold':
184 conf['fragm_threshold'] = v
185 elif k == 'shortpreamble':
186 conf['preamble'] = 1
187 elif k == 'authmode':
188 if v == "open":
189 conf['auth_algs'] = 1
190 elif v == "shared":
191 conf['auth_algs'] = 2
192 elif k == 'hidessid':
193 conf['ignore_broadcast_ssid'] = 1
194 elif k == 'wme':
195 conf['wmm_enabled'] = 1
196 elif k == '-wme':
197 conf['wmm_enabled'] = 0
198 elif k == 'deftxkey':
199 conf['wep_default_key'] = v
200 elif k == 'ht20':
201 htcaps.add('') # NB: ensure 802.11n setup below
202 conf['wmm_enabled'] = 1
203 elif k == 'ht40':
204 htcaps.add('[HT40-]')
205 htcaps.add('[HT40+]')
206 conf['wmm_enabled'] = 1
Paul Stewartc1df8d62011-04-07 14:28:15 -0700207 elif k in ('ht40+', 'ht40-'):
208 htcaps.add('[%s]' % k.upper())
209 conf['wmm_enabled'] = 1
Paul Stewartc2b3de82011-03-03 14:45:31 -0800210 elif k == 'shortgi':
211 htcaps.add('[SHORT-GI-20]')
212 htcaps.add('[SHORT-GI-40]')
213 elif k == 'pureg':
214 pass # TODO(sleffler) need hostapd support
215 elif k == 'puren':
216 pass # TODO(sleffler) need hostapd support
217 elif k == 'protmode':
218 pass # TODO(sleffler) need hostapd support
219 elif k == 'ht':
220 htcaps.add('') # NB: ensure 802.11n setup below
221 elif k == 'htprotmode':
222 pass # TODO(sleffler) need hostapd support
223 elif k == 'rifs':
224 pass # TODO(sleffler) need hostapd support
225 elif k == 'wepmode':
226 pass # NB: meaningless for hostapd; ignore
227 elif k == '-ampdu':
228 pass # TODO(sleffler) need hostapd support
229 elif k == 'txpower':
230 tx_power_params['power'] = v
Nebojsa Sabovic60ae1462010-05-07 16:14:45 -0700231 else:
Paul Stewartc2b3de82011-03-03 14:45:31 -0800232 conf[k] = v
Nebojsa Sabovic60ae1462010-05-07 16:14:45 -0700233
Paul Stewartc2b3de82011-03-03 14:45:31 -0800234 # Aggregate ht_capab.
235 if htcaps:
236 conf['ieee80211n'] = 1
237 conf['ht_capab'] = ''.join(htcaps)
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700238
Paul Stewartc2b3de82011-03-03 14:45:31 -0800239 # Figure out the correct interface.
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700240 conf['interface'] = self._get_wlanif(self.hostapd['frequency'],
241 self.phytype,
242 mode=conf.get('hw_mode', 'b'))
Paul Stewart9877fe42010-12-10 08:28:21 -0800243
Paul Stewartc2b3de82011-03-03 14:45:31 -0800244 # Generate hostapd.conf.
245 self.router.run("cat <<EOF >%s\n%s\nEOF\n" %
246 (self.hostapd['file'], '\n'.join(
247 "%s=%s" % kv for kv in conf.iteritems())))
248
249 if not multi_interface:
250 logging.info("Initializing bridge...")
251 self.router.run("%s addbr %s" %
252 (self.cmd_brctl, self.bridgeif))
253 self.router.run("%s setfd %s %d" %
254 (self.cmd_brctl, self.bridgeif, 0))
255 self.router.run("%s stp %s %d" %
256 (self.cmd_brctl, self.bridgeif, 0))
257
258 # Run hostapd.
259 logging.info("Starting hostapd...")
Paul Stewartf854d2e2011-05-04 13:19:18 -0700260 self.router.run("%s -dd %s > %s &" %
261 (self.cmd_hostapd, self.hostapd['file'], self.hostapd['log']))
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700262
Nebojsa Sabovic60ae1462010-05-07 16:14:45 -0700263
Paul Stewartc2b3de82011-03-03 14:45:31 -0800264 # Set up the bridge.
265 if not multi_interface:
266 logging.info("Setting up the bridge...")
267 self.router.run("%s addif %s %s" %
268 (self.cmd_brctl, self.bridgeif, self.wiredif))
269 self.router.run("%s link set %s up" %
270 (self.cmd_ip, self.wiredif))
271 self.router.run("%s link set %s up" %
272 (self.cmd_ip, self.bridgeif))
273 self.hostapd['interface'] = conf['interface']
274 else:
275 tx_power_params['interface'] = conf['interface']
Paul Stewart1ae854b2011-02-08 15:10:14 -0800276
Paul Stewartc2b3de82011-03-03 14:45:31 -0800277 # Configure transmit power
278 self.set_txpower(tx_power_params)
Nebojsa Sabovic138ff912010-04-06 15:47:42 -0700279
Paul Stewartc2b3de82011-03-03 14:45:31 -0800280 logging.info("AP configured.")
Sam Leffler6969d1d2010-03-15 16:07:11 -0700281
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700282 self.hostapd['configured'] = True
Sam Leffler6969d1d2010-03-15 16:07:11 -0700283
284
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700285 def station_local_addr(self, idx):
286 """
287 Simple IPv4 calculator. Takes host address in "IP/bits" notation
288 and returns netmask, broadcast address as well as integer offsets
289 into the address range.
290 """
291 addr_str,bits = self.station['local_server_state']['address'].split('/')
292 addr = map(int, addr_str.split('.'))
293 mask_bits = (-1 << (32-int(bits))) & 0xffffffff
294 mask = [(mask_bits >> s) & 0xff for s in range(24, -1, -8)]
295 if idx == 'netmask':
296 return '.'.join(map(str, mask))
297 elif idx == 'broadcast':
298 offset = [m ^ 0xff for m in mask]
299 else:
300 offset = [(idx >> s) & 0xff for s in range(24, -1, -8)]
301 return '.'.join(map(str, [(a & m) + o
302 for a, m, o in zip(addr, mask, offset)]))
303
304
Paul Stewartc2b3de82011-03-03 14:45:31 -0800305 def station_config(self, params):
306 multi_interface = 'multi_interface' in params
307 if multi_interface:
308 params.pop('multi_interface')
309 elif self.station['configured'] or self.hostapd['configured']:
310 self.deconfig({})
311
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700312 local_server = params.pop('local_server', False)
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700313 mode = None
Paul Stewartc2b3de82011-03-03 14:45:31 -0800314 conf = self.station['conf']
315 for k, v in params.iteritems():
316 if k == 'ssid_suffix':
317 conf['ssid'] = self.defssid + v
318 elif k == 'channel':
319 freq = int(v)
320 if freq > 2484:
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700321 mode = 'a'
Paul Stewartc2b3de82011-03-03 14:45:31 -0800322 elif k == 'mode':
323 if v == '11a':
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700324 mode = 'a'
Paul Stewartc2b3de82011-03-03 14:45:31 -0800325 else:
326 conf[k] = v
327
Paul Stewart2ee7fdf2011-05-19 16:29:23 -0700328 interface = self._get_wlanif(freq, self.phytype, mode)
329
Paul Stewartc2b3de82011-03-03 14:45:31 -0800330 if not multi_interface:
331 logging.info("Initializing bridge...")
332 self.router.run("%s addbr %s" %
333 (self.cmd_brctl, self.bridgeif))
334 self.router.run("%s setfd %s %d" %
335 (self.cmd_brctl, self.bridgeif, 0))
336 self.router.run("%s stp %s %d" %
337 (self.cmd_brctl, self.bridgeif, 0))
338
339 # Run interface configuration commands
340 for k, v in conf.iteritems():
341 if k != 'ssid':
342 self.router.run("%s dev %s set %s %s" %
343 (self.cmd_iw, interface, k, v))
344
345 # Connect the station
346 self.router.run("%s link set %s up" % (self.cmd_ip, interface))
347 connect_cmd = ('ibss join' if self.station['type'] == 'ibss'
348 else 'connect')
349 self.router.run("%s dev %s %s %s %d" %
350 (self.cmd_iw, interface, connect_cmd,
351 conf['ssid'], freq))
352
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700353 if self.station['type'] != 'ibss':
354 # Add wireless interface to the bridge
Paul Stewartc2b3de82011-03-03 14:45:31 -0800355 self.router.run("%s addif %s %s" %
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700356 (self.cmd_brctl, self.bridgeif, interface))
357
358 # Add wired interface to the bridge, then bring up the bridge if
359 if not multi_interface:
360 logging.info("Setting up the bridge...")
361 self.router.run("%s addif %s %s" %
362 (self.cmd_brctl, self.bridgeif, self.wiredif))
363 self.router.run("%s link set %s up" %
364 (self.cmd_ip, self.wiredif))
365 self.router.run("%s link set %s up" %
366 (self.cmd_ip, self.bridgeif))
367
368 if local_server is not False:
369 logging.info("Starting up local server...")
370 params = self.station['local_server'].copy()
371 params.update(local_server or {})
372 self.station['local_server_state'] = params
373 params['subnet'] = self.station_local_addr(0)
374 params['netmask'] = self.station_local_addr('netmask')
375 params['dhcp_range'] = ' '.join(map(self.station_local_addr,
376 params['dhcp_range']))
377
378 params['ip_params'] = ("%s broadcast %s dev %s" %
379 (params['address'],
380 self.station_local_addr('broadcast'),
381 interface))
382 self.router.run("%s addr add %s" %
383 (self.cmd_ip, params['ip_params']))
Paul Stewartc2b3de82011-03-03 14:45:31 -0800384 self.router.run("%s link set %s up" %
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700385 (self.cmd_ip, interface))
386
387 self.router.run("cat <<EOF >%s\n%s\nEOF\n" %
388 (params['dhcpd_conf'],
389 '\n'.join(('ddns-update-style none;',
390 'subnet %(subnet)s netmask %(netmask)s {',
391 ' range %(dhcp_range)s;', '}')) % params))
392 self.router.run("pkill dhcpd >/dev/null 2>&1", ignore_status=True)
393 self.router.run("%s -q -cf %s -lf %s %s" %
394 (self.cmd_dhcpd, params['dhcpd_conf'],
395 params['lease_file'], interface))
Paul Stewartc2b3de82011-03-03 14:45:31 -0800396
397 self.station['configured'] = True
398 self.station['interface'] = interface
399
400
401 def config(self, params):
402 if self.apmode:
403 self.hostap_config(params)
404 else:
405 self.station_config(params)
406
407
Sam Leffler6969d1d2010-03-15 16:07:11 -0700408 def deconfig(self, params):
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700409 """ De-configure the AP (will also bring wlan and the bridge down) """
Sam Leffler6969d1d2010-03-15 16:07:11 -0700410
Paul Stewartc2b3de82011-03-03 14:45:31 -0800411 if not self.hostapd['configured'] and not self.station['configured']:
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700412 return
Sam Leffler6969d1d2010-03-15 16:07:11 -0700413
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700414 # Taking down hostapd takes wlan0 and mon.wlan0 down.
Paul Stewartc2b3de82011-03-03 14:45:31 -0800415 if self.hostapd['configured']:
416 self.router.run("pkill hostapd >/dev/null 2>&1", ignore_status=True)
417# self.router.run("rm -f %s" % self.hostapd['file'])
Paul Stewartf854d2e2011-05-04 13:19:18 -0700418 self.router.get_file(self.hostapd['log'],
419 'debug/hostapd_router_%d.log' %
420 self.hostapd['log_count'])
421 self.hostapd['log_count'] += 1
Paul Stewartc2b3de82011-03-03 14:45:31 -0800422 if self.station['configured']:
423 if self.station['type'] == 'ibss':
424 self.router.run("%s dev %s ibss leave" %
425 (self.cmd_iw, self.station['interface']))
426 else:
427 self.router.run("%s dev %s disconnect" %
428 (self.cmd_iw, self.station['interface']))
429 self.router.run("%s link set %s down" % (self.cmd_ip,
430 self.station['interface']))
Paul Stewartf05d7fd2011-04-06 16:19:37 -0700431 if self.station['local_server_state']:
432 self.router.run("pkill dhcpd >/dev/null 2>&1",
433 ignore_status=True)
434 self.router.run("%s addr del %s" %
435 (self.cmd_ip, self.station
436 ['local_server_state']['ip_params']))
437 self.station['local_server_state'] = None
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700438
Paul Stewart310928c2010-09-07 11:54:11 -0700439 # Try a couple times to remove the bridge; hostapd may still be exiting
440 for attempt in range(3):
Paul Stewart66dcb082010-09-08 16:42:12 -0700441 self.router.run("%s link set %s down" %
442 (self.cmd_ip, self.bridgeif), ignore_status=True)
443
Paul Stewart310928c2010-09-07 11:54:11 -0700444 result = self.router.run("%s delbr %s" %
445 (self.cmd_brctl, self.bridgeif),
446 ignore_status=True)
Paul Stewartaf5847e2010-09-21 12:02:03 -0700447 if not result.stderr or 'No such device' in result.stderr:
Paul Stewart310928c2010-09-07 11:54:11 -0700448 break
449 time.sleep(1)
450 else:
451 raise error.TestFail("Unable to delete bridge %s: %s" %
452 (self.bridgeif, result.stderr))
453
Nebojsa Sabovic4cc2ce92010-04-21 15:08:01 -0700454
455 self.hostapd['configured'] = False
Paul Stewartc2b3de82011-03-03 14:45:31 -0800456 self.station['configured'] = False
Paul Stewart7cb1f062010-06-10 15:46:20 -0700457
458
459 def get_ssid(self):
460 return self.hostapd['conf']['ssid']
Paul Stewart98022e22010-10-22 10:33:14 -0700461
462
463 def set_txpower(self, params):
464 self.router.run("%s dev %s set txpower %s" %
465 (self.cmd_iw, params.get('interface',
466 self.hostapd['interface']),
467 params.get('power', 'auto')))