Skip to content

feat: Enhanced UAPI, full CLI, wg-quick in pure Python, and wg-daemon#25

Open
radawson wants to merge 7 commits intocmusatyalab:mainfrom
radawson:dev
Open

feat: Enhanced UAPI, full CLI, wg-quick in pure Python, and wg-daemon#25
radawson wants to merge 7 commits intocmusatyalab:mainfrom
radawson:dev

Conversation

@radawson
Copy link
Copy Markdown

Summary

This PR extends wireguard-tools with several significant enhancements developed while building a WireGuard management GUI. All changes are backward-compatible with the existing API.

Core library fixes

  • IPv6 endpoint handling: _parse_endpoint() properly handles [::1]:51820 bracket notation throughout config parsing, UAPI serialization, and wgconfig output
  • Robust comma-list parsing: _split_comma_list() filters empty entries from inputs like "10.0.0.0/24," or "a,,b" that previously caused ValueError in downstream parsers
  • Hex FwMark support: int(value, 0) for FwMark parsing accepts both 0x1234 and decimal formats
  • Case-insensitive SaveConfig: value.lower() == "true" instead of value == "true"

UAPI backend (wireguard_uapi.py)

  • Hostname resolution with WG_ENDPOINT_RESOLUTION_RETRIES support (matching C wg(8) behavior)
  • Correct IPv6 endpoint formatting in UAPI endpoint= lines (bracket notation)
  • Hex-encoded preshared keys in UAPI protocol (was passing base64)
  • Distinct set_config() / sync_config(): set_config does atomic replace (replace_peers=true); sync_config diffs against running state and applies only changes (removes absent peers, skips unchanged ones)
  • Extracted _build_peer_uapi() and _send_uapi_set() helpers to reduce duplication

Netlink backend (wireguard_netlink.py)

  • Refactored shared logic into _apply_config(); both set_config and sync_config delegate to it

Device abstraction (wireguard_device.py)

  • Added abstract sync_config() with a default implementation that falls back to set_config()

CLI (cli.py) — full wg(8) parity

  • wg set: Complex argument parser (_parse_set_args) supporting peer, endpoint, allowed-ips, preshared-key, persistent-keepalive, remove, and incremental allowed-ips
  • wg addconf and wg syncconf subcommands
  • wg show enhancements: dump format, per-field filtering, interfaces mode, WG_HIDE_KEYS support
  • wg-py up / wg-py down subcommands

Pure Python wg-quick (wg_quick.py) — new module

  • up() and down() functions implementing wg-quick(8) using pyroute2 for netlink-based interface, address, route, and fwmark rule management
  • DNS setup/teardown via resolvconf
  • Pre/Post Up/Down hook execution
  • Table = auto|off|<number> routing logic with catch-all AllowedIPs detection

Privileged daemon (daemon.py, daemon_client.py) — new modules

  • JSON-over-Unix-socket server (wg-daemon) enabling privilege separation: run the daemon as root while clients (e.g., a web GUI) connect unprivileged
  • 6 commands: up, down, show, set_peer, remove_peer, list_devices
  • WgDaemonClient class for easy IPC from any Python consumer
  • Systemd service template in contrib/wg-daemon.service

Tests — 67 new tests

  • test_cli_commands.py (21): _parse_set_args and show field coverage
  • test_config_parsing.py (26): endpoint parsing, comma lists, IPv6 roundtrip, FwMark hex, Table/SaveConfig
  • test_uapi_protocol.py (12): peer UAPI serialization, set/sync config, endpoint resolution
  • test_wg_quick.py (11): config resolution, table routing, network collection, up/down guards
  • test_daemon.py (15): full protocol coverage with mocked privileged operations

Test plan

  • All 106 tests pass (pytest tests/ -v), 1 pre-existing skip
  • Backward-compatible: no changes to existing public API signatures
  • No new runtime dependencies beyond existing pyroute2, attrs, segno

- sync_config() on WireguardDevice / UAPI / Netlink backends
- _resolve_endpoint() with WG_ENDPOINT_RESOLUTION_RETRIES support
- IPv6 bracket notation for UAPI and config endpoints
- _split_comma_list() filters empty entries
- FwMark hex parsing (int(value, 0))
- Preshared key sent as hex to UAPI
- Full wg set CLI with _parse_set_args()
- wg addconf merging new config onto running config
- wg syncconf using diff-based sync_config()
- wg show with dump, field filtering, WG_HIDE_KEYS
- wg_quick.py: up()/down() via pyroute2 netlink
  (interface create/delete, address, route, fwmark rules, DNS)
- CLI up/down subcommands
- test_cli_commands: 21 tests for _parse_set_args and SHOW_FIELDS
- test_config_parsing: 27 tests for _parse_endpoint, _split_comma_list,
  IPv6, FwMark hex, AllowedIPs, Table/SaveConfig, round-trips
- test_uapi_protocol: 11 tests for _build_peer_uapi, set_config,
  sync_config, _resolve_endpoint with IPv6 and hostname
- test_wg_quick: 11 tests for _find_config, _resolve_table,
  _collect_allowed_networks, up/down error conditions
- Fix PermissionError in _find_config for /etc/wireguard access
…losure

- Updated the up() function to use a context manager for opening the WireGuard configuration file, ensuring proper closure of file handles.
- Added a new test case in test_wg_quick.py to verify that configuration file handles are closed after use in the up() function.
- Introduced a new `wg-daemon` command in `pyproject.toml` for managing the WireGuard Tools Daemon.
- Implemented the `WgDaemonClient` class for IPC over a Unix socket, allowing commands like `up`, `down`, `show`, `set_peer`, and `remove_peer`.
- Added a `wg-daemon.service` file for systemd integration, enabling easy management of the daemon.
- Enhanced the CLI with improved argument parsing for the `show` command, allowing for flexible input.
- Added comprehensive tests for the daemon and its client to ensure robust functionality and error handling.
- Updated existing tests to accommodate new features and ensure compatibility.
…n of zero value

- Modified the `set_` function to ensure that an explicit zero value for `fwmark` is preserved when the user requests "fwmark off" or "0".
- Added a new test case in `TestSetCommand` to verify that the `fwmark` value remains zero when set to these tokens, ensuring correct behavior in CLI command parsing.
- Updated README.md to clarify installation instructions and expand on CLI commands, including `wg-py` and `wg-daemon`.
- Introduced new ARCHITECTURE.md file detailing module purposes, layer diagrams, and backend selection logic.
- Added CHANGELOG.md to document significant changes and enhancements in the library.
- Created DAEMON.md for comprehensive guidance on the `wg-daemon`, including its purpose, protocol, commands, and systemd setup.
- Developed INTEGRATION.md to serve as a developer guide for using the library, covering installation, key generation, configuration file handling, and device operations.
- Added comprehensive docstrings to key modules including `wireguard_tools`, `daemon_client`, `daemon`, `wg_quick`, and `wireguard_config`, detailing their functionalities and usage.
- Improved method-level documentation for clarity on parameters, return types, and exceptions.
- Updated the `__init__.py` files to provide module-level documentation for better understanding of the package structure and public API.
- Enhanced comments and type hints throughout the codebase to improve maintainability and readability.
@jaharkes
Copy link
Copy Markdown
Member

Overall comments without looking at the details.

  • This PR is not going to be merged as is. It does things that are completely out of scope for this project. (wg-daemon)
  • It adds docstrings to a file that is not exposed API, that has an explicit comment that states that the intent is to keep that file as close to the original public domain code from a gist as possible. The AI you used to write the docstrings clearly didn't read the comment. (
  • If an AI can write the docstring, then we probably don't need to have a docstring (this can be debated). In my experience code and documentation almost always diverge, so it is better to have understandable code and then you can have an AI explain it to you if it isn't quite clear.
  • The one exception I can see to this would be for exposed APIs, because then the documentation becomes part of the 'contract' with the user of the API, if there is a discrepancy between code and documentation it implies the contract was breached and the API is not kept stable.

From the summary it looks like there are a lot of useful improvements here as well, but... simply not going to merge a PR that changes just about every single file and touches over 5000 lines just to get a few valuable nuggets.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants