| #!/usr/bin/env python3 | |
| # Copyright 2024 The ChromiumOS Authors | |
| # Use of this source code is governed by a BSD-style license that can be | |
| # found in the LICENSE file. | |
| """Ethernet-hide (ehide) | |
| Ethernet-hide (ehide) is a tool that creates an environment that hides the | |
| Ethernet interface on DUT but still allows SSH'ing to it through that Ethernet | |
| interface. This tool can help CrOS developers test in a no-network or WiFi- | |
| only environment with SSH connections unaffected. This tool can also help | |
| protect SSH connection when developers' behavior, such as restarting or | |
| deploying shill, can potentially affect it. Ehide only runs on test images. | |
| """ | |
| import argparse | |
| import logging | |
| import logging.handlers | |
| import time | |
| import ehide.ehide_daemon | |
| def get_parser() -> argparse.ArgumentParser: | |
| """Gets the argument parser. | |
| Returns: | |
| The argument parser. | |
| """ | |
| parser = argparse.ArgumentParser(description=__doc__) | |
| parser.add_argument( | |
| "action", | |
| choices=("start", "stop", "state"), | |
| help="To start, stop, or show the current state of ehide.", | |
| ) | |
| parser.add_argument( | |
| "-a", | |
| "--approach", | |
| default=ehide.ehide_daemon.Approach.FORWARDING.value, | |
| choices=( | |
| ehide.ehide_daemon.Approach.FORWARDING.value, | |
| ehide.ehide_daemon.Approach.SHELL.value, | |
| ), | |
| help=( | |
| "The approach to enabling SSH after hiding Ethernet. Must be " | |
| "'forwarding' or 'shell'. The forwarding approach uses socat to " | |
| "bidirectionally forward traffic between the port:22 in the ehide " | |
| "network namespace and the port:22 in the root network namespace. " | |
| "The shell approach starts an SSH server inside the created " | |
| "namespace and configures the SSH server to start a shell in the " | |
| "root network namespace using nsenter command in the ForceCommand " | |
| "option. The default value is 'forwarding'." | |
| ), | |
| ) | |
| parser.add_argument( | |
| "-d", | |
| "--dhclient-dir", | |
| type=str, | |
| default="/run/dhclient", | |
| help=( | |
| "Directory to save dhclient lease file and pid file. The default " | |
| "value is '/run/dhclient'." | |
| ), | |
| ) | |
| parser.add_argument( | |
| "-e", | |
| "--ether-ifname", | |
| type=str, | |
| default="eth0", | |
| help=( | |
| "Name of the ethernet device to hide. The default value is " | |
| "'eth0'." | |
| ), | |
| ) | |
| parser.add_argument( | |
| "-l", | |
| "--log-path", | |
| type=str, | |
| help="File path for logging.", | |
| ) | |
| parser.add_argument( | |
| "-n", | |
| "--netns-name", | |
| type=str, | |
| default="netns-ehide", | |
| help=( | |
| "Name of the network namespace to hide Ethernet interface. The " | |
| "default value is 'netns-ehide'." | |
| ), | |
| ) | |
| parser.add_argument( | |
| "-s", | |
| "--static-ipv4-addr", | |
| type=str, | |
| help=( | |
| "Configure static IPv4 address (CIDR) for the Ethernet interface. " | |
| "If it is none, ehide will resort to DHCP." | |
| ), | |
| ) | |
| return parser | |
| def main(): | |
| opts = get_parser().parse_args() | |
| # Set up logging. | |
| logging_handlers = [ | |
| logging.handlers.SysLogHandler( | |
| "/dev/log", facility=logging.handlers.SysLogHandler.LOG_LOCAL1 | |
| ) | |
| ] | |
| if opts.log_path: | |
| logging_handlers.append(logging.FileHandler(opts.log_path)) | |
| logging.basicConfig( | |
| format="ehide: %(asctime)s.%(msecs)03d %(levelname)s %(message)s", | |
| level=logging.INFO, | |
| datefmt="%Y-%m-%dT%H:%M:%S", | |
| handlers=logging_handlers, | |
| ) | |
| # Use GMT timezone. | |
| logging.Formatter.converter = time.gmtime | |
| ehide_daemon = ehide.ehide_daemon.EhideDaemon( | |
| approach=ehide.ehide_daemon.Approach.from_str(opts.approach), | |
| ether_ifname=opts.ether_ifname, | |
| static_ipv4_cidr=opts.static_ipv4_addr, | |
| dhclient_dir=opts.dhclient_dir, | |
| netns_name=opts.netns_name, | |
| ) | |
| action: str = opts.action | |
| if action == "start": | |
| ehide_daemon.start() | |
| elif action == "stop": | |
| ehide_daemon.stop() | |
| elif action == "state": | |
| print(ehide_daemon.get_state().value) | |
| else: | |
| raise ValueError(f"Unknown action: {action}.") | |
| if __name__ == "__main__": | |
| main() |