vmnet-cli 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
vmnet_cli/__init__.py ADDED
@@ -0,0 +1,2 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """vmnet_cli - CLI clone of vmnetcfg.exe over vnetlib.dll (RECON.md)."""
vmnet_cli/adapters.py ADDED
@@ -0,0 +1,236 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """vmnet_cli.adapters - see vmnet_cli.cli for the tool overview."""
3
+
4
+ import csv
5
+ import ctypes
6
+ import io
7
+ import subprocess
8
+ import sys
9
+ import time
10
+ import winreg
11
+
12
+ from .registry import CFG
13
+ from .vnl import VL, VNetLib
14
+
15
+
16
+ # ---- physical interface enumeration (PowerShell Get-NetAdapter) --------------
17
+ def physical_interfaces():
18
+ """Return {friendly name: adapter GUID} for physical (non-VMware) interfaces."""
19
+ ps = (
20
+ "Get-NetAdapter | Where-Object { $_.InterfaceDescription -notlike 'VMware*' }"
21
+ " | Sort-Object ifIndex | ConvertTo-Csv -NoTypeInformation"
22
+ )
23
+ try:
24
+ out = subprocess.run(
25
+ ["powershell", "-NoProfile", "-Command", ps],
26
+ capture_output=True,
27
+ text=True,
28
+ check=True,
29
+ timeout=60,
30
+ ).stdout
31
+ except (FileNotFoundError, subprocess.CalledProcessError) as e:
32
+ sys.exit(f"adapter enumeration failed: {e}")
33
+ import csv
34
+ import io
35
+
36
+ result = {}
37
+ for row in csv.DictReader(io.StringIO(out)):
38
+ guid = (row.get("InterfaceGuid") or "").strip().upper()
39
+ name = (row.get("Name") or "").strip('"')
40
+ if guid and name:
41
+ result[name] = guid
42
+ return result
43
+
44
+
45
+ # Real-hardware PnP bus prefixes. Software adapters (TAP, Wintun/WireGuard,
46
+ # ZeroTier, VMware VMnet…) are ROOT\- or SWD\-enumerated and never appear here,
47
+ # even when their driver masquerades as an 802.3 medium.
48
+ PHYSICAL_BUSES = ("PCI", "VPCI", "USB", "PCMCIA", "SDIO", "SDCC", "SD", "IEEE1394", "MDIO", "ACPI")
49
+ NET_CLASS = r"SYSTEM\CurrentControlSet\Control\Class\{4d36e972-e325-11ce-bfc1-08002be10318}"
50
+
51
+
52
+ def _adapter_medium(guid):
53
+ """(*IfType, *PhysicalMediaType) from the adapter's registry class key.
54
+ 71 = IEEE 802.11 in both fields; 6/14 = 802.3 Ethernet. (0, 0) = unknown
55
+ (some drivers omit the REV values; the bus gate already proved physical)."""
56
+ try:
57
+ with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, NET_CLASS) as k:
58
+ for i in range(winreg.QueryInfoKey(k)[0]):
59
+ try:
60
+ with winreg.OpenKey(k, winreg.EnumKey(k, i)) as sk:
61
+ if winreg.QueryValueEx(sk, "NetCfgInstanceId")[0].upper() != guid:
62
+ continue
63
+ out = []
64
+ for rev, plain in (
65
+ ("*IfType", "IfType"),
66
+ ("*PhysicalMediaType", "PhysicalMediaType"),
67
+ ):
68
+ for name in (rev, plain):
69
+ try:
70
+ out.append(winreg.QueryValueEx(sk, name)[0])
71
+ break
72
+ except OSError:
73
+ continue
74
+ else:
75
+ out.append(0)
76
+ return tuple(out)
77
+ except OSError:
78
+ continue
79
+ except OSError:
80
+ pass
81
+ return (0, 0)
82
+
83
+
84
+ def classify_interfaces():
85
+ """Physical NICs only, in ifIndex order: [(guid, name, kind, desc)].
86
+ kind is 'wifi' for IEEE 802.11 (ifType/PhysicalMediaType 71) else 'ethernet'.
87
+ VPN/virtual adapters are filtered by requiring the adapter's PnP device to
88
+ sit on a real hardware bus: ROOT\\-enumerated drivers (TAP, WireGuard,
89
+ ZeroTier, VMware VMnet) and SWD\\ devices never qualify, whatever medium
90
+ their driver claims."""
91
+ ps = (
92
+ "Get-CimInstance -Namespace root/StandardCimv2 -ClassName MSFT_NetAdapter"
93
+ " | Sort-Object ifIndex | Select-Object Name,InterfaceGuid,PnPDeviceID,"
94
+ "InterfaceDescription | ConvertTo-Csv -NoTypeInformation"
95
+ )
96
+ out = subprocess.run(
97
+ ["powershell", "-NoProfile", "-Command", ps],
98
+ capture_output=True,
99
+ text=True,
100
+ check=True,
101
+ timeout=60,
102
+ ).stdout
103
+ result = []
104
+ for row in csv.DictReader(io.StringIO(out)):
105
+ guid = (row.get("InterfaceGuid") or "").strip().upper()
106
+ name = (row.get("Name") or "").strip('"')
107
+ desc = (row.get("InterfaceDescription") or "").strip('"')
108
+ pnp = (row.get("PnPDeviceID") or "").strip('"').upper()
109
+ if not guid or "VMware" in desc or not pnp:
110
+ continue
111
+ if not pnp.split("\\")[0].startswith(PHYSICAL_BUSES):
112
+ continue # ROOT\/SWD\… = software driver
113
+ iftype, pmt = _adapter_medium(guid)
114
+ if iftype in (24, 131, 135, 136, 148, 154, 166, 181, 209):
115
+ continue # ppp / tunnel / virtual ifTypes
116
+ kind = "wifi" if 71 in (iftype, pmt) else "ethernet"
117
+ result.append((guid, name, kind, desc))
118
+ return result
119
+
120
+
121
+ def install_adapter_start(vnet):
122
+ """Kick the VNL adapter install; wait via install_adapter_finish."""
123
+ vl = VNetLib()
124
+ # 5-arg signature: (vnet, hwnd, flags, p4, p5*) — p5 is dereferenced if not NULL,
125
+ # so every trailing arg MUST be passed explicitly (stale-register AV otherwise).
126
+ vl._c("VNL_InstallNetworkAdapter", vnet.encode(), 0, 0, 0, 0)
127
+
128
+
129
+ def install_adapter_finish(vnet, rounds=10):
130
+ """Wait for the PnP install; fall back to drvInst64 installRootDriver
131
+ (works headless, verified)."""
132
+ import time
133
+
134
+ for _ in range(rounds): # PnP completes asynchronously; give it room
135
+ time.sleep(3)
136
+ if _adapter_present(vnet):
137
+ _name_adapter(vnet) # headless VNL path also leaves generic names
138
+ return True
139
+ exe = VL.rsplit("\\", 1)[0] + "\\drvInst64.exe"
140
+ inf = VL.rsplit("\\", 1)[0] + "\\netadapter.inf"
141
+ subprocess.run(
142
+ [exe, "installRootDriver", "0", f"*{vnet.replace('vmnet', 'VMnetAdapter')}", inf, "1"],
143
+ capture_output=True,
144
+ timeout=120,
145
+ )
146
+ for _ in range(4):
147
+ time.sleep(3)
148
+ if _adapter_present(vnet):
149
+ _name_adapter(vnet)
150
+ return True
151
+ return False
152
+
153
+
154
+ def install_adapter(vnet):
155
+ install_adapter_start(vnet)
156
+ return install_adapter_finish(vnet)
157
+
158
+
159
+ def _adapter_present(vnet):
160
+ return int(vnet[5:]) in _adapters_present_set()
161
+
162
+
163
+ def _name_adapter(vnet):
164
+ """drvInst installs with generic connection names; give it the VMnetN name."""
165
+ want = f"VMware Network Adapter {vnet.replace('vmnet', 'VMnet')}"
166
+ subprocess.run(
167
+ [
168
+ "powershell",
169
+ "-NoProfile",
170
+ "-Command",
171
+ f"$a = Get-NetAdapter | Where-Object {{ $_.InterfaceDescription -match 'VMnet{vnet[5:]}$' -and $_.Name -ne '{want}' }};"
172
+ f" if ($a) {{ Rename-NetAdapter -InputObject $a -NewName '{want}' -Confirm:$false }}",
173
+ ],
174
+ capture_output=True,
175
+ text=True,
176
+ timeout=60,
177
+ )
178
+
179
+
180
+ def _adapters_present_set():
181
+ """VMnet segments that currently have an adapter device (one PS call)."""
182
+ r = subprocess.run(
183
+ [
184
+ "powershell",
185
+ "-NoProfile",
186
+ "-Command",
187
+ "(Get-NetAdapter | ForEach-Object { if ($_.InterfaceDescription -match 'VMnet(\\d+)$') { $Matches[1] } }) -join ' '",
188
+ ],
189
+ capture_output=True,
190
+ text=True,
191
+ timeout=60,
192
+ )
193
+ if r.returncode != 0:
194
+ raise RuntimeError("Get-NetAdapter failed: " + (r.stderr or "").strip()[:200])
195
+ return {int(x) for x in r.stdout.split()}
196
+
197
+
198
+ def uninstall_adapter_start(vnet, n):
199
+ """Kick the VNL adapter removal; clear the config flag. Wait via finish."""
200
+ vl = VNetLib()
201
+ try:
202
+ vl._c("VNL_UninstallNetworkAdapter", vnet.encode(), 0, ctypes.byref(ctypes.c_int(0)))
203
+ except OSError:
204
+ pass
205
+ with winreg.CreateKey(winreg.HKEY_LOCAL_MACHINE, f"{CFG}\\{vnet}\\Adapter") as k:
206
+ winreg.SetValueEx(k, "UseAdapter", 0, winreg.REG_DWORD, 0)
207
+
208
+
209
+ def uninstall_adapter_finish(n, present=None):
210
+ """Wait for device removal; pnputil fallback. present = live segment set."""
211
+ if present is None:
212
+ time.sleep(3)
213
+ present = _adapters_present_set()
214
+ if n in present:
215
+ r = subprocess.run(
216
+ [
217
+ "powershell",
218
+ "-NoProfile",
219
+ "-Command",
220
+ "Get-PnpDevice -Class Net -ErrorAction SilentlyContinue |"
221
+ f" Where-Object {{ $_.FriendlyName -match 'VMnet{n}$' }} | Select-Object -Expand InstanceId",
222
+ ],
223
+ capture_output=True,
224
+ text=True,
225
+ timeout=60,
226
+ )
227
+ for iid in r.stdout.split():
228
+ subprocess.run(["pnputil.exe", "/remove-device", iid], capture_output=True, timeout=120)
229
+ time.sleep(3)
230
+ present = _adapters_present_set()
231
+ return n not in present
232
+
233
+
234
+ def uninstall_adapter(vnet, n):
235
+ uninstall_adapter_start(vnet, n)
236
+ return uninstall_adapter_finish(n)
vmnet_cli/batch.py ADDED
@@ -0,0 +1,138 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """vmnet_cli.batch - see vmnet_cli.cli for the tool overview."""
3
+
4
+ import time
5
+ import winreg
6
+
7
+ from .adapters import (
8
+ _adapters_present_set,
9
+ _name_adapter,
10
+ uninstall_adapter_finish,
11
+ uninstall_adapter_start,
12
+ )
13
+ from .conf import regen_nat_conf
14
+ from .registry import CFG, reg_bridge_bindings, reg_bridge_del, reg_delete_tree, reg_flag
15
+ from .services import cycle_bridge, restart_services
16
+ from .vnl import FEATURE_BITS, VL
17
+
18
+
19
+ def batch_new():
20
+ """Collects multi-network changes so every expensive wait/restart happens
21
+ once for the whole batch instead of once per network."""
22
+ return {"dhcp": False, "nat": False, "bridge": False, "gone": [], "added": [], "vnets": []}
23
+
24
+
25
+ def remove_stage(vl, n, ctx):
26
+ """Cheap part of a removal: pins, adapter kick, config key, conf edits."""
27
+ vnet = f"vmnet{n}"
28
+ f = vl.features(vnet) or 0
29
+ pinned = any(v == n for v in reg_bridge_bindings().values())
30
+ had_dhcp = bool(f & 0x1000000) or reg_flag(vnet, "DHCP")
31
+ had_nat = bool(f & 0x10000) or reg_flag(vnet, "NAT")
32
+ had_adapter = bool(f & 0x100) or reg_flag(vnet, "Adapter")
33
+ try:
34
+ winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, CFG + "\\" + vnet).Close()
35
+ has_config = True
36
+ except OSError:
37
+ has_config = False
38
+ if f == 0 and not (had_dhcp or had_nat or had_adapter or pinned) and not has_config:
39
+ print(f"* {vnet} is not configured; nothing to remove")
40
+ return False
41
+ parts = [v for b, v in FEATURE_BITS.items() if f & b]
42
+ print(f"* removing {vnet}" + (f" ({', '.join(parts)})" if parts else ""))
43
+ ctx["vnets"].append(n)
44
+ for guid, v in reg_bridge_bindings().items():
45
+ if v == n:
46
+ reg_bridge_del(guid)
47
+ print(f" - unbound {guid}")
48
+ ctx["bridge"] = True
49
+ if had_adapter:
50
+ uninstall_adapter_start(vnet, n)
51
+ ctx["gone"].append(n)
52
+ reg_delete_tree(CFG + "\\" + vnet)
53
+ print(" - deleted VMnetConfig\\" + vnet)
54
+ if had_dhcp:
55
+ rc = vl.update_dhcp(vnet)
56
+ print(f" - vmnetdhcp.conf regenerated: {'ok' if rc == 1 else 'rc=' + str(rc)}")
57
+ ctx["dhcp"] = True
58
+ if had_nat:
59
+ ctx["nat"] = True # conf regen once at apply (whole-file rewrite)
60
+ return True
61
+
62
+
63
+ def _present_safe():
64
+ """Adapter enumeration must never abort a batch apply: a transient
65
+ failure degrades to 'nothing present' (matching the C tool, whose
66
+ enumeration just returns 0 on failure), with a warning. The service
67
+ and bridge phases below must always run."""
68
+ try:
69
+ return _adapters_present_set()
70
+ except Exception as e:
71
+ print(f" WARNING: adapter enumeration failed: {e}")
72
+ return set()
73
+
74
+
75
+ def batch_apply(vl, ctx):
76
+ """One wait for all PnP operations, one conf regen, one service restart,
77
+ one bridge cycle — regardless of batch size."""
78
+ if ctx["nat"]:
79
+ regen_nat_conf(vl)
80
+ print(" - vmnetnat.conf regenerated")
81
+ if ctx["gone"]:
82
+ try:
83
+ time.sleep(3)
84
+ present = _present_safe()
85
+ for n in ctx["gone"]:
86
+ if n in present:
87
+ uninstall_adapter_finish(n, present=present)
88
+ present = _present_safe()
89
+ print(
90
+ f" - host adapter device vmnet{n}: {'STILL PRESENT' if n in present else 'gone'}"
91
+ )
92
+ except Exception as e:
93
+ print(f" WARNING: adapter removal step failed: {e} (continuing)")
94
+ if ctx["added"]:
95
+ try:
96
+ pending = list(ctx["added"])
97
+ for _ in range(10):
98
+ time.sleep(3)
99
+ present = _present_safe()
100
+ pending = [n for n in pending if n not in present]
101
+ if not pending:
102
+ break
103
+ import subprocess
104
+
105
+ exe = VL.rsplit("\\", 1)[0] + "\\drvInst64.exe"
106
+ inf = VL.rsplit("\\", 1)[0] + "\\netadapter.inf"
107
+ for n in pending:
108
+ try:
109
+ subprocess.run(
110
+ [exe, "installRootDriver", "0", f"*VMnetAdapter{n}", inf, "1"],
111
+ capture_output=True,
112
+ timeout=120,
113
+ )
114
+ except (subprocess.TimeoutExpired, OSError):
115
+ pass # keep waiting; do not abort the batch
116
+ time.sleep(3)
117
+ for _ in range(4):
118
+ if not pending:
119
+ break
120
+ time.sleep(3)
121
+ present = _present_safe()
122
+ pending = [n for n in pending if n not in present]
123
+ present = _present_safe()
124
+ for n in ctx["added"]:
125
+ if n in present:
126
+ _name_adapter(f"vmnet{n}")
127
+ print(
128
+ f" - host adapter device vmnet{n}: {'ok' if n in present else 'pending/failed (PnP is async)'}"
129
+ )
130
+ except Exception as e:
131
+ print(f" WARNING: adapter install step failed: {e} (continuing)")
132
+ if ctx["dhcp"] or ctx["nat"]:
133
+ restart_services(dhcp=ctx["dhcp"], nat=ctx["nat"])
134
+ if ctx["bridge"]:
135
+ cycle_bridge()
136
+ for n in ctx["vnets"]:
137
+ left = vl.features(f"vmnet{n}") or 0 # absent (None) counts as removed
138
+ print(f" {'OK' if left == 0 else 'WARNING'}: vmnet{n} features now 0x{left:08x}")
vmnet_cli/cli.py ADDED
@@ -0,0 +1,196 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """vmnet_cli.cli - see vmnet_cli.cli for the tool overview."""
3
+
4
+ import argparse
5
+ import sys
6
+
7
+ if sys.platform != "win32":
8
+ sys.exit(
9
+ "vmnet-cli must run on a Windows host with VMware Workstation "
10
+ "installed (it drives vnetlib.dll)."
11
+ )
12
+
13
+ from .commands import (
14
+ cmd_adapters,
15
+ cmd_add,
16
+ cmd_autobridge,
17
+ cmd_bridged,
18
+ cmd_clear,
19
+ cmd_edit,
20
+ cmd_list,
21
+ cmd_remove,
22
+ cmd_restore,
23
+ cmd_unpin,
24
+ )
25
+ from .commands_nat import cmd_export, cmd_import, cmd_natcfg, cmd_natfw, cmd_setip
26
+ from .vnl import VNetLib
27
+
28
+ """
29
+ vmnet-cli — unofficial command-line interface to the VMware Workstation
30
+ Virtual Network Editor, built on the reverse-engineered vnetlib.dll VNL_* API
31
+ (Workstation 26H1,
32
+ build 25688693; API v1.0). Run elevated on the Windows host running Workstation.
33
+
34
+ Usage (full vmnetcfg.exe Network Editor parity):
35
+ vmnet-cli.py adapters list physical interfaces
36
+ vmnet-cli.py list list vmnet segments + features
37
+ vmnet-cli.py add hostonly --name X [--subnet A [--mask M]] [--vnet N] [--dhcp on|off]
38
+ vmnet-cli.py add nat --name X [--subnet A [--mask M]] [--vnet N]
39
+ vmnet-cli.py bridged --name LAN --adapter Ethernet [--vnet N]
40
+ vmnet-cli.py edit --vnet N [--name X] [--subnet A] [--mask M] [--dhcp on|off] [--nat on|off]
41
+ [--type bridged|hostonly|nat] [--adapter on|off]
42
+ [--dhcp-begin A --dhcp-end A] [--lease S --max-lease S]
43
+ vmnet-cli.py set-ip --vnet N --addr A host adapter IP
44
+ vmnet-cli.py natfw list --vnet N --proto tcp|udp --hport P
45
+ vmnet-cli.py natfw add --vnet N --proto tcp|udp --hport P --vm-ip A --vm-port P [--desc D]
46
+ vmnet-cli.py natfw remove --vnet N --proto tcp|udp --hport P
47
+ vmnet-cli.py natcfg show|set --vnet N [--gateway-ip A] [--host-ip A] [--configport P]
48
+ [--ipv6 on|off] [--active-ftp on|off] [--allow-any-oui on|off]
49
+ [--udp-timeout S] [--dns-auto on|off] [--dns1 A --dns2 A --dns3 A]
50
+ [--dns-policy order|rotate|burst] [--dns-retries N] [--dns-timeout S]
51
+ [--nbns-timeout S]
52
+ vmnet-cli.py remove --vnet N [--force] delete a vmnet network entirely
53
+ vmnet-cli.py unpin --vnet N drop bridge binding(s), keep network
54
+ vmnet-cli.py autobridge [--dry-run] bridge each physical NIC:
55
+ Ethernet->LAN<n>, Wi-Fi->WLAN<n>
56
+ vmnet-cli.py restore-defaults --force wipe custom nets, recreate vmnet1+vmnet8
57
+ vmnet-cli.py clear|flush --force remove EVERY network (vmnet0 stays)
58
+ vmnet-cli.py export --file F save config (registry hive)
59
+ vmnet-cli.py import --file F restore config
60
+ """
61
+
62
+
63
+ def parse_vnet(s):
64
+ """Accept only '2' or 'vmnet2' (letters then digits, nothing else) —
65
+ mirrors the C tool's strict strtol parse; vnetlib's index resolution
66
+ clamps, so sloppy parses would silently target other segments."""
67
+ t = str(s)
68
+ i = 0
69
+ while i < len(t) and t[i].isascii() and t[i].isalpha():
70
+ i += 1
71
+ rest = t[i:]
72
+ if not rest.isascii() or not rest.isdigit() or int(rest) > 1000000:
73
+ raise argparse.ArgumentTypeError(
74
+ f"expected a segment number or name (e.g. 2 or vmnet2), got {s!r}"
75
+ )
76
+ return int(rest)
77
+
78
+
79
+ def require_elevated():
80
+ """Same bar as vmnetcfg.exe: full elevated Administrator token.
81
+ SYSTEM is not required; Network Configuration Operators is not enough."""
82
+ import ctypes
83
+
84
+ if not ctypes.windll.shell32.IsUserAnAdmin():
85
+ sys.exit(
86
+ "vmnet-cli must run elevated as an Administrator "
87
+ "(elevated prompt or Run as administrator); SYSTEM not required"
88
+ )
89
+
90
+
91
+ def main():
92
+ ap = argparse.ArgumentParser()
93
+ sub = ap.add_subparsers(dest="cmd", required=True)
94
+ sub.add_parser("adapters")
95
+ sub.add_parser("list")
96
+ a = sub.add_parser("add")
97
+ a.add_argument("kind", choices=["hostonly", "nat"])
98
+ a.add_argument("--name", required=True)
99
+ a.add_argument("--subnet")
100
+ a.add_argument("--mask")
101
+ a.add_argument("--vnet", type=parse_vnet)
102
+ a.add_argument("--dhcp", choices=["on", "off"], default="on", help="hostonly only")
103
+ b = sub.add_parser("bridged")
104
+ b.add_argument("--name", required=True)
105
+ b.add_argument("--adapter", required=True)
106
+ b.add_argument("--vnet", type=parse_vnet)
107
+ e = sub.add_parser("edit")
108
+ e.add_argument("--vnet", type=parse_vnet, required=True)
109
+ e.add_argument("--name")
110
+ e.add_argument("--subnet")
111
+ e.add_argument("--mask")
112
+ e.add_argument("--dhcp", choices=["on", "off"])
113
+ e.add_argument("--nat", choices=["on", "off"])
114
+ e.add_argument("--type", choices=["bridged", "hostonly", "nat"])
115
+ e.add_argument("--adapter", choices=["on", "off"], help="host adapter checkbox")
116
+ e.add_argument("--dhcp-begin")
117
+ e.add_argument("--dhcp-end")
118
+ e.add_argument("--lease", type=int, help="default lease seconds")
119
+ e.add_argument("--max-lease", type=int)
120
+ fw = sub.add_parser("natfw", help="NAT port forwarding (mapPortDlg)")
121
+ fw.add_argument("action", choices=["list", "add", "remove"])
122
+ fw.add_argument("--vnet", type=parse_vnet, required=True)
123
+ fw.add_argument("--proto", choices=["tcp", "udp"])
124
+ fw.add_argument("--hport", type=int)
125
+ fw.add_argument("--vm-ip")
126
+ fw.add_argument("--vm-port", type=int)
127
+ fw.add_argument("--desc")
128
+ nc = sub.add_parser("natcfg", help="NAT/DNS/NetBIOS advanced settings (natDlg)")
129
+ nc.add_argument("action", choices=["show", "set"])
130
+ nc.add_argument("--vnet", type=parse_vnet, required=True)
131
+ for flag, typ in (
132
+ ("gateway-ip", str),
133
+ ("host-ip", str),
134
+ ("configport", int),
135
+ ("udp-timeout", int),
136
+ ("dns1", str),
137
+ ("dns2", str),
138
+ ("dns3", str),
139
+ ("dns-policy", str),
140
+ ("dns-retries", int),
141
+ ("dns-timeout", int),
142
+ ("nbns-timeout", int),
143
+ ):
144
+ nc.add_argument("--" + flag, type=typ)
145
+ for flag in ("ipv6", "active-ftp", "allow-any-oui", "dns-auto"):
146
+ nc.add_argument("--" + flag, choices=["on", "off"])
147
+ rd = sub.add_parser("restore-defaults", help="wipe custom nets, recreate vmnet1+vmnet8")
148
+ rd.add_argument("--force", action="store_true")
149
+ cl = sub.add_parser(
150
+ "clear", aliases=["flush"], help="remove ALL vmnet networks incl. stock (vmnet0 stays)"
151
+ )
152
+ cl.add_argument("--force", action="store_true")
153
+ i = sub.add_parser("set-ip")
154
+ i.add_argument("--vnet", type=parse_vnet, required=True)
155
+ i.add_argument("--addr", required=True)
156
+ r = sub.add_parser(
157
+ "remove",
158
+ help="delete vmnet networks (adapter, config, conf, services) — batched across all --vnet args",
159
+ )
160
+ r.add_argument("--vnet", type=parse_vnet, required=True, action="append")
161
+ r.add_argument("--force", action="store_true", help="allow removing stock vmnet1/vmnet8")
162
+ u = sub.add_parser("unpin", help="only remove bridge binding(s) of a vnet, keep the network")
163
+ u.add_argument("--vnet", type=parse_vnet, required=True)
164
+ ab = sub.add_parser(
165
+ "autobridge", help="bridge every physical NIC: Ethernet->LAN<n>, Wi-Fi->WLAN<n>"
166
+ )
167
+ ab.add_argument("--dry-run", action="store_true", help="show the plan, change nothing")
168
+ x = sub.add_parser("export")
169
+ x.add_argument("--file", required=True)
170
+ m = sub.add_parser("import")
171
+ m.add_argument("--file", required=True)
172
+ args = ap.parse_args()
173
+ require_elevated()
174
+ vl = VNetLib()
175
+ {
176
+ "adapters": lambda: cmd_adapters(vl),
177
+ "list": lambda: cmd_list(vl),
178
+ "add": lambda: cmd_add(vl, args),
179
+ "bridged": lambda: cmd_bridged(vl, args),
180
+ "edit": lambda: cmd_edit(vl, args),
181
+ "set-ip": lambda: cmd_setip(vl, args),
182
+ "remove": lambda: cmd_remove(vl, args),
183
+ "unpin": lambda: cmd_unpin(vl, args),
184
+ "autobridge": lambda: cmd_autobridge(vl, args),
185
+ "natfw": lambda: cmd_natfw(vl, args),
186
+ "natcfg": lambda: cmd_natcfg(vl, args),
187
+ "restore-defaults": lambda: cmd_restore(vl, args),
188
+ "clear": lambda: cmd_clear(vl, args),
189
+ "flush": lambda: cmd_clear(vl, args),
190
+ "export": lambda: cmd_export(vl, args),
191
+ "import": lambda: cmd_import(vl, args),
192
+ }[args.cmd]()
193
+
194
+
195
+ if __name__ == "__main__":
196
+ main()