diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e6f4d76..77949e9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,15 +16,21 @@ jobs: - uses: actions/checkout@v2 with: fetch-depth: 1 + - name: Show host info + run: | + uname -a + sw_vers + ifconfig - name: Install build dependencies run: brew install vde - name: Make run: make - name: Install run: sudo make install - - name: Print launchd status + - name: Print launchd status (shared mode) run: launchctl print system/io.github.AkihiroSuda.vde_vmnet.plist - name: Install test dependencies run: brew install qemu bash coreutils - - name: Test - run: ./test/test.sh + - name: Test (shared mode) + run: ./test/test.sh /var/run/vde.ctl +# Bridged mode cannot be tested on GHA diff --git a/Makefile b/Makefile index d90c5cd..1efa1f4 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,9 @@ CFLAGS += -DVERSION=\"$(VERSION)\" LDFLAGS += -lvdeplug -framework vmnet +# Interface name for bridged mode. Empty value (default) disables bridged mode. +BRIDGED ?= + all: vde_vmnet OBJS = $(patsubst %.c, %.o, $(wildcard *.c)) @@ -23,10 +26,18 @@ install.bin: vde_vmnet install.launchd.plist: launchd/*.plist install launchd/io.github.virtualsquare.vde-2.vde_switch.plist "$(DESTDIR)/Library/LaunchDaemons/io.github.virtualsquare.vde-2.vde_switch.plist" install launchd/io.github.AkihiroSuda.vde_vmnet.plist "$(DESTDIR)/Library/LaunchDaemons/io.github.AkihiroSuda.vde_vmnet.plist" +ifneq ($(BRIDGED),) + sed -e "s/en0/$(BRIDGED)/g" launchd/io.github.virtualsquare.vde-2.vde_switch.bridged.en0.plist > "$(DESTDIR)/Library/LaunchDaemons/io.github.virtualsquare.vde-2.vde_switch.bridged.$(BRIDGED).plist" + sed -e "s/en0/$(BRIDGED)/g" launchd/io.github.AkihiroSuda.vde_vmnet.bridged.en0.plist > "$(DESTDIR)/Library/LaunchDaemons/io.github.AkihiroSuda.vde_vmnet.bridged.$(BRIDGED).plist" +endif install.launchd: install.launchd.plist launchctl load -w "$(DESTDIR)/Library/LaunchDaemons/io.github.virtualsquare.vde-2.vde_switch.plist" launchctl load -w "$(DESTDIR)/Library/LaunchDaemons/io.github.AkihiroSuda.vde_vmnet.plist" +ifneq ($(BRIDGED),) + launchctl load -w "$(DESTDIR)/Library/LaunchDaemons/io.github.virtualsquare.vde-2.vde_switch.bridged.$(BRIDGED).plist" + launchctl load -w "$(DESTDIR)/Library/LaunchDaemons/io.github.AkihiroSuda.vde_vmnet.bridged.$(BRIDGED).plist" +endif install: install.bin install.launchd @@ -38,10 +49,18 @@ uninstall.bin: uninstall.launchd: launchctl unload -w "$(DESTDIR)/Library/LaunchDaemons/io.github.AkihiroSuda.vde_vmnet.plist" launchctl unload -w "$(DESTDIR)/Library/LaunchDaemons/io.github.virtualsquare.vde-2.vde_switch.plist" +ifneq ($(BRIDGED),) + launchctl unload -w "$(DESTDIR)/Library/LaunchDaemons/io.github.AkihiroSuda.vde_vmnet.bridged.$(BRIDGED).plist" + launchctl unload -w "$(DESTDIR)/Library/LaunchDaemons/io.github.virtualsquare.vde-2.vde_switch.bridged.$(BRIDGED).plist" +endif uninstall.launchd.plist: uninstall.launchd rm -f "$(DESTDIR)/Library/LaunchDaemons/io.github.AkihiroSuda.vde_vmnet.plist" rm -f "$(DESTDIR)/Library/LaunchDaemons/io.github.virtualsquare.vde-2.vde_switch.plist" +ifneq ($(BRIDGED),) + rm -f "$(DESTDIR)/Library/LaunchDaemons/io.github.AkihiroSuda.vde_vmnet.bridged.$(BRIDGED).plist" + rm -f "$(DESTDIR)/Library/LaunchDaemons/io.github.virtualsquare.vde-2.vde_switch.bridged.$(BRIDGED).plist" +endif uninstall: uninstall.launchd.plist uninstall.bin diff --git a/README.md b/README.md index ad20b18..71cc3b6 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,20 @@ Make sure to specify unique MAC addresses to VMs: `-device virtio-net-pci,netdev NOTE: don't confuse MAC addresses of VMs with the MAC address of `vde_vmnet` itself that is printed as `vmnet_mac_address` in the debug log. You do not need to configure (and you can't, currently) the MAC address of `vde_vmnet` itself. +### Bridged mode + +Run `sudo make install BRIDGED=en0`. + +The following additional files will be installed: +- `/Library/LaunchDaemons/io.github.virtualsquare.vde-2.vde_switch.bridged.en0.plist` +- `/Library/LaunchDaemons/io.github.AkihiroSuda.vde_vmnet.bridged.en0.plist` + +Use `/var/run/vde.bridged.en0.ctl` as the VDE socket path. + +Needs macOS 10.15 or later. + +## Advanced usage + ### Testing without launchd ```console diff --git a/cli.c b/cli.c index 30781c3..b2292d9 100644 --- a/cli.c +++ b/cli.c @@ -4,6 +4,8 @@ #include +#include + #include "cli.h" #ifndef VERSION @@ -16,9 +18,15 @@ static void print_usage(const char *argv0) { printf("vde_vmnet does not require QEMU to run as the root user, but " "vde_vmnet itself has to run as the root, in most cases.\n"); printf("\n"); - printf("--vde-group=GROUP VDE group name (default: \"staff\")\n"); - printf("-h, --help display this help and exit\n"); - printf("-v, --version display version information and exit\n"); + printf("--vde-group=GROUP VDE group name (default: " + "\"staff\")\n"); + printf( + "--vmnet-mode=(host|shared|bridged) vmnet mode (default: \"shared\")\n"); + printf("--vmnet-interface=INTERFACE interface used for " + "--vmnet=bridged, e.g., \"en0\"\n"); + printf("-h, --help display this help and exit\n"); + printf("-v, --version display version information and " + "exit\n"); printf("\n"); printf("version: " VERSION "\n"); } @@ -26,6 +34,8 @@ static void print_usage(const char *argv0) { static void print_version() { puts(VERSION); } #define CLI_OPTIONS_ID_VDE_GROUP -42 +#define CLI_OPTIONS_ID_VMNET_MODE -43 +#define CLI_OPTIONS_ID_VMNET_INTERFACE -44 struct cli_options *cli_options_parse(int argc, char *argv[]) { struct cli_options *res = malloc(sizeof(*res)); if (res == NULL) { @@ -33,9 +43,13 @@ struct cli_options *cli_options_parse(int argc, char *argv[]) { } memset(res, 0, sizeof(*res)); res->vde_group = strdup("staff"); /* use strdup to make it freeable */ + res->vmnet_mode = VMNET_SHARED_MODE; const struct option longopts[] = { {"vde-group", required_argument, NULL, CLI_OPTIONS_ID_VDE_GROUP}, + {"vmnet-mode", required_argument, NULL, CLI_OPTIONS_ID_VMNET_MODE}, + {"vmnet-interface", required_argument, NULL, + CLI_OPTIONS_ID_VMNET_INTERFACE}, {"help", no_argument, NULL, 'h'}, {"version", no_argument, NULL, 'v'}, {0, 0, 0, 0}, @@ -46,6 +60,27 @@ struct cli_options *cli_options_parse(int argc, char *argv[]) { case CLI_OPTIONS_ID_VDE_GROUP: res->vde_group = strdup(optarg); break; + case CLI_OPTIONS_ID_VMNET_MODE: + if (strcmp(optarg, "host") == 0) { + res->vmnet_mode = VMNET_HOST_MODE; + } else if (strcmp(optarg, "shared") == 0) { + res->vmnet_mode = VMNET_SHARED_MODE; + } else if (strcmp(optarg, "bridged") == 0) { +#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101500 + res->vmnet_mode = VMNET_BRIDGED_MODE; +#else + fprintf(stderr, + "vmnet mode \"bridged\" requires macOS 10.15 or later\n"); + goto error; +#endif + } else { + fprintf(stderr, "Unknown vmnet mode \"%s\"\n", optarg); + goto error; + } + break; + case CLI_OPTIONS_ID_VMNET_INTERFACE: + res->vmnet_interface = strdup(optarg); + break; case 'h': print_usage(argv[0]); cli_options_destroy(res); @@ -67,6 +102,14 @@ struct cli_options *cli_options_parse(int argc, char *argv[]) { goto error; } res->vde_switch = strdup(argv[optind]); +#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101500 + if (res->vmnet_mode == VMNET_BRIDGED_MODE && res->vmnet_interface == NULL) { + fprintf( + stderr, + "vmnet mode \"bridged\" require --vmnet-interface to be specified\n"); + goto error; + } +#endif return res; error: print_usage(argv[0]); @@ -82,5 +125,7 @@ void cli_options_destroy(struct cli_options *x) { free(x->vde_group); if (x->vde_switch != NULL) free(x->vde_switch); + if (x->vmnet_interface != NULL) + free(x->vmnet_interface); free(x); } diff --git a/cli.h b/cli.h index 858f2b3..d44c9af 100644 --- a/cli.h +++ b/cli.h @@ -1,9 +1,13 @@ #ifndef VDE_VMNET_CLI_H #define VDE_VMNET_CLI_H +#include + struct cli_options { - char *vde_group; // --vde-group - char *vde_switch; // arg + char *vde_group; // --vde-group + operating_modes_t vmnet_mode; // --vmnet-mode + char *vmnet_interface; // --vmnet-interface + char *vde_switch; // arg }; struct cli_options *cli_options_parse(int argc, char *argv[]); diff --git a/launchd/io.github.AkihiroSuda.vde_vmnet.bridged.en0.plist b/launchd/io.github.AkihiroSuda.vde_vmnet.bridged.en0.plist new file mode 100644 index 0000000..85a6784 --- /dev/null +++ b/launchd/io.github.AkihiroSuda.vde_vmnet.bridged.en0.plist @@ -0,0 +1,33 @@ + + + + + Label + io.github.AkihiroSuda.vde_vmnet.bridged.en0.plist + Program + /usr/local/bin/vde_vmnet + ProgramArguments + + /usr/local/bin/vde_vmnet + --vmnet-mode=bridged + --vmnet-interface=en0 + /var/run/vde.bridged.en0.ctl + + StandardErrorPath + /var/run/vde_vmnet.bridged.en0.stderr + StandardOutPath + /var/run/vde_vmnet.bridged.en0.stdout + RunAtLoad + + UserName + root + KeepAlive + + PathState + + /var/run/vde.bridged.en0.pid + + + + + diff --git a/launchd/io.github.virtualsquare.vde-2.vde_switch.bridged.en0.plist b/launchd/io.github.virtualsquare.vde-2.vde_switch.bridged.en0.plist new file mode 100644 index 0000000..ca9ca52 --- /dev/null +++ b/launchd/io.github.virtualsquare.vde-2.vde_switch.bridged.en0.plist @@ -0,0 +1,33 @@ + + + + + + Label + io.github.virtualsquare.vde-2.vde_switch.bridged.en0.plist + Program + /usr/local/bin/vde_switch + ProgramArguments + + /usr/local/bin/vde_switch + --sock + /var/run/vde.bridged.en0.ctl + --pidfile + /var/run/vde.bridged.en0.pid + --group + staff + --dirmode + 0770 + + StandardErrorPath + /var/run/vde.bridged.en0.stderr + StandardOutPath + /var/run/vde.bridged.en0.stdout + RunAtLoad + + UserName + daemon + GroupName + staff + + diff --git a/main.c b/main.c index af301fb..831ec9e 100644 --- a/main.c +++ b/main.c @@ -149,12 +149,16 @@ static void on_vmnet_packets_available(interface_ref iface, int64_t estim_count, _on_vmnet_packets_available(iface, r, max_bytes, vdeconn); } -static interface_ref start(VDECONN *vdeconn) { - printf("Initializing vmnet.framework (VMNET_SHARED_MODE, with random " - "interface ID)\n"); +static interface_ref start(VDECONN *vdeconn, struct cli_options *cliopt) { + printf("Initializing vmnet.framework (mode %d, with random interface ID)\n", + cliopt->vmnet_mode); xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0); - // TODO: support non-shared modes - xpc_dictionary_set_uint64(dict, vmnet_operation_mode_key, VMNET_SHARED_MODE); + xpc_dictionary_set_uint64(dict, vmnet_operation_mode_key, cliopt->vmnet_mode); + if (cliopt->vmnet_interface != NULL) { + printf("Using network interface \"%s\"\n", cliopt->vmnet_interface); + xpc_dictionary_set_string(dict, vmnet_shared_interface_name_key, + cliopt->vmnet_interface); + } uuid_t uuid; // TODO: support deterministic UUID and MAC address @@ -244,7 +248,7 @@ int main(int argc, char *argv[]) { goto done; } - iface = start(vdeconn); + iface = start(vdeconn, cliopt); if (iface == NULL) { perror("start"); goto done; diff --git a/test/test.sh b/test/test.sh index 2869dbe..8710564 100755 --- a/test/test.sh +++ b/test/test.sh @@ -2,6 +2,13 @@ set -eux -o pipefail cd "$(dirname "$0")" +if [ "$#" -ne 1 ]; then + echo >&2 "Usage: $0 VDESOCK" + exit 1 +fi + +VDESOCK="$1" + if [ ! -f ipxe.lkrn ]; then curl -fSL -O https://boot.ipxe.org/ipxe.lkrn fi @@ -10,7 +17,7 @@ rm -f serial.log echo >&2 "===== QEMU BEGIN =====" qemu-system-x86_64 \ -device virtio-net-pci,netdev=net0 \ - -netdev vde,id=net0,sock=/var/run/vde.ctl \ + -netdev vde,id=net0,sock=$VDESOCK \ -kernel ipxe.lkrn \ -initrd test.ipxe \ -no-reboot \