Replaces --no-ipv6/--no-veth (plain flags) with --with-ipv6/--with-veth,
each taking an explicit true/false value (e.g. --with-veth=false), parsed
via the same parse_bool_flag() the config file itself already uses (now
exported from config_file.h so cli_args.cpp can reuse it).
create_network_command() now resolves ipv6/veth as CLI flag -> config's own
global.with-ipv6/global.with-veth -> true, so a host that always wants the
tap+relay fallback (or no IPv6) can set it once in the config instead of
passing the flag on every network creation. -w/--write-config fills in both
new keys like the existing six unshare-* bools.
One file per source area, exercising the pure/isolated parsing and CIDR-
arithmetic functions already exposed via headers with no side effects --
parse_port_forward_spec() (protocol suffix parsing/validation, network
resolution left to add_port_forward()), resolve_env_specs() (literal and
--env-file parsing, ordering, error cases -- a small local RAII ScratchFile
helper writes the --env-file fixtures under /tmp), network_subnet.h's CIDR
validation/overlap/allocation/address-arithmetic functions, and parse_args()
itself against synthetic argv's.
Two real bugs found running parse_args() repeatedly in one process (never
possible before -- a real invocation only ever calls it once), not
assumed:
1. getopt_long's scanning position (`optind`) is process-global and never
reset, so a second parse_args() call would silently resume scanning
wherever the first one left off. Fixing this alone (optind = 1) wasn't
enough on its own, either --
2. -h/-V return out of the getopt_long loop early (their own `return 0`
case), before a call ever completes its scan and lets getopt_long null
out its own private `nextchar` pointer -- the *next* parse_args() call
then resumed scanning through that stale pointer into the *previous*
call's already-destroyed argv strings, misparsing its own fresh argv.
glibc documents `optind = 0` (not 1) as the "fully reinitialize private
state before rescanning a new argv" signal; switching to it fixed this
for good, confirmed by 3 repeated runs each in both random and
deterministic (--order lex) Catch2 ordering with zero flakiness either
way.
Neither bug could ever have surfaced in real usage (parse_args() is only
ever called once per process from main()) -- purely a testability gap the
new unit tests exposed, now fixed at the source rather than worked around
in the test file.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv3s5jckJKzh6JkMoi2Akz