Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rawnetworkinterface stream #1320

Merged
merged 5 commits into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 42 additions & 24 deletions helpers/labgrid-raw-interface
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,18 @@ def get_denylist():
return denylist


def main(program, ifname, count):
if not ifname:
def main(program, options):
if not options.ifname:
raise ValueError("Empty interface name.")
if any((c == "/" or c.isspace()) for c in ifname):
raise ValueError(f"Interface name '{ifname}' contains invalid characters.")
if len(ifname) > 16:
raise ValueError(f"Interface name '{ifname}' is too long.")
if any((c == "/" or c.isspace()) for c in options.ifname):
raise ValueError(f"Interface name '{options.ifname}' contains invalid characters.")
if len(options.ifname) > 16:
raise ValueError(f"Interface name '{options.ifname}' is too long.")

denylist = get_denylist()

if ifname in denylist:
raise ValueError(f"Interface name '{ifname}' is denied in denylist.")
if options.ifname in denylist:
raise ValueError(f"Interface name '{options.ifname}' is denied in denylist.")

programs = ["tcpreplay", "tcpdump"]
if program not in programs:
Expand All @@ -54,18 +54,30 @@ def main(program, ifname, count):
]

if program == "tcpreplay":
args.append(f"--intf1={ifname}")
args.append('-')
args.append(f"--intf1={options.ifname}")
args.append("-")

if program == "tcpdump":
args.append("-n")
args.append(f"--interface={ifname}")
args.append(f"--interface={options.ifname}")
# Write out each packet as it is received
args.append("--packet-buffered")
# Capture complete packets (for compatibility with older tcpdump versions)
args.append("--snapshot-length=0")
args.append("-w")
args.append('-')
args.append("-")

if count:
if options.count:
args.append("-c")
args.append(str(count))
args.append(str(options.count))

if options.timeout:
# The timeout is implemented by specifying the number of seconds before rotating the
# dump file, but limiting the number of files to 1
args.append("-G")
args.append(str(options.timeout))
args.append("-W")
args.append("1")

try:
os.execvp(args[0], args)
Expand All @@ -75,22 +87,28 @@ def main(program, ifname, count):

if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
'-d',
'--debug',
action='store_true',
default=False,
help="enable debug mode"
parser.add_argument("-d", "--debug", action="store_true", default=False, help="enable debug mode")
subparsers = parser.add_subparsers(dest="program", help="program to run")

# tcpdump
tcpdump_parser = subparsers.add_parser("tcpdump")
tcpdump_parser.add_argument("ifname", type=str, help="interface name")
tcpdump_parser.add_argument("count", type=int, default=None, help="amount of frames to capture while recording")
tcpdump_parser.add_argument(
"--timeout", type=int, default=None, help="Amount of time to capture while recording. 0 means capture forever"
)
parser.add_argument('program', type=str, help='program to run, either tcpreplay or tcpdump')
parser.add_argument('interface', type=str, help='interface name')
parser.add_argument('count', nargs="?", type=int, default=None, help='amount of frames to capture while recording')

# tcpreplay
tcpreplay_parser = subparsers.add_parser("tcpreplay")
tcpreplay_parser.add_argument("ifname", type=str, help="interface name")

args = parser.parse_args()
try:
main(args.program, args.interface, args.count)
main(args.program, args)
except Exception as e: # pylint: disable=broad-except
if args.debug:
import traceback

traceback.print_exc(file=sys.stderr)
print(f"ERROR: {e}", file=sys.stderr)
exit(1)
40 changes: 24 additions & 16 deletions labgrid/driver/rawnetworkinterfacedriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,15 @@ def _stop(self, proc, *, timeout=None):
)

@Driver.check_active
@step(args=["filename", "count"])
def start_record(self, filename, *, count=None):
@step(args=["filename", "count", "timeout"])
def start_record(self, filename, *, count=None, timeout=None):
"""
Starts tcpdump on bound network interface resource.

Args:
filename (str): name of a file to record to
filename (str): name of a file to record to, or None to record to stdout
count (int): optional, exit after receiving this many number of packets
timeout (int): optional, number of seconds to capture packets before tcpdump exits
Returns:
Popen object of tcpdump process
"""
Expand All @@ -69,9 +70,15 @@ def start_record(self, filename, *, count=None):
cmd = ["tcpdump", self.iface.ifname]
if count is not None:
cmd.append(str(count))
if timeout is not None:
cmd.append("--timeout")
cmd.append(str(timeout))
cmd = self._wrap_command(cmd)
with open(filename, "wb") as outdata:
self._record_handle = subprocess.Popen(cmd, stdout=outdata, stderr=subprocess.PIPE)
if filename is None:
self._record_handle = subprocess.Popen(cmd, stdout=subprocess.PIPE)
else:
with open(filename, "wb") as outdata:
self._record_handle = subprocess.Popen(cmd, stdout=outdata, stderr=subprocess.PIPE)
return self._record_handle

@Driver.check_active
Expand All @@ -86,6 +93,11 @@ def stop_record(self, *, timeout=None):
"""
try:
self._stop(self._record_handle, timeout=timeout)
except subprocess.TimeoutExpired:
# If live streaming packets, there is no reason to wait for tcpdump
# to finish, so expect a timeout if piping to stdout
if self._record_handle.stdout is None:
raise
finally:
self._record_handle = None

Expand All @@ -97,17 +109,18 @@ def record(self, filename, *, count=None, timeout=None):
Either count or timeout must be specified.

Args:
filename (str): name of a file to record to
filename (str): name of a file to record to, or None to live stream packets
count (int): optional, exit after receiving this many number of packets
timeout (int): optional, maximum number of seconds to wait for the tcpdump process to
terminate
timeout (int): optional, number of seconds to capture packets before tcpdump exits
Returns:
Popen object of tcpdump process. If filename is None, packets can be read from stdout
"""
assert count or timeout

try:
yield self.start_record(filename, count=count)
yield self.start_record(filename, count=count, timeout=timeout)
finally:
self.stop_record(timeout=timeout)
self.stop_record(timeout=0 if filename is None else None)

@Driver.check_active
@step(args=["filename"])
Expand Down Expand Up @@ -170,12 +183,7 @@ def get_statistics(self):
"""
Returns basic interface statistics of bound network interface resource.
"""
cmd = self.iface.command_prefix + [
"ip",
"--json",
"-stats", "-stats",
"link", "show",
self.iface.ifname]
cmd = self.iface.command_prefix + ["ip", "--json", "-stats", "-stats", "link", "show", self.iface.ifname]
output = processwrapper.check_output(cmd)
return json.loads(output)[0]

Expand Down
Loading