Scripts/programs to test FreeBSD ethernet interfaces.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

642 lines
18 KiB

  1. #
  2. # Copyright 2021 John-Mark Gurney.
  3. #
  4. # Redistribution and use in source and binary forms, with or without
  5. # modification, are permitted provided that the following conditions
  6. # are met:
  7. # 1. Redistributions of source code must retain the above copyright
  8. # notice, this list of conditions and the following disclaimer.
  9. # 2. Redistributions in binary form must reproduce the above copyright
  10. # notice, this list of conditions and the following disclaimer in the
  11. # documentation and/or other materials provided with the distribution.
  12. #
  13. # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  14. # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  15. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  16. # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  17. # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  18. # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  19. # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  20. # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  21. # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  22. # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  23. # SUCH DAMAGE.
  24. # from: https://en.wikipedia.org/wiki/Reserved_IP_addresses
  25. # TEST-NET-1: 192.0.2.0/24
  26. # TEST-NET-2: 198.51.100.0/24
  27. # TEST-NET-3: 203.0.113.0/24
  28. from unittest.mock import patch
  29. from mocks import *
  30. import unittest
  31. # Silence useless warning: WARNING: No IPv4 address found on
  32. import scapy.error
  33. scapy.error.warning = lambda *a, **kw: None
  34. from scapy.all import *
  35. from kvm import _TestCase as _KVMTestCase
  36. from kvm import KVM
  37. from bpf import Timeval
  38. from ctypes import Structure, Union, POINTER, sizeof, create_string_buffer, byref
  39. from ctypes import c_char, c_void_p, c_uint8, c_uint16, c_uint32, c_int64, c_uint64
  40. import asyncio
  41. import fcntl
  42. import functools
  43. import itertools
  44. import re
  45. import sockio
  46. import string
  47. import sys
  48. class if_data(Structure):
  49. _fields_ = [
  50. ('ifi_type', c_uint8),
  51. ('ifi_physical', c_uint8),
  52. ('ifi_addrlen', c_uint8),
  53. ('ifi_hdrlen', c_uint8),
  54. ('ifi_link_state', c_uint8),
  55. ('ifi_vhid', c_uint8),
  56. ('ifi_datalen', c_uint16),
  57. ('ifi_mtu', c_uint32),
  58. ('ifi_metric', c_uint32),
  59. ('ifi_baudrate', c_uint64),
  60. ('ifi_ipackets', c_uint64),
  61. ('ifi_ierrors', c_uint64),
  62. ('ifi_opackets', c_uint64),
  63. ('ifi_collisions', c_uint64),
  64. ('ifi_ibytes', c_uint64),
  65. ('ifi_obytes', c_uint64),
  66. ('ifi_imcasts', c_uint64),
  67. ('ifi_omcasts', c_uint64),
  68. ('ifi_iqdrops', c_uint64),
  69. ('ifi_oqdrops', c_uint64),
  70. ('ifi_noproto', c_uint64),
  71. ('ifi_hwassist', c_uint64),
  72. ('ifi_epoch', c_int64), # XXX - broken on i386
  73. ('ifi_lastchange', Timeval), # XXX - broken on 32-bit platforms
  74. ]
  75. class ifr_ifru(Union):
  76. _fields_ = [
  77. ('ifru_data', POINTER(c_char)),
  78. ]
  79. class ifreq(Structure):
  80. _fields_ = [
  81. ('ifr_name', c_char * 16),
  82. ('ifr_ifru', ifr_ifru),
  83. ]
  84. def _makeflags(s):
  85. return set(s.split(','))
  86. def _parsemedia(s):
  87. reg = '^Ethernet autoselect (1000baseT <full-duplex>)$'
  88. m = re.match(reg, s)
  89. return dict(ethernet='autoselect', media=dict(medium='1000baseT', options={ 'full-duplex' }))
  90. __ifreqsock = None
  91. def get_ifreqsock():
  92. global __ifreqsock
  93. if __ifreqsock == None:
  94. __ifreqsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  95. return __ifreqsock
  96. __kvm = None
  97. def get_kvm():
  98. global __kvm
  99. if __kvm == None:
  100. __kvm = KVM()
  101. return __kvm
  102. def if_data(iface):
  103. s = get_ifreqsock()
  104. kd = get_kvm()
  105. ifdatalen = kd.structsize('struct if_data')
  106. ifdata = create_string_buffer(ifdatalen)
  107. ifq = ifreq()
  108. ifq.ifr_name = iface.encode('us-ascii')
  109. ifq.ifr_ifru.ifru_data = ifdata
  110. r = fcntl.ioctl(s, sockio.SIOCGIFDATA, ifq)
  111. if r != 0:
  112. raise RuntimeError('ioctl returned %d')
  113. return kd.getstruct('struct if_data', ifdata)
  114. async def waitcarrier(iface):
  115. while True:
  116. res = await ifconfig(iface)
  117. if res[iface]['status'] == 'active':
  118. return
  119. await asyncio.sleep(.5)
  120. async def ifconfig(iface, *args, **kwargs):
  121. preargs = ()
  122. args = sum([ [k, str(v)] for k, v in kwargs.items() ], list(args))
  123. # Get detailed info about the interface if nothing is specified
  124. if not args:
  125. preargs = ('-m',)
  126. proc = await asyncio.create_subprocess_exec('/sbin/ifconfig', *preargs,
  127. iface, *args, stdout=asyncio.subprocess.PIPE,
  128. stderr=asyncio.subprocess.PIPE)
  129. stdout, stderr = await proc.communicate()
  130. if proc.returncode != 0:
  131. raise RuntimeError(stderr.decode('us-ascii').strip())
  132. if not stdout:
  133. return None
  134. # got something, parse it
  135. #print('ifc:', repr(stdout))
  136. stdout = stdout.decode('us-ascii')
  137. lines = stdout.split('\n')
  138. reg = '^(?P<iface>^.+): flags=[0-9a-f]{4,4}<(?P<flags>([A-Z0-9_]+(,[A-Z0-9_]+)*)?)> metric (?P<metric>[0-9]+) mtu (?P<mtu>[0-9]+)$'
  139. m = re.match(reg, lines[0])
  140. iface = m.group('iface')
  141. res = { iface: dict(mtu=int(m.group('mtu')), metric=int(m.group('metric')), flags=_makeflags(m.group('flags'))) }
  142. obj = res[iface]
  143. for i in lines[1:]:
  144. reg = '^\\toptions=[0-9a-f]{5,5}<(?P<options>([A-Z0-9_]+(,[A-Z0-9_]+)*)?)>$'
  145. m = re.match(reg, i)
  146. if m:
  147. obj['options'] = _makeflags(m.group('options'))
  148. continue
  149. reg = '^\\tcapabilities=[0-9a-f]{5,6}<(?P<capabilities>([A-Z0-9_]+(,[A-Z0-9_]+)*)?)>$'
  150. m = re.match(reg, i)
  151. if m:
  152. obj['capabilities'] = _makeflags(m.group('capabilities'))
  153. continue
  154. reg = '^\\tether (?P<ether>[0-9a-f]{2,2}(:[0-9a-f]{2,2}){5,5})$'
  155. m = re.match(reg, i)
  156. if m:
  157. obj['ether'] = m.group('ether')
  158. continue
  159. reg = '^\\tmedia: (?P<media>.+)$'
  160. m = re.match(reg, i)
  161. if m:
  162. obj['media'] = _parsemedia(m.group('media'))
  163. continue
  164. reg = '^\\tstatus: (?P<status>.+)$'
  165. m = re.match(reg, i)
  166. if m:
  167. obj['status'] = m.group('status')
  168. continue
  169. reg = '^\\tnd6 options=[0-9a-f]{2,2}<(?P<nd6options>([A-Z0-9_]+(,[A-Z0-9_]+)*)?)>$'
  170. m = re.match(reg, i)
  171. if m:
  172. obj['nd6'] = _makeflags(m.group('nd6options'))
  173. continue
  174. return res
  175. async def asendp(*args, **kwargs):
  176. '''a coroutine wrapping scapy sendp. All the arguments are
  177. the same, but you must await to get the results.'''
  178. loop = asyncio.get_running_loop()
  179. return await loop.run_in_executor(None, functools.partial(sendp, *args, **kwargs))
  180. class PacketManager(object):
  181. def __init__(self, iface):
  182. '''Create a context manager for sniffing packets on the
  183. interface specified by iface.
  184. Sniffing will only begin once used as a context manager.
  185. Example:
  186. async with PacketManager(iface) as pm:
  187. pkt = await pm.getpkt()
  188. '''
  189. self._iface = iface
  190. self._sniffer = None
  191. def enqueuepkt(self, pkt):
  192. '''Internal function to enqueue a received packet.'''
  193. # hopefully these routines are executed in order they are
  194. # scheduled. The Python docs does not assure that this this
  195. # will happen.
  196. #print('enq:', repr(pkt))
  197. asyncio.run_coroutine_threadsafe(self._queue.put(pkt), self._loop)
  198. async def __aenter__(self):
  199. if self._sniffer is not None:
  200. raise RuntimeError('already in a context')
  201. self._loop = asyncio.get_running_loop()
  202. self._queue = asyncio.Queue()
  203. self._sniffer = AsyncSniffer(iface=self._iface, prn=self.enqueuepkt)
  204. self._sniffer.start()
  205. return self
  206. async def __aexit__(self, exc_type, exc_value, traceback):
  207. await self._loop.run_in_executor(None, self._sniffer.stop)
  208. def getpkt(self, timeout=None):
  209. '''coroutine. Return the next available packet. If timeout is
  210. specified (in seconds), if a packet is not available in the timeout
  211. specified, the exception asyncio.TimeoutError is raised.'''
  212. return asyncio.wait_for(self._queue.get(), timeout)
  213. _mkpktdata = lambda: itertools.cycle(string.printable)
  214. _getpktdata = lambda *args: ''.join(itertools.islice(_mkpktdata(), *args)).encode()
  215. # Convert capability flags to their respective ifconfig command argument
  216. flagtoopt = dict(
  217. TXCSUM_IPV6='txcsum6',
  218. RXCSUM_IPV6='rxcsum6',
  219. TXCSUM='txcsum',
  220. RXCSUM='rxcsum',
  221. VLAN_HWTAGGING='vlanhwtag',
  222. )
  223. async def csuminternal(flag, testiface, checkiface):
  224. # XXX - I can't figure out how to get checksum to ensure that
  225. # the hardcoded check will ALWAYS be invalid
  226. if flag[0] == 'T':
  227. txiface, rxiface = testiface, checkiface
  228. else:
  229. return
  230. txiface, rxiface = checkiface, testiface
  231. # base packet
  232. p = Ether(src=get_if_hwaddr(txiface), dst=get_if_hwaddr(rxiface))
  233. # https://en.wikipedia.org/wiki/Reserved_IP_addresses
  234. if flag.endswith('_IPV6'):
  235. p = p / IP(src='192.168.0.1', dst='192.168.0.2',
  236. chksum=0xbadc, flags='DF')
  237. else:
  238. p = p / IPv6(src='fc00:0b5d:041c:7e37::7e37',
  239. dst='fc00:0b5d:041c:7e37::c43c')
  240. tcp = p / TCP(dport=443, flags='S', chksum=0xbadc)
  241. udp = p / UDP(dport=53, chksum=0xbadc) / \
  242. b'this is a udp checksum test packet'
  243. await ifconfig(checkiface, '-' + flagtoopt[flag])
  244. for pref in [ '-', '' ]:
  245. await ifconfig(testiface, pref + flagtoopt[flag])
  246. await ifconfig(testiface, 'up')
  247. await ifconfig(checkiface, 'up')
  248. await waitcarrier(testiface)
  249. await waitcarrier(checkiface)
  250. async with PacketManager(rxiface) as pm:
  251. await asendp(tcp, iface=txiface, verbose=False)
  252. try:
  253. rpkt = await pm.getpkt(timeout=.5)
  254. except asyncio.TimeoutError:
  255. raise RuntimeError('failed to receive checksum test')
  256. print('recv:', repr(rpkt))
  257. async def csumtest(testiface, checkiface):
  258. if False:
  259. raise ValueError('cannot be implemented this way, need the host'
  260. 'stack to set special mbuf flags, etc')
  261. csumtests = { 'RXCSUM', 'TXCSUM', 'RXCSUM_IPV6', 'TXCSUM_IPV6' }
  262. ifc = await ifconfig(testiface)
  263. print(ifc)
  264. for csum in csumtests.intersection(ifc[testiface]['capabilities']):
  265. print(repr(csum))
  266. await csuminternal(csum, testiface, checkiface)
  267. async def mtucheck(sndiface, rcviface, mtusize):
  268. # make sure packet is padded out to full frame size
  269. p = Ether(src=get_if_hwaddr(sndiface), dst=get_if_hwaddr(rcviface)) / \
  270. _getpktdata(mtusize - 14)
  271. #print('pktlen:', repr(p.build()))
  272. #print('sndiface:', repr(sndiface))
  273. #print('rcviface:', repr(rcviface))
  274. async with PacketManager(rcviface) as pm:
  275. try:
  276. await asendp(p, iface=sndiface, verbose=False)
  277. except OSError:
  278. raise RuntimeError('failed to send mtu size %d' %
  279. mtusize)
  280. try:
  281. rpkt = await pm.getpkt(timeout=.5)
  282. except asyncio.TimeoutError:
  283. raise RuntimeError('failed to receive mtu size %d' %
  284. mtusize)
  285. #print('got pkt:', repr(rpkt))
  286. if rpkt != p:
  287. raise RuntimeError(
  288. 'received packet did not match sent: %s != %s' %
  289. (repr(rpkt), repr(p)))
  290. async def mtutest(testiface, checkiface):
  291. for mtusize in [ 512, 1500, 1504, 2025, 4074, 7000, 8000, 9000, 9216 ]:
  292. try:
  293. await ifconfig(testiface, mtu=mtusize)
  294. await ifconfig(checkiface, mtu=mtusize)
  295. except RuntimeError:
  296. print('failed to set mtu %d, skipping...' % mtusize)
  297. continue
  298. print('mtu size:', mtusize)
  299. await ifconfig(testiface, 'up')
  300. await ifconfig(checkiface, 'up')
  301. await waitcarrier(testiface)
  302. await waitcarrier(checkiface)
  303. # if other way around ure won't work
  304. await mtucheck(testiface, checkiface, mtusize)
  305. await mtucheck(checkiface, testiface, mtusize)
  306. async def hwassisttest(testiface):
  307. '''Check to make sure that the hwassist flags follow the
  308. ifconfig options.
  309. '''
  310. ifconf = await ifconfig(testiface)
  311. caps = ifconf[testiface]['capabilities']
  312. basecsumflags = { 'RXCSUM', 'TXCSUM', 'RXCSUM_IPV6', 'TXCSUM_IPV6' }
  313. # get the supported flags
  314. supcsumflags = caps & basecsumflags
  315. # turn everything off
  316. await ifconfig(testiface, *('-%s' % flagtoopt[x] for x in supcsumflags))
  317. # make sure it is off
  318. ifconf = await ifconfig(testiface)
  319. if ifconf[testiface]['flags'] & basecsumflags:
  320. raise RuntimeError('failed to clear all the csum flags.')
  321. # make sure _hwassist is 0
  322. raise NotImplementedError('tbd')
  323. def main():
  324. testiface, checkiface = sys.argv[1:3]
  325. print('running...')
  326. if sys.argv[3] == 'mtu':
  327. asyncio.run(mtutest(testiface, checkiface))
  328. elif sys.argv[3] == 'csum':
  329. asyncio.run(csumtest(testiface, checkiface))
  330. elif sys.argv[3] == 'hwassist':
  331. asyncio.run(hwassisttest(testiface))
  332. print('done')
  333. if __name__ == '__main__':
  334. main()
  335. class _TestCase(unittest.IsolatedAsyncioTestCase):
  336. @patch('asyncio.sleep')
  337. @patch(__name__ + '.ifconfig')
  338. async def test_waitcarrier(self, ifc, asleep):
  339. cnt = [ 0 ]
  340. async def se(iface):
  341. cnt[0] += 1
  342. if cnt[0] < 5:
  343. return { iface: dict(status='no carrier') }
  344. return { iface: dict(status='active') }
  345. ifc.side_effect = se
  346. await waitcarrier('foo')
  347. ifc.assert_called_with('foo')
  348. # Better to have an event to wait on, but that isn't available
  349. asleep.assert_called_with(.5)
  350. @patch('asyncio.create_subprocess_exec')
  351. async def test_ifconf_err(self, cse):
  352. errmsg = \
  353. b'ifconfig: ioctl SIOCSIFMTU (set mtu): Invalid argument'
  354. wrap_subprocess_exec(cse, stderr=errmsg, retcode=1)
  355. with self.assertRaisesRegex(RuntimeError, re.escape(errmsg.decode())):
  356. await ifconfig('foobar', mtu=7000)
  357. @patch('asyncio.create_subprocess_exec')
  358. async def test_ifconf(self, cse):
  359. wrap_subprocess_exec(cse, b'')
  360. self.assertIsNone(await ifconfig('foobar', mtu=4000))
  361. cse.assert_called_with('/sbin/ifconfig', 'foobar',
  362. 'mtu', '4000', stdout=asyncio.subprocess.PIPE,
  363. stderr=asyncio.subprocess.PIPE)
  364. wrap_subprocess_exec(cse, b'')
  365. self.assertIsNone(await ifconfig('foobar', 'up'))
  366. cse.assert_called_with('/sbin/ifconfig', 'foobar',
  367. 'up', stdout=asyncio.subprocess.PIPE,
  368. stderr=asyncio.subprocess.PIPE)
  369. output = b'''foobar: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
  370. options=80188<VLAN_MTU,VLAN_HWCSUM,TSO4,LINKSTATE>
  371. capabilities=c019b<RXCSUM,TXCSUM,VLAN_MTU,VLAN_HWTAGGING,VLAN_HWCSUM,TSO4,VLAN_HWTSO,LINKSTATE>
  372. ether 52:12:34:56:78:90
  373. media: Ethernet autoselect (1000baseT <full-duplex>)
  374. status: active
  375. nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>
  376. '''
  377. res = dict(foobar=dict(
  378. flags={ 'UP','BROADCAST', 'RUNNING',
  379. 'SIMPLEX', 'MULTICAST' },
  380. metric=0, mtu=1500,
  381. capabilities={ 'RXCSUM', 'TXCSUM', 'VLAN_MTU',
  382. 'VLAN_HWTAGGING', 'VLAN_HWCSUM', 'TSO4',
  383. 'VLAN_HWTSO', 'LINKSTATE' },
  384. options={ 'VLAN_MTU', 'VLAN_HWCSUM', 'TSO4',
  385. 'LINKSTATE' },
  386. ether='52:12:34:56:78:90',
  387. media=dict(ethernet='autoselect',
  388. media=dict(medium='1000baseT',
  389. options={ 'full-duplex' })),
  390. status='active',
  391. nd6={ 'PERFORMNUD', 'IFDISABLED', 'AUTO_LINKLOCAL' },
  392. )
  393. )
  394. wrap_subprocess_exec(cse, output)
  395. ret = await ifconfig('foobar')
  396. cse.assert_called_with('/sbin/ifconfig', '-m', 'foobar',
  397. stdout=asyncio.subprocess.PIPE,
  398. stderr=asyncio.subprocess.PIPE)
  399. self.assertEqual(ret, res)
  400. output = b'''foobar: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
  401. options=80188<VLAN_MTU,VLAN_HWCSUM,TSO4,LINKSTATE>
  402. capabilities=68009b<RXCSUM,TXCSUM,VLAN_MTU,VLAN_HWTAGGING,VLAN_HWCSUM,LINKSTATE,RXCSUM_IPV6,TXCSUM_IPV6>
  403. ether 52:12:34:56:78:90
  404. media: Ethernet autoselect (1000baseT <full-duplex>)
  405. status: active
  406. nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>
  407. '''
  408. res = dict(foobar=dict(
  409. flags={ 'UP','BROADCAST', 'RUNNING',
  410. 'SIMPLEX', 'MULTICAST' },
  411. metric=0, mtu=1500,
  412. capabilities={ 'RXCSUM', 'TXCSUM', 'VLAN_MTU',
  413. 'VLAN_HWTAGGING', 'VLAN_HWCSUM', 'LINKSTATE',
  414. 'RXCSUM_IPV6', 'TXCSUM_IPV6', },
  415. options={ 'VLAN_MTU', 'VLAN_HWCSUM', 'TSO4',
  416. 'LINKSTATE' },
  417. ether='52:12:34:56:78:90',
  418. media=dict(ethernet='autoselect',
  419. media=dict(medium='1000baseT',
  420. options={ 'full-duplex' })),
  421. status='active',
  422. nd6={ 'PERFORMNUD', 'IFDISABLED', 'AUTO_LINKLOCAL' },
  423. )
  424. )
  425. wrap_subprocess_exec(cse, output)
  426. ret = await ifconfig('foobar')
  427. cse.assert_called_with('/sbin/ifconfig', '-m', 'foobar',
  428. stdout=asyncio.subprocess.PIPE,
  429. stderr=asyncio.subprocess.PIPE)
  430. self.assertEqual(ret, res)
  431. output = b'''foobar: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
  432. options=80188<VLAN_MTU,VLAN_HWCSUM,TSO4,LINKSTATE>
  433. ether 52:12:34:56:78:90
  434. media: Ethernet autoselect (1000baseT <full-duplex>)
  435. status: no carrier
  436. nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>
  437. '''
  438. res = dict(foobar=dict(
  439. flags={ 'UP','BROADCAST', 'RUNNING',
  440. 'SIMPLEX', 'MULTICAST' },
  441. metric=0, mtu=1500,
  442. options={ 'VLAN_MTU', 'VLAN_HWCSUM', 'TSO4',
  443. 'LINKSTATE' },
  444. ether='52:12:34:56:78:90',
  445. media=dict(ethernet='autoselect',
  446. media=dict(medium='1000baseT',
  447. options={ 'full-duplex' })),
  448. status='no carrier',
  449. nd6={ 'PERFORMNUD', 'IFDISABLED', 'AUTO_LINKLOCAL' },
  450. )
  451. )
  452. wrap_subprocess_exec(cse, output)
  453. ret = await ifconfig('foobar')
  454. self.assertEqual(ret, res)
  455. @patch(__name__ + '.sendp')
  456. async def test_asendp(self, sndp):
  457. kwargs = dict(a=5, b='foo')
  458. pkt = object()
  459. retv = object()
  460. sndp.return_value = retv
  461. r = await asendp(pkt, **kwargs)
  462. self.assertIs(r, retv)
  463. sndp.assert_called_with(pkt, **kwargs)
  464. @patch(__name__ + '.AsyncSniffer')
  465. async def test_pktmgr(self, asyncs):
  466. # That it can be a context manager
  467. async with PacketManager('iface') as pm:
  468. # that a RuntimeError is raised
  469. with self.assertRaises(RuntimeError):
  470. # when it's used as a context manager again
  471. async with pm as foo:
  472. pass
  473. # That it created a queue
  474. self.assertIsInstance(pm._queue, asyncio.Queue)
  475. # that it called asyncsniffer with the correct arguments
  476. asyncs.assert_called_with(iface='iface', prn=pm.enqueuepkt)
  477. # that it was started
  478. asyncs().start.assert_called()
  479. # that when a pkt
  480. pkt = object()
  481. # is enqueued
  482. pm.enqueuepkt(pkt)
  483. # that getpkt returns it
  484. self.assertIs(await pm.getpkt(), pkt)
  485. # that a timeout parameter can be provided
  486. with self.assertRaises(asyncio.TimeoutError):
  487. await pm.getpkt(timeout=0)
  488. # that when the context manager stops, stop is called
  489. asyncs().stop.assert_called()
  490. def test_ifdata(self):
  491. r = if_data('ue0')
  492. print('if:', repr(r))