commit e4ee0d41426bf641aa7927afd5b80764b09dad4f Author: John-Mark Gurney Date: Tue Feb 2 22:53:27 2021 +0000 set of files that are in progress for testing FreeBSD ethernet interfaces.. This is in progress, and documentation will follow.. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..98594a3 --- /dev/null +++ b/Makefile @@ -0,0 +1,17 @@ +MODULES=testeth +VIRTUALENV?=python3.8 -m virtualenv + +test: + (ls kvm.py $(MODULES).py | entr sh -c 'python -m coverage run -m unittest -f $(MODULES) && coverage report --omit=p/\* -m -i') + +env: + ($(VIRTUALENV) p && . ./p/bin/activate && pip install -r requirements.txt) + +sockio.py: ioctlgen + ./ioctlgen > $@ || rm -- $@ + +ioctlgen: ioctlgen.awk Makefile + awk -f ioctlgen.awk < /usr/include/sys/sockio.h| cc -x c -o ioctlgen - + +testioctlgen: + ls ioctlgen.awk Makefile | entr sh -c 'clear; make sockio.py 2>&1 | head -n 20 && cat sockio.py' diff --git a/bpf.py b/bpf.py new file mode 100644 index 0000000..966b1d4 --- /dev/null +++ b/bpf.py @@ -0,0 +1,139 @@ +# git+https://github.com/secdev/scapy.git + +import fcntl +import os +import struct +import sys + +from scapy.all import * +from scapy.arch.bpf.consts import BIOCGBLEN, BIOCGDLT, BIOCSDLT, BIOCSETIF, BIOCIMMEDIATE + +from ctypes import Structure, c_char, c_long, c_int, c_uint, c_ushort, c_uint16, c_uint32, sizeof + +class ReprStructureMixin: + def format_field(self, k): + try: + bits = getattr(self, '_bits_%s' % k) + return str(makebits(getattr(self, k), bits)) + except AttributeError: + return repr(getattr(self, k)) + + def __repr__(self): + args = tuple(self.format_field(x[0]) for x in self._fields_) + return '%s(%s)' % (self.__class__.__name__, ', '.join(args)) + +class Timeval(Structure, ReprStructureMixin): + _fields_ = [ + ('tv_sec', c_long), + ('tv_usec', c_long), + ] + +class bpf_hdr(Structure, ReprStructureMixin): + _fields_ = [ + ('bh_tstamp', Timeval), + ('bh_caplen', c_uint32), + ('bh_datalen', c_uint32), + ('bh_hdrlen', c_ushort), + ] + +pkthdrbits = { 1: 'CSUM', 2: 'VLAN_TAG', 4: 'TSTMP' } +# awk '{ print $3 ": '"'"'" $2 "'"'"'," }' +csumflagbits = { + 0x00000001: 'CSUM_IP', + 0x00000002: 'CSUM_IP_UDP', + 0x00000004: 'CSUM_IP_TCP', + 0x00000008: 'CSUM_IP_SCTP', + 0x00000010: 'CSUM_IP_TSO', + 0x00000020: 'CSUM_IP_ISCSI', + 0x00000040: 'CSUM_INNER_IP6_UDP', + 0x00000080: 'CSUM_INNER_IP6_TCP', + 0x00000100: 'CSUM_INNER_IP6_TSO', + 0x00000200: 'CSUM_IP6_UDP', + 0x00000400: 'CSUM_IP6_TCP', + 0x00000800: 'CSUM_IP6_SCTP', + 0x00001000: 'CSUM_IP6_TSO', + 0x00002000: 'CSUM_IP6_ISCSI', + 0x00004000: 'CSUM_INNER_IP', + 0x00008000: 'CSUM_INNER_IP_UDP', + 0x00010000: 'CSUM_INNER_IP_TCP', + 0x00020000: 'CSUM_INNER_IP_TSO', + 0x00040000: 'CSUM_ENCAP_VXLAN', + 0x00080000: 'CSUM_ENCAP_RSVD1', + 0x00100000: 'CSUM_INNER_L3_CALC', + 0x00200000: 'CSUM_INNER_L3_VALID', + 0x00400000: 'CSUM_INNER_L4_CALC', + 0x00800000: 'CSUM_INNER_L4_VALID', + 0x01000000: 'CSUM_L3_CALC', + 0x02000000: 'CSUM_L3_VALID', + 0x04000000: 'CSUM_L4_CALC', + 0x08000000: 'CSUM_L4_VALID', + 0x10000000: 'CSUM_L5_CALC', + 0x20000000: 'CSUM_L5_VALID', + 0x40000000: 'CSUM_COALESCED', + 0x80000000: 'CSUM_SND_TAG', +} + +class fbsdpkthdr(Structure, ReprStructureMixin): + _bits_fph_bits = pkthdrbits + _bits_fph_csum_flags = csumflagbits + _fields_ = [ + ('fph_magic', c_char * 4), + ('fph_hdrlen', c_uint32), + ('fph_recvif', c_char * 16), + ('fph_bits', c_uint32), + ('fph_flowid', c_uint32), + ('fph_csum_flags', c_uint32), + ('fph_csum_data', c_uint32), + ('fph_vlan_tag', c_uint16), + ] + +class BPF: + def __init__(self, iface): + f = self._fp = os.open('/dev/bpf', os.O_RDONLY) + l = fcntl.ioctl(f, BIOCGBLEN, b'\x00'*4, True) + sz = c_int.from_buffer_copy(l) + + self._blen = sz.value + + fcntl.ioctl(f, BIOCIMMEDIATE, struct.pack('I', 1)) + fcntl.ioctl(f, BIOCSETIF, struct.pack('16s16x', iface.encode())) + fcntl.ioctl(f, BIOCSDLT, struct.pack('I', 154)) + + def getpkt(self): + bytes = os.read(self._fp, self._blen) + + bpfhdr = bpf_hdr.from_buffer_copy(bytes) + + off = bpfhdr.bh_hdrlen + fbsdhdr = fbsdpkthdr.from_buffer_copy(bytes, off) + off += fbsdhdr.fph_hdrlen + pkt = bytes[off:] + + return fbsdhdr, pkt + +def makebits(val, bits): + r = [ v for k, v in bits.items() if k & val ] + + if not r: + return 0 + + return '|'.join(r) + +if __name__ == '__main__': + bpf = BPF(sys.argv[1]) + + while True: + hdr, pkt = bpf.getpkt() + print(repr(hdr)) + #pkthdr = struct.unpack(fbsdpkthdr, pkt[hdrlen:hdrlen + fbsdpkthdrlen]) + #pkthdr = list(pkthdr) + #pkthdr[2] = pkthdr[2].strip(b'\x00').decode('us-ascii') + #print(repr(pkthdr), fbsdpkthdrlen) + if hdr.fph_magic != b'FBSD': + print('magic wrong') + continue + # XXX calcsize is wrong here + if sizeof(hdr) != hdr.fph_hdrlen: + print('length mismatch') + continue + print(repr(Ether(pkt))[:300]) diff --git a/ioctlgen.awk b/ioctlgen.awk new file mode 100644 index 0000000..7862b6b --- /dev/null +++ b/ioctlgen.awk @@ -0,0 +1,27 @@ +#!/usr/bin/awk + +BEGIN { + print "#include " + print "#include " + print "#include " + print "#include " + print "#include " + print "#include " + print "#include " + print "#include " + print "#include " + print "#include " + + print "int" + print "main()" + print "{" +} + +$1 == "#define" && $3 ~ "^_IO[WR]" { + printf("\tprintf(\"%s = %%ld\\n\", %s);\n", $2, $2) +} + +END { + print " return 0;" + print "}" +} diff --git a/kvm.py b/kvm.py new file mode 100644 index 0000000..0f70121 --- /dev/null +++ b/kvm.py @@ -0,0 +1,157 @@ +import os +import sys +import unittest + +from ctypes import * + +__all__ = [ 'KVM' ] + +_kvm = CDLL('libkvm.so') + +class kvm_t(Structure): + pass + +class timespec(Structure): + _fields_ = [ + ('tv_sec', c_ulong), + ('tv_nsec', c_long), + ] + +KVM_K_UNSIGNED_INT = 1 +KVM_K_SIGNED_INT = 2 + +_bytesizes = [ 1, 2, 4, 8 ] +_ttvlookup = [ ((KVM_K_UNSIGNED_INT, x), globals()['c_uint%d' % (x * 8)]) for x + in _bytesizes ] + [ ((KVM_K_SIGNED_INT, x), + globals()['c_int%d' % (x * 8)]) for x in _bytesizes ] +_ttvlookup = { k: POINTER(v) for k, v in _ttvlookup } + +kvm_t_p = POINTER(kvm_t) + +kvm_iter_struct_t = CFUNCTYPE(c_int, c_char_p, c_int, c_size_t, + POINTER(c_char), c_void_p) + +_funs = dict( + kvm_open=(kvm_t_p, (c_char_p, c_char_p, c_char_p, c_int, c_char_p)), + kvm_close=(c_int, (kvm_t_p,)), + + kvm_geterr=(c_char_p, (kvm_t_p,)), + + kvm_structsize=(c_ssize_t, (kvm_t_p, c_char_p)), + kvm_iterstruct=(c_int, (kvm_t_p, c_char_p, c_void_p, + kvm_iter_struct_t, c_void_p)), +) + +for k, v in _funs.items(): + f = getattr(_kvm, k) + f.restype, f.argtypes = v + +def _fetchmembers(kd, typ, obj): + res = [] + def func(memb, type, len, buf, arg): + t = _ttvlookup[(type, len)] + res.append((memb, cast(buf, t)[0])) + + return 0 + + cbfun = kvm_iter_struct_t(func) + + r = _kvm.kvm_iterstruct(kd, typ, byref(obj), cbfun, None) + + if r == -1: + err = _kvm.kvm_geterr(kd) + raise RuntimeError(err.decode('us-ascii')) + + return res + +class KVM(object): + def __init__(self): + self.kd = _kvm.kvm_open(None, None, None, os.O_RDONLY, None) + + def __enter__(self): + return self + + def close(self): + if self.kd is not None: + _kvm.kvm_close(self.kd) + self.kd = None + + def __exit__(self, a, b, c): + self.close() + + def _iferr(self, fun, *args): + res = fun(*args) + if res == -1: + err = _kvm.kvm_geterr(self.kd) + raise RuntimeError(err.decode('us-ascii')) + + return res + + def structsize(self, typ): + return self._iferr(_kvm.kvm_structsize, self.kd, + typ.encode('us-ascii')) + + def getstruct(self, typ, obj): + res = _fetchmembers(self.kd, typ.encode('us-ascii'), obj) + + return { k.decode('us-ascii'): v for k, v in res } + + def __del__(self): + self.close() + +def deb(*args): + if True: #pragma: no cover + print(*args) + sys.stdout.flush() + +class _TestCase(unittest.TestCase): + def setUp(self): + self.kd = _kvm.kvm_open(None, None, None, os.O_RDONLY, None) + + def tearDown(self): + _kvm.kvm_close(self.kd) + self.kd = None + + def test_ss(self): + self.assertEqual(_kvm.kvm_structsize(self.kd, + b'struct timespec'), 16) + + def test_iter(self): + exp = [ + (b'tv_sec', 0x1234), + (b'tv_nsec', 0xabcd), + ] + + ts = timespec(0x1234, 0xabcd) + + res = _fetchmembers(self.kd, b'struct timespec', ts) + + self.assertEqual(res, exp) + + def test_class_errs(self): + kd = KVM() + + self.assertEqual(kd.structsize('struct timespec'), 16) + + sec = 1839238 + nsec = 19849873 + ts = timespec(sec, nsec) + + res = dict( + tv_sec=sec, + tv_nsec=nsec, + ) + + self.assertEqual(kd.getstruct('struct timespec', ts), res) + def test_class(self): + kd = KVM() + + with KVM() as kd: + with self.assertRaisesRegex(RuntimeError, + 'unable to find kernel type: struct flksjdi'): + kd.structsize('struct flksjdi') + + ts = timespec(0, 0) + with self.assertRaisesRegex(RuntimeError, + 'unable to find kernel type: struct weoiud'): + kd.getstruct('struct weoiud', ts) diff --git a/mocks.py b/mocks.py new file mode 100644 index 0000000..f9a63f3 --- /dev/null +++ b/mocks.py @@ -0,0 +1,47 @@ +# !!fetch -q -o - https://www.funkthat.com/gitea/jmg/bitelab/raw/branch/main/bitelab/mocks.py +# +# Copyright (c) 2020 The FreeBSD Foundation +# +# This software1 was developed by John-Mark Gurney under sponsorship +# from the FreeBSD Foundation. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# + +from unittest.mock import AsyncMock, Mock + +__all__ = [ 'wrap_subprocess_exec' ] + +def wrap_subprocess_exec(mockobj, stdout=b'', stderr=b'', retcode=0): + assert isinstance(stdout, bytes) + assert isinstance(stderr, bytes) + proc = Mock() + proc.communicate = AsyncMock() + proc.communicate.return_value = (stdout, stderr) + proc.stdout.read = AsyncMock() + proc.stdout.read.side_effect = [ stdout, b'' ] + proc.stdin.drain = AsyncMock() + proc.stdin.wait_closed = AsyncMock() + proc.wait = AsyncMock() + proc.wait.return_value = retcode + proc.returncode = retcode + mockobj.return_value = proc diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b0aad48 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +git+https://github.com/secdev/scapy.git +coverage diff --git a/testeth.py b/testeth.py new file mode 100644 index 0000000..dd98fd3 --- /dev/null +++ b/testeth.py @@ -0,0 +1,641 @@ +# +# Copyright 2021 John-Mark Gurney. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + + +# from: https://en.wikipedia.org/wiki/Reserved_IP_addresses +# TEST-NET-1: 192.0.2.0/24 +# TEST-NET-2: 198.51.100.0/24 +# TEST-NET-3: 203.0.113.0/24 + +from unittest.mock import patch +from mocks import * +import unittest + +# Silence useless warning: WARNING: No IPv4 address found on +import scapy.error +scapy.error.warning = lambda *a, **kw: None +from scapy.all import * + +from kvm import _TestCase as _KVMTestCase +from kvm import KVM + +from bpf import Timeval +from ctypes import Structure, Union, POINTER, sizeof, create_string_buffer, byref +from ctypes import c_char, c_void_p, c_uint8, c_uint16, c_uint32, c_int64, c_uint64 + +import asyncio +import fcntl +import functools +import itertools +import re +import sockio +import string +import sys + +class if_data(Structure): + _fields_ = [ + ('ifi_type', c_uint8), + ('ifi_physical', c_uint8), + ('ifi_addrlen', c_uint8), + ('ifi_hdrlen', c_uint8), + ('ifi_link_state', c_uint8), + ('ifi_vhid', c_uint8), + ('ifi_datalen', c_uint16), + ('ifi_mtu', c_uint32), + ('ifi_metric', c_uint32), + ('ifi_baudrate', c_uint64), + ('ifi_ipackets', c_uint64), + ('ifi_ierrors', c_uint64), + ('ifi_opackets', c_uint64), + ('ifi_collisions', c_uint64), + ('ifi_ibytes', c_uint64), + ('ifi_obytes', c_uint64), + ('ifi_imcasts', c_uint64), + ('ifi_omcasts', c_uint64), + ('ifi_iqdrops', c_uint64), + ('ifi_oqdrops', c_uint64), + ('ifi_noproto', c_uint64), + ('ifi_hwassist', c_uint64), + ('ifi_epoch', c_int64), # XXX - broken on i386 + ('ifi_lastchange', Timeval), # XXX - broken on 32-bit platforms + ] + +class ifr_ifru(Union): + _fields_ = [ + ('ifru_data', POINTER(c_char)), + ] + +class ifreq(Structure): + _fields_ = [ + ('ifr_name', c_char * 16), + ('ifr_ifru', ifr_ifru), + ] + +def _makeflags(s): + return set(s.split(',')) + +def _parsemedia(s): + reg = '^Ethernet autoselect (1000baseT )$' + m = re.match(reg, s) + + return dict(ethernet='autoselect', media=dict(medium='1000baseT', options={ 'full-duplex' })) + +__ifreqsock = None +def get_ifreqsock(): + global __ifreqsock + + if __ifreqsock == None: + __ifreqsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + + return __ifreqsock + +__kvm = None +def get_kvm(): + global __kvm + + if __kvm == None: + __kvm = KVM() + + return __kvm + +def if_data(iface): + s = get_ifreqsock() + + kd = get_kvm() + + ifdatalen = kd.structsize('struct if_data') + ifdata = create_string_buffer(ifdatalen) + + ifq = ifreq() + + ifq.ifr_name = iface.encode('us-ascii') + ifq.ifr_ifru.ifru_data = ifdata + + r = fcntl.ioctl(s, sockio.SIOCGIFDATA, ifq) + + if r != 0: + raise RuntimeError('ioctl returned %d') + + return kd.getstruct('struct if_data', ifdata) + +async def waitcarrier(iface): + while True: + res = await ifconfig(iface) + + if res[iface]['status'] == 'active': + return + + await asyncio.sleep(.5) + +async def ifconfig(iface, *args, **kwargs): + preargs = () + args = sum([ [k, str(v)] for k, v in kwargs.items() ], list(args)) + + # Get detailed info about the interface if nothing is specified + if not args: + preargs = ('-m',) + + proc = await asyncio.create_subprocess_exec('/sbin/ifconfig', *preargs, + iface, *args, stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE) + + stdout, stderr = await proc.communicate() + + if proc.returncode != 0: + raise RuntimeError(stderr.decode('us-ascii').strip()) + + if not stdout: + return None + + # got something, parse it + #print('ifc:', repr(stdout)) + stdout = stdout.decode('us-ascii') + lines = stdout.split('\n') + + reg = '^(?P^.+): flags=[0-9a-f]{4,4}<(?P([A-Z0-9_]+(,[A-Z0-9_]+)*)?)> metric (?P[0-9]+) mtu (?P[0-9]+)$' + m = re.match(reg, lines[0]) + + iface = m.group('iface') + res = { iface: dict(mtu=int(m.group('mtu')), metric=int(m.group('metric')), flags=_makeflags(m.group('flags'))) } + + obj = res[iface] + + for i in lines[1:]: + reg = '^\\toptions=[0-9a-f]{5,5}<(?P([A-Z0-9_]+(,[A-Z0-9_]+)*)?)>$' + m = re.match(reg, i) + if m: + obj['options'] = _makeflags(m.group('options')) + continue + + reg = '^\\tcapabilities=[0-9a-f]{5,6}<(?P([A-Z0-9_]+(,[A-Z0-9_]+)*)?)>$' + m = re.match(reg, i) + if m: + obj['capabilities'] = _makeflags(m.group('capabilities')) + continue + + reg = '^\\tether (?P[0-9a-f]{2,2}(:[0-9a-f]{2,2}){5,5})$' + m = re.match(reg, i) + if m: + obj['ether'] = m.group('ether') + continue + + reg = '^\\tmedia: (?P.+)$' + m = re.match(reg, i) + if m: + obj['media'] = _parsemedia(m.group('media')) + continue + + reg = '^\\tstatus: (?P.+)$' + m = re.match(reg, i) + if m: + obj['status'] = m.group('status') + continue + + reg = '^\\tnd6 options=[0-9a-f]{2,2}<(?P([A-Z0-9_]+(,[A-Z0-9_]+)*)?)>$' + m = re.match(reg, i) + if m: + obj['nd6'] = _makeflags(m.group('nd6options')) + continue + + return res + +async def asendp(*args, **kwargs): + '''a coroutine wrapping scapy sendp. All the arguments are + the same, but you must await to get the results.''' + + loop = asyncio.get_running_loop() + + return await loop.run_in_executor(None, functools.partial(sendp, *args, **kwargs)) + +class PacketManager(object): + def __init__(self, iface): + '''Create a context manager for sniffing packets on the + interface specified by iface. + + Sniffing will only begin once used as a context manager. + + Example: + async with PacketManager(iface) as pm: + pkt = await pm.getpkt() + ''' + + self._iface = iface + self._sniffer = None + + def enqueuepkt(self, pkt): + '''Internal function to enqueue a received packet.''' + + # hopefully these routines are executed in order they are + # scheduled. The Python docs does not assure that this this + # will happen. + #print('enq:', repr(pkt)) + asyncio.run_coroutine_threadsafe(self._queue.put(pkt), self._loop) + + async def __aenter__(self): + if self._sniffer is not None: + raise RuntimeError('already in a context') + + self._loop = asyncio.get_running_loop() + self._queue = asyncio.Queue() + self._sniffer = AsyncSniffer(iface=self._iface, prn=self.enqueuepkt) + self._sniffer.start() + + return self + + async def __aexit__(self, exc_type, exc_value, traceback): + await self._loop.run_in_executor(None, self._sniffer.stop) + + def getpkt(self, timeout=None): + '''coroutine. Return the next available packet. If timeout is + specified (in seconds), if a packet is not available in the timeout + specified, the exception asyncio.TimeoutError is raised.''' + + return asyncio.wait_for(self._queue.get(), timeout) + +_mkpktdata = lambda: itertools.cycle(string.printable) +_getpktdata = lambda *args: ''.join(itertools.islice(_mkpktdata(), *args)).encode() + +# Convert capability flags to their respective ifconfig command argument +flagtoopt = dict( + TXCSUM_IPV6='txcsum6', + RXCSUM_IPV6='rxcsum6', + TXCSUM='txcsum', + RXCSUM='rxcsum', + VLAN_HWTAGGING='vlanhwtag', +) + +async def csuminternal(flag, testiface, checkiface): + # XXX - I can't figure out how to get checksum to ensure that + # the hardcoded check will ALWAYS be invalid + if flag[0] == 'T': + txiface, rxiface = testiface, checkiface + else: + return + txiface, rxiface = checkiface, testiface + + # base packet + p = Ether(src=get_if_hwaddr(txiface), dst=get_if_hwaddr(rxiface)) + + # https://en.wikipedia.org/wiki/Reserved_IP_addresses + if flag.endswith('_IPV6'): + p = p / IP(src='192.168.0.1', dst='192.168.0.2', + chksum=0xbadc, flags='DF') + else: + p = p / IPv6(src='fc00:0b5d:041c:7e37::7e37', + dst='fc00:0b5d:041c:7e37::c43c') + + tcp = p / TCP(dport=443, flags='S', chksum=0xbadc) + udp = p / UDP(dport=53, chksum=0xbadc) / \ + b'this is a udp checksum test packet' + + + await ifconfig(checkiface, '-' + flagtoopt[flag]) + + for pref in [ '-', '' ]: + await ifconfig(testiface, pref + flagtoopt[flag]) + + await ifconfig(testiface, 'up') + await ifconfig(checkiface, 'up') + + await waitcarrier(testiface) + await waitcarrier(checkiface) + + async with PacketManager(rxiface) as pm: + await asendp(tcp, iface=txiface, verbose=False) + + try: + rpkt = await pm.getpkt(timeout=.5) + except asyncio.TimeoutError: + raise RuntimeError('failed to receive checksum test') + + print('recv:', repr(rpkt)) + + +async def csumtest(testiface, checkiface): + if False: + raise ValueError('cannot be implemented this way, need the host' + 'stack to set special mbuf flags, etc') + csumtests = { 'RXCSUM', 'TXCSUM', 'RXCSUM_IPV6', 'TXCSUM_IPV6' } + ifc = await ifconfig(testiface) + + print(ifc) + for csum in csumtests.intersection(ifc[testiface]['capabilities']): + print(repr(csum)) + await csuminternal(csum, testiface, checkiface) + +async def mtucheck(sndiface, rcviface, mtusize): + + # make sure packet is padded out to full frame size + p = Ether(src=get_if_hwaddr(sndiface), dst=get_if_hwaddr(rcviface)) / \ + _getpktdata(mtusize - 14) + + #print('pktlen:', repr(p.build())) + #print('sndiface:', repr(sndiface)) + #print('rcviface:', repr(rcviface)) + async with PacketManager(rcviface) as pm: + try: + await asendp(p, iface=sndiface, verbose=False) + except OSError: + raise RuntimeError('failed to send mtu size %d' % + mtusize) + + try: + rpkt = await pm.getpkt(timeout=.5) + except asyncio.TimeoutError: + raise RuntimeError('failed to receive mtu size %d' % + mtusize) + + #print('got pkt:', repr(rpkt)) + if rpkt != p: + raise RuntimeError( + 'received packet did not match sent: %s != %s' % + (repr(rpkt), repr(p))) + +async def mtutest(testiface, checkiface): + for mtusize in [ 512, 1500, 1504, 2025, 4074, 7000, 8000, 9000, 9216 ]: + try: + await ifconfig(testiface, mtu=mtusize) + await ifconfig(checkiface, mtu=mtusize) + except RuntimeError: + print('failed to set mtu %d, skipping...' % mtusize) + continue + + print('mtu size:', mtusize) + + await ifconfig(testiface, 'up') + await ifconfig(checkiface, 'up') + + await waitcarrier(testiface) + await waitcarrier(checkiface) + + # if other way around ure won't work + await mtucheck(testiface, checkiface, mtusize) + await mtucheck(checkiface, testiface, mtusize) + +async def hwassisttest(testiface): + '''Check to make sure that the hwassist flags follow the + ifconfig options. + ''' + + ifconf = await ifconfig(testiface) + caps = ifconf[testiface]['capabilities'] + + basecsumflags = { 'RXCSUM', 'TXCSUM', 'RXCSUM_IPV6', 'TXCSUM_IPV6' } + + # get the supported flags + supcsumflags = caps & basecsumflags + + # turn everything off + await ifconfig(testiface, *('-%s' % flagtoopt[x] for x in supcsumflags)) + + # make sure it is off + ifconf = await ifconfig(testiface) + + if ifconf[testiface]['flags'] & basecsumflags: + raise RuntimeError('failed to clear all the csum flags.') + + # make sure _hwassist is 0 + raise NotImplementedError('tbd') + +def main(): + testiface, checkiface = sys.argv[1:3] + + print('running...') + + if sys.argv[3] == 'mtu': + asyncio.run(mtutest(testiface, checkiface)) + elif sys.argv[3] == 'csum': + asyncio.run(csumtest(testiface, checkiface)) + elif sys.argv[3] == 'hwassist': + asyncio.run(hwassisttest(testiface)) + + print('done') + +if __name__ == '__main__': + main() + +class _TestCase(unittest.IsolatedAsyncioTestCase): + @patch('asyncio.sleep') + @patch(__name__ + '.ifconfig') + async def test_waitcarrier(self, ifc, asleep): + cnt = [ 0 ] + async def se(iface): + cnt[0] += 1 + + if cnt[0] < 5: + return { iface: dict(status='no carrier') } + + return { iface: dict(status='active') } + + ifc.side_effect = se + + await waitcarrier('foo') + + ifc.assert_called_with('foo') + + # Better to have an event to wait on, but that isn't available + asleep.assert_called_with(.5) + + @patch('asyncio.create_subprocess_exec') + async def test_ifconf_err(self, cse): + errmsg = \ + b'ifconfig: ioctl SIOCSIFMTU (set mtu): Invalid argument' + wrap_subprocess_exec(cse, stderr=errmsg, retcode=1) + + with self.assertRaisesRegex(RuntimeError, re.escape(errmsg.decode())): + await ifconfig('foobar', mtu=7000) + + @patch('asyncio.create_subprocess_exec') + async def test_ifconf(self, cse): + wrap_subprocess_exec(cse, b'') + + self.assertIsNone(await ifconfig('foobar', mtu=4000)) + + cse.assert_called_with('/sbin/ifconfig', 'foobar', + 'mtu', '4000', stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE) + + wrap_subprocess_exec(cse, b'') + + self.assertIsNone(await ifconfig('foobar', 'up')) + + cse.assert_called_with('/sbin/ifconfig', 'foobar', + 'up', stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE) + + output = b'''foobar: flags=8843 metric 0 mtu 1500 + options=80188 + capabilities=c019b + ether 52:12:34:56:78:90 + media: Ethernet autoselect (1000baseT ) + status: active + nd6 options=29 +''' + + res = dict(foobar=dict( + flags={ 'UP','BROADCAST', 'RUNNING', + 'SIMPLEX', 'MULTICAST' }, + metric=0, mtu=1500, + capabilities={ 'RXCSUM', 'TXCSUM', 'VLAN_MTU', + 'VLAN_HWTAGGING', 'VLAN_HWCSUM', 'TSO4', + 'VLAN_HWTSO', 'LINKSTATE' }, + options={ 'VLAN_MTU', 'VLAN_HWCSUM', 'TSO4', + 'LINKSTATE' }, + ether='52:12:34:56:78:90', + media=dict(ethernet='autoselect', + media=dict(medium='1000baseT', + options={ 'full-duplex' })), + status='active', + nd6={ 'PERFORMNUD', 'IFDISABLED', 'AUTO_LINKLOCAL' }, + ) + ) + + wrap_subprocess_exec(cse, output) + + ret = await ifconfig('foobar') + + cse.assert_called_with('/sbin/ifconfig', '-m', 'foobar', + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE) + + self.assertEqual(ret, res) + + output = b'''foobar: flags=8843 metric 0 mtu 1500 + options=80188 + capabilities=68009b + ether 52:12:34:56:78:90 + media: Ethernet autoselect (1000baseT ) + status: active + nd6 options=29 +''' + + res = dict(foobar=dict( + flags={ 'UP','BROADCAST', 'RUNNING', + 'SIMPLEX', 'MULTICAST' }, + metric=0, mtu=1500, + capabilities={ 'RXCSUM', 'TXCSUM', 'VLAN_MTU', + 'VLAN_HWTAGGING', 'VLAN_HWCSUM', 'LINKSTATE', + 'RXCSUM_IPV6', 'TXCSUM_IPV6', }, + options={ 'VLAN_MTU', 'VLAN_HWCSUM', 'TSO4', + 'LINKSTATE' }, + ether='52:12:34:56:78:90', + media=dict(ethernet='autoselect', + media=dict(medium='1000baseT', + options={ 'full-duplex' })), + status='active', + nd6={ 'PERFORMNUD', 'IFDISABLED', 'AUTO_LINKLOCAL' }, + ) + ) + + wrap_subprocess_exec(cse, output) + + ret = await ifconfig('foobar') + + cse.assert_called_with('/sbin/ifconfig', '-m', 'foobar', + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE) + + self.assertEqual(ret, res) + + output = b'''foobar: flags=8843 metric 0 mtu 1500 + options=80188 + ether 52:12:34:56:78:90 + media: Ethernet autoselect (1000baseT ) + status: no carrier + nd6 options=29 +''' + + res = dict(foobar=dict( + flags={ 'UP','BROADCAST', 'RUNNING', + 'SIMPLEX', 'MULTICAST' }, + metric=0, mtu=1500, + options={ 'VLAN_MTU', 'VLAN_HWCSUM', 'TSO4', + 'LINKSTATE' }, + ether='52:12:34:56:78:90', + media=dict(ethernet='autoselect', + media=dict(medium='1000baseT', + options={ 'full-duplex' })), + status='no carrier', + nd6={ 'PERFORMNUD', 'IFDISABLED', 'AUTO_LINKLOCAL' }, + ) + ) + + wrap_subprocess_exec(cse, output) + + ret = await ifconfig('foobar') + + self.assertEqual(ret, res) + + @patch(__name__ + '.sendp') + async def test_asendp(self, sndp): + kwargs = dict(a=5, b='foo') + pkt = object() + retv = object() + + sndp.return_value = retv + + r = await asendp(pkt, **kwargs) + + self.assertIs(r, retv) + + sndp.assert_called_with(pkt, **kwargs) + + @patch(__name__ + '.AsyncSniffer') + async def test_pktmgr(self, asyncs): + # That it can be a context manager + async with PacketManager('iface') as pm: + # that a RuntimeError is raised + with self.assertRaises(RuntimeError): + # when it's used as a context manager again + async with pm as foo: + pass + + # That it created a queue + self.assertIsInstance(pm._queue, asyncio.Queue) + + # that it called asyncsniffer with the correct arguments + asyncs.assert_called_with(iface='iface', prn=pm.enqueuepkt) + + # that it was started + asyncs().start.assert_called() + + # that when a pkt + pkt = object() + + # is enqueued + pm.enqueuepkt(pkt) + + # that getpkt returns it + self.assertIs(await pm.getpkt(), pkt) + + # that a timeout parameter can be provided + with self.assertRaises(asyncio.TimeoutError): + await pm.getpkt(timeout=0) + + # that when the context manager stops, stop is called + asyncs().stop.assert_called() + + def test_ifdata(self): + r = if_data('ue0') + print('if:', repr(r)) diff --git a/testinterfaces.sh b/testinterfaces.sh new file mode 100644 index 0000000..3c14bd7 --- /dev/null +++ b/testinterfaces.sh @@ -0,0 +1,616 @@ +#!/bin/sh - +# +# Copyright 2021 John-Mark Gurney. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# + +# +# ls testinterfaces.sh | entr sh -c 'fsync testinterfaces.sh && sh testinterfaces.sh run ue0 ue1' +# ifconfig ue0 -vnet testjail; ifconfig ue1 -vnet checkjail +# + +# Tests to add: +# Multicast filter programming +# bad checksum filtering +# TSO/LRO functionality +# polling? +# TOE? +# WOL +# vlan hw tso +# vlan hwfilter +# hardware timestamp (may not be testable) +# + +testjail=testjail +checkjail=checkjail + +# Function to setup jails as needed for tests. +setup() +{ + if ! jail -c persist=1 path=/ name=testjail vnet=new vnet.interface="$testiface"; then + echo failed to start test jail + exit 1 + fi + + if ! jail -c persist=1 path=/ name=checkjail vnet=new vnet.interface="$checkiface"; then + echo failed to start check jail + exit 1 + fi + + run test ifconfig lo0 up + run check ifconfig lo0 up +} + +# Verify that jails are present +checkjails() +{ + if ! jls -j "$testjail" >/dev/null 2>/dev/null; then + echo "test jail not present." + return 1 + fi + if ! jls -j "$checkjail" >/dev/null 2>/dev/null; then + echo "check jail not present." + return 1 + fi +} + +# Destroy jails +cleanup() +{ + jail -r "$testjail" 2>/dev/null + jail -r "$checkjail" 2>/dev/null +} + +# Subroutin for down'ing the interface, and removing any addresses from it. +# There are issues where if the addresses are not cleared, the same address +# won't always work. +cleaniface() +{ + jailname="$1" + ifacename="$2" + + run "$jailname" ifconfig $ifacename down + + run "$jailname" ifconfig $ifacename | grep inet | while read a b c; do + if [ x"$a" = x"inet" -o x"$a" = x"inet6" ]; then + #if [ x"$a" = x"inet" ]; then + run "$jailname" ifconfig $ifacename "$a" "$b" remove + fi + done + run "$jailname" ifconfig $ifacename +} + +# Setup the temp directory, switch to it, and more +starttest() +{ + tmpdir=$(mktemp -d -t testiface) + if [ $? != 0 ]; then + echo "failed to make temp directory" + exit 2 + fi + + oldpwd=$(pwd) + cd "$tmpdir" + + run test tcpdump -n -p -i $(getiface test) -w "$tmpdir"/test.tcpdump 2> /dev/null & + testdumppid="$!" + # XXX - -p should be used on both interfaces + run check tcpdump -n -i $(getiface check) -w "$tmpdir"/check.tcpdump 2> /dev/null & + checkdumppid="$!" + + echo "$testdumppid $checkdumppid" +} + +endtest() +{ + echo "$testdumppid" "$checkdumppid" + if ! kill "$testdumppid" "$checkdumppid"; then + echo "unable to kill off tcpdumps" + fi + + if ! wait "$testdumppid" "$checkdumppid"; then + echo "tcpdumps failed to exit" + fi + + cd "$oldpwd" + + # only attempt to clean up when successful + if [ "$1" -eq 0 ]; then + echo rm -rf "$tmpdir" + else + echo "test failed, data in $tmpdir" + fi +} + +# Run a command in a jail. The first arg is either check or test. +# Remainder of the args are passed for execution. +run() +{ + if [ x"$1" = x"check" ]; then + jid="$checkjail" + elif [ x"$1" = x"test" ]; then + jid="$testjail" + else + echo Invalid: "$1" >&2 + exit 1 + fi + + shift + jexec "$jid" "$@" +} + +# Return the interface that is in the specified jail. +# The first arg is either check or test. +getiface() +{ + if [ x"$1" = x"check" ]; then + echo "$checkiface" + elif [ x"$1" = x"test" ]; then + echo "$testiface" + else + echo Invalid: "$1" >&2 + exit 1 + fi +} + +# Run ifconfig on the base interface in a jail. +# The first arg is either check or test. +# Remainder of the args are passed to ifconfig for execution. +ifjail() +{ + j="$1" + shift + + iface=$(getiface "$j") + + run "$j" ifconfig "$iface" "$@" +} + +# Convert an ifconfig option to the options returned by ifconfig +iftoopt() +{ + case "$1" in + -txcsum6|txcsum6) + echo TXCSUM_IPV6 + ;; + -rxcsum6|rxcsum6) + echo RXCSUM_IPV6 + ;; + -txcsum|txcsum) + echo TXCSUM + ;; + -rxcsum|rxcsum) + echo RXCSUM + ;; + -vlanhwtag|vlanhwtag) + echo VLAN_HWTAGGING + ;; + *) + echo "Unknown option: $1" >&2 + exit 5 + ;; + esac +} + +# Get the list of capabilities or options for the interface in the jail. +# The first arg is either check or test. +# If the second arg is specified, it must be one of cap or opt, +# specifying which of the capabilities or options to be returned +# respectively. If this arg is not specified, it defaults to +# capabilities. +getcaps() +{ + if [ x"$2" = x"" -o x"$2" = x"cap" ]; then + ifoptcap=capabilities + elif [ x"$2" = x"opt" ]; then + ifoptcap=options + else + echo "Invalid getcaps arg: $2" >&2 + exit 1 + fi + run "$1" ifconfig -m $(getiface "$1") | + awk '$1 ~ /^'"$ifoptcap"'=/ { split($0, a, "<"); split(a[2], b, ">"); split(b[1], caps, ","); for (i in caps) print caps[i] }' | + sort +} + +# Verify that the interface in a jail is capable of the spefified feature. +# The first arg is either check or test. +# The second arg is the capability as printed in the options line. +# This is checked against the capabilities line printed by -m. +# Return 0 if present, return 1 if not present +hascap() +{ + getcaps "$1" | grep "^$(iftoopt "$2")$" >/dev/null +} + +# Verify that the interface in a jail has a specific capability enabled. +# The first arg is either check or test. +# The second arg is the capability as printed in the options line. +# This is checked against the options line. +# Return 0 if present, return 1 if not present +verifycap() +{ + if [ x"${2#-}" = x"${2}" ]; then + # without leading hyphen, present == success + presentretval="0" + absentretval="1" + else + # with leading hyphen, not present == success + presentretval="1" + absentretval="0" + fi + if getcaps "$1" opt | grep "^$(iftoopt "$2")$" >/dev/null; then + return $presentretval + else + return $absentretval + fi +} + +# Make sure that the carrier on both interface is up before returning. +# There is currently no timeout. +waitcarrier() +{ + local i + + for i in test check; do + while :; do + # make sure carrier is present and any IPv6 addresses + # are no longer tentative + if ifjail "$i" | grep 'status: active' >/dev/null && + ( ! ifjail "$i" | grep 'tentative' >/dev/null); then + break + fi + sleep .5 + done + done +} + +################################## +######### START OF TESTS ######### +################################## +# +# Run tests verify that vlan hardware tagging works (or at least doesn't +# break anything. There isn't an easy way to verify that packets in the +# kernel get/do not get the mbuf tag. (dtrace MAY be an option) + +hwvlantest() +{ + local i err + + err=0 + + for i in "-vlanhwtag" "vlanhwtag"; do + if ! hascap test "$i"; then + echo "skipping: $i" + continue + fi + echo "testing: $i" + + ifjail test "$i" + verifycap test "$i" || return 1 + ifjail check -vlanhwtag + verifycap check -vlanhwtag || return 1 + + cleaniface test $testiface + cleaniface check $checkiface + + sleep .5 + + ifjail test up + ifjail check up + + # make sure they don't exist + run test ifconfig $testiface.42 destroy 2>/dev/null + run check ifconfig $checkiface.42 destroy 2>/dev/null + + run test ifconfig $testiface.42 create 172.30.5.5/24 + run check ifconfig $checkiface.42 create 172.30.5.4/24 + + waitcarrier + + #run check tcpdump -XXX -p -q -c 2 -n -i "$checkiface" ip & + #run test tcpdump -XXX -p -q -c 2 -n -i "$testiface" ip & + #run test ifconfig -a + if ! run test ping -o -c 4 -t 5 -i .5 172.30.5.4 >/dev/null; then + echo FAILED on "$i"!!!! + #run test ifconfig -a + err=1 + fi + + #run test ifconfig $testiface.42 destroy + #run check ifconfig $checkiface.42 destroy + + if [ x"$err" != x"0" ]; then + return 1 + fi + done +} + +# Internal function for testing checksum. +# The first argument is the flag for the interface under test. +# If it ends with a 6, then IPv6 will be used for testing. + +csuminternal() +{ + local i + + i="$1" + + #if ! hascap test "$i"; then + # echo "skipping $i, test interface not capable" + # continue + #fi + + echo "testing: $i" + + if [ x"$i" = x"${i%6}" ]; then + # IPv4 + testnet="172.29.5.5/24" + checknet="172.29.5.4/24" + checkaddr="172.29.5.4" + pingcmd="ping" + ncopt="" + else + # IPv6 + testnet="inet6 fc00:0b5d:041c:7e37::7e37/64" + checknet="inet6 fc00:0b5d:041c:7e37::c43c/64" + checkaddr="fc00:0b5d:041c:7e37::c43c" + pingcmd="ping6" + ncopt="-6" + fi + + cleaniface test $testiface + cleaniface check $checkiface + + ifjail test "$i" + verifycap test "$i" || return 1 + ifjail check -txcsum -rxcsum -txcsum6 -rxcsum6 + + verifycap check "-txcsum" || return 1 + verifycap check "-rxcsum" || return 1 + + sleep .5 + + ifjail test up + ifjail check up + + run test ifconfig $testiface $testnet + run check ifconfig $checkiface $checknet + + waitcarrier + + sleep .5 + + #run check tcpdump -XXX -p -q -n -i "$checkiface" not ip6 & + #run test tcpdump -XXX -p -q -n -i "$testiface" not ip6 & + #run test ifconfig -a + #run check ifconfig -a + + # make sure connectivity works + if ! run test "$pingcmd" -o -c 4 -t 5 -i .5 "$checkaddr" >/dev/null; then + ifjail test + ifjail check + echo ping FAILED on "$i"!!!! + return 1 + fi + + mkfifo /tmp/tififo.$$ + run check sh -c 'cat /tmp/tififo.'$$' | nc '"$ncopt"' -l 3333 | cat > /tmp/tififo.'$$ 2>/dev/null & + sleep .5 + value="foobar $$ TCP $i" + res=$( (echo "$value"; sleep .5) | run test nc -w 2 -N "$checkaddr" 3333) + # We cannot use kill %% to kill the nc -l process as it only + # kills jexec, and NOT the jail process + # The following is heavy, but should be the only one running + run check killall nc >/dev/null 2>/dev/null + rm /tmp/tififo.$$ + if [ x"$res" != x"$value" ]; then + echo TCP FAILED on "$i"!!!! + return 1 + fi + + mkfifo /tmp/tififo.$$ + run check sh -c 'cat /tmp/tififo.'$$' | nc '"$ncopt"' -u -l 3333 | cat > /tmp/tififo.'$$ 2>/dev/null & + sleep .5 + value="foobar $$ UDP $i" + res=$( (echo "$value"; sleep .5) | run test nc -u -w 2 -N "$checkaddr" 3333) + # We cannot use kill %% to kill the nc -l process as it only + # kills jexec, and NOT the jail process + # The following is heavy, but should be the only one running + run check killall nc >/dev/null 2>/dev/null + rm /tmp/tififo.$$ + if [ x"$res" != x"$value" ]; then + echo UDP FAILED on "$i"!!!! + return 1 + fi + + # make sure that checksum validation works by sending invalid packets + # this includes using dtrace to verify the results from the driver +} + +# Iterate through the various hardware checksum off loading flags. + +csumtest() +{ + local i + + #for i in "-rxcsum6" "rxcsum6" "-txcsum6" "txcsum6" "-rxcsum" "rxcsum" "-txcsum" "txcsum"; do + for i in "-rxcsum" "rxcsum" "-txcsum" "txcsum"; do + if ! csuminternal "$i"; then + return 1 + fi + done +} + +# Verify that packets can be sent and received w/ a larger MTU. +# This test is a bit tricky in that it sets both interfaces to the +# larger MTU. Better option would be to set both sides to the larger +# MTU, but add a route w/ the -mtu specified for the return (or transmit) +# side so that we aren't testing both sides at the same time. + +mtutest() +{ + local i err + + oldtestmtu=$(ifjail test | awk '{ print $6; exit 0 }') + oldcheckmtu=$(ifjail check | awk '{ print $6; exit 0 }') + err=0 + for i in 1500 1504 2025 4074 7000 8000 9000 9216; do + echo "testing mtu: $i" + + if ! ifjail test mtu "$i" 2>/dev/null; then + echo "Failed to set mtu $i on test interface, skipping..." + continue + fi + + if ! ifjail check mtu "$i" 2>/dev/null; then + echo "Failed to set mtu $i on check interface." + echo "Please use different check interface." + return 1 + fi + + cleaniface test $testiface + cleaniface check $checkiface + + #run test arp -an + #run check arp -an + #run test ifconfig $testiface + #run check ifconfig $checkiface + + sleep .5 + + ifjail test up + ifjail check up + + waitcarrier + + run test ifconfig $testiface 172.29.5.5/24 + run check ifconfig $checkiface 172.29.5.4/24 + + # Subtract off ethernet header, ICMP header and ethernet CRC + if ! run test ping -D -s $(($i - 8 - 20)) -o -c 4 -t 5 -i .5 172.29.5.4 >/dev/null; then + echo failed for MTU size $i + err=1 + break + fi + done + + # restore MTU + ifjail test mtu $oldtestmtu + ifjail check mtu $oldcheckmtu + + if [ x"$err" != x"0" ]; then + return 1 + fi +} + +runtest() +{ + test="$1" + + starttest + + eval "$test"test + res="$?" + + endtest "$res" + + if [ "$res" -ne 0 ]; then + echo "$test"test failed. + exit 1 + fi +} + +usage() +{ + echo "Usage: $0 {init,run,deinit,$(echo "$alltests" | sed -e 's/ /,/g')} " + echo "" + echo "The ifaceundertest is the name of the interface which featres are being" + echo "tested." + echo "" + echo "The checinterface is the name of the interface used to validate that the" + echo "test interface operatates correctly. Features of this interface will" + echo "be turned off to prevent any of it's hardware offloading capabilities" + echo "interfering with the test." +} + +cmd="$1" +testiface="$2" +checkiface="$3" + +if [ -z "$cmd" ]; then + echo "command must be specified!" + echo "" + usage + exit 1 +fi + +if [ -z "$testiface" -o -z "$checkiface" ]; then + echo "both interfaces must be specified!" + echo "" + usage + exit 1 +fi + +alltests="mtu csum hwvlan" +case "$cmd" in +init) + setup + ;; + +mtu|csum|hwvlan) + if ! checkjails; then + exit 1 + fi + + echo starting $cmd, test $testiface, check $checkiface + + runtest "$cmd" + + echo "done" + ;; +run) + if ! checkjails; then + exit 1 + fi + + echo starting, test $testiface, check $checkiface + + for i in $alltests; do + runtest "$i" + done + + echo "done" + ;; + +deinit) + cleanup + ;; + +*) + echo "invalid cmd: $cmd" + echo + usage + exit 1 + ;; +esac