Browse Source

set of files that are in progress for testing FreeBSD ethernet interfaces..

This is in progress, and documentation will follow..
main
John-Mark Gurney 3 years ago
commit
e4ee0d4142
8 changed files with 1646 additions and 0 deletions
  1. +17
    -0
      Makefile
  2. +139
    -0
      bpf.py
  3. +27
    -0
      ioctlgen.awk
  4. +157
    -0
      kvm.py
  5. +47
    -0
      mocks.py
  6. +2
    -0
      requirements.txt
  7. +641
    -0
      testeth.py
  8. +616
    -0
      testinterfaces.sh

+ 17
- 0
Makefile View File

@@ -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'

+ 139
- 0
bpf.py View File

@@ -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])

+ 27
- 0
ioctlgen.awk View File

@@ -0,0 +1,27 @@
#!/usr/bin/awk

BEGIN {
print "#include <sys/types.h>"
print "#include <sys/ioctl.h>"
print "#include <sys/time.h>"
print "#include <sys/socket.h>"
print "#include <net/if.h>"
print "#include <net/route.h>"
print "#include <netinet/in.h>"
print "#include <netinet/ip_mroute.h>"
print "#include <sys/sockio.h>"
print "#include <stdio.h>"

print "int"
print "main()"
print "{"
}

$1 == "#define" && $3 ~ "^_IO[WR]" {
printf("\tprintf(\"%s = %%ld\\n\", %s);\n", $2, $2)
}

END {
print " return 0;"
print "}"
}

+ 157
- 0
kvm.py View File

@@ -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)

+ 47
- 0
mocks.py View File

@@ -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

+ 2
- 0
requirements.txt View File

@@ -0,0 +1,2 @@
git+https://github.com/secdev/scapy.git
coverage

+ 641
- 0
testeth.py View File

@@ -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 <full-duplex>)$'
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<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]+)$'
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<options>([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<capabilities>([A-Z0-9_]+(,[A-Z0-9_]+)*)?)>$'
m = re.match(reg, i)
if m:
obj['capabilities'] = _makeflags(m.group('capabilities'))
continue

reg = '^\\tether (?P<ether>[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<media>.+)$'
m = re.match(reg, i)
if m:
obj['media'] = _parsemedia(m.group('media'))
continue

reg = '^\\tstatus: (?P<status>.+)$'
m = re.match(reg, i)
if m:
obj['status'] = m.group('status')
continue

reg = '^\\tnd6 options=[0-9a-f]{2,2}<(?P<nd6options>([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<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
options=80188<VLAN_MTU,VLAN_HWCSUM,TSO4,LINKSTATE>
capabilities=c019b<RXCSUM,TXCSUM,VLAN_MTU,VLAN_HWTAGGING,VLAN_HWCSUM,TSO4,VLAN_HWTSO,LINKSTATE>
ether 52:12:34:56:78:90
media: Ethernet autoselect (1000baseT <full-duplex>)
status: active
nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>
'''

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<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
options=80188<VLAN_MTU,VLAN_HWCSUM,TSO4,LINKSTATE>
capabilities=68009b<RXCSUM,TXCSUM,VLAN_MTU,VLAN_HWTAGGING,VLAN_HWCSUM,LINKSTATE,RXCSUM_IPV6,TXCSUM_IPV6>
ether 52:12:34:56:78:90
media: Ethernet autoselect (1000baseT <full-duplex>)
status: active
nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>
'''

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<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
options=80188<VLAN_MTU,VLAN_HWCSUM,TSO4,LINKSTATE>
ether 52:12:34:56:78:90
media: Ethernet autoselect (1000baseT <full-duplex>)
status: no carrier
nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>
'''

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))

+ 616
- 0
testinterfaces.sh View File

@@ -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')} <ifaceundertest> <checkinterface>"
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

Loading…
Cancel
Save