Skip to content

Add native Windows support via wintun and in-process PPP engine#1334

Open
hkirste wants to merge 22 commits into
adrienverge:masterfrom
hkirste:windows-support
Open

Add native Windows support via wintun and in-process PPP engine#1334
hkirste wants to merge 22 commits into
adrienverge:masterfrom
hkirste:windows-support

Conversation

@hkirste
Copy link
Copy Markdown

@hkirste hkirste commented Mar 30, 2026

Summary

  • Native Windows 10+ support using the wintun TUN driver and an in-process PPP LCP/IPCP negotiation engine, eliminating the pppd dependency on Windows
  • Cross-platform CMake build system added alongside existing autotools (both coexist, no changes to autotools)
  • Windows-specific code lives in dedicated source files selected at build time — existing Unix code paths are untouched

Architecture

On Unix, openfortivpn forks pppd for PPP negotiation and uses a PTY for communication. On Windows, pppd is unavailable, so this PR replaces it with:

  • ppp.c — In-process PPP state machine implementing LCP and IPCP negotiation per RFC 1661/1332
  • wintun.h — Dynamic loading of wintun.dll (lightweight TUN driver from the WireGuard project)
  • io_win.c — 5-thread I/O model: ssl_read, ssl_write, tun_read, tun_write, if_config
  • tunnel_win.c — Windows tunnel lifecycle and wintun adapter management
  • ipv4_win.c — Route/DNS management via Windows IP Helper API (iphlpapi)
  • compat_win32.h — POSIX-to-Win32 shims (ssize_t, signals, sockets, string functions)

Shared headers have minimal #ifdef _WIN32 guards (config.h, tunnel.h, io.h, ipv4.h, ssl.h). Shared source files (http.c, main.c, config.c) required only include guards for POSIX headers.

New files (Windows-only, not compiled on Unix)

File Purpose
src/ppp.c, src/ppp.h PPP LCP/IPCP state machine
src/io_win.c Windows I/O loop
src/tunnel_win.c Wintun adapter lifecycle, SSL/TLS, auth
src/ipv4_win.c Routes and DNS via IP Helper API
src/userinput_win.c Password input via SetConsoleMode
src/log_win.c Windows logging with ANSI color
src/http_server_win.c Winsock2-based SAML HTTP server
src/wintun.h Wintun API dynamic loader
src/compat_win32.h POSIX compatibility shims
lib/getopt/getopt.c Bundled getopt_long for MSVC
CMakeLists.txt Cross-platform CMake build
tests/test_ppp.c 13 PPP unit tests
installer/openfortivpn.nsi NSIS Windows installer
.github/workflows/windows.yml CI: MinGW-w64 + CMake builds

Build & runtime requirements

  • MinGW-w64 (MSYS2) or MSVC (VS2022 BuildTools + vcpkg)
  • OpenSSL 1.0.2+
  • wintun.dll (from https://www.wintun.net/, placed alongside exe)
  • Administrator privileges (TUN adapter + route management)

Testing

  • 13 PPP unit tests (LCP/IPCP negotiation, echo, terminate, protocol-reject, full flow) — all pass on both MinGW and MSVC
  • MSVC 19.44 (VS2022) build verified with vcpkg OpenSSL + PThreads4W
  • MinGW-w64 (MSYS2) build verified
  • End-to-end VPN tunnel tested against a production FortiGate gateway on Windows 11:
    • Authentication, PPP negotiation, wintun adapter creation
    • Split-tunnel routing (25 routes applied)
    • DNS configuration via netsh
    • Bidirectional IP traffic confirmed with ping across multiple subnets (63-87ms RTT)

Test plan

  • Verify Unix autotools build is unaffected (./configure && make && make check)
  • Verify CMake Linux build (mkdir build && cd build && cmake .. && make)
  • Verify MinGW-w64 Windows build
  • Verify MSVC Windows build
  • Run PPP unit tests (ctest or ./test_ppp)
  • Test VPN connection on Windows against a FortiGate gateway

claude and others added 19 commits March 30, 2026 03:55
Replace pppd dependency on Windows with an in-process PPP LCP/IPCP
negotiation engine (ppp.c) and wintun TUN adapter. Windows-specific
code lives in dedicated source files (io_win.c, tunnel_win.c,
ipv4_win.c, etc.) selected at build time by CMake, keeping existing
Unix code paths completely untouched.

Key components:
- CMakeLists.txt: Cross-platform CMake build alongside existing autotools
- ppp.c/ppp.h: PPP state machine handling LCP/IPCP negotiation (RFC 1661/1332)
- wintun.h: Dynamic loading of wintun.dll TUN driver
- io_win.c: Windows I/O loop with 5-thread model
- tunnel_win.c: Windows tunnel lifecycle (wintun adapter management)
- ipv4_win.c: Route management via Windows IP Helper API (iphlpapi)
- userinput_win.c: Console password input via SetConsoleMode
- log_win.c: Windows logging with ANSI color and Event Log support
- http_server_win.c: Winsock2-based SAML HTTP server
- compat_win32.h: POSIX-to-Win32 compatibility shims
- lib/getopt/: Bundled getopt_long for MSVC
- tests/test_ppp.c: 13 unit tests for PPP state machine
- .github/workflows/windows.yml: CI for Windows (MinGW) and CMake builds
- README.md: Windows build/install instructions

Modified shared headers with minimal #ifdef _WIN32 guards:
config.h, tunnel.h, io.h, ipv4.h, ssl.h, main.c, config.c

Both autotools (Unix) and CMake (all platforms) build systems verified.
- .github/workflows/release.yml: On tag push, build Windows (MinGW-w64)
  and Linux binaries, bundle wintun.dll + OpenSSL DLLs, and upload as
  GitHub Release artifacts.
- installer/openfortivpn.nsi: NSIS installer script that installs
  openfortivpn.exe + wintun.dll, adds to PATH, creates example config,
  and registers in Add/Remove Programs.
- Add -DBUILD_TESTING=ON to CMake configure
- Use PowerShell for wintun download and zip packaging (more reliable
  on Windows runners than MSYS2 wget/7z)
- Allow test step to continue on failure (tests need Windows APIs that
  may not be fully available in CI)
- Add PThreads4W dependency for MSVC (MinGW has built-in pthreads)
- Add MSVC compatibility shims: strcasecmp, strncasecmp, strtok_r,
  memmem, strcasestr, getline
- Fix sys/types.h and io.h header conflicts on MSVC
- Guard unistd.h/arpa/inet.h includes in shared http.c
- Fix wintun LOAD_FUNC macro case mismatch (CreateAdapter vs
  CREATE_ADAPTER)
- Fix crash in wintun_configure_ip: GetAdapterLUID called with NULL
  adapter handle
- Defer split route installation until TUN adapter LUID is available
- Properly store and close wintun adapter handle on cleanup
The bundled getopt_long for MSVC returned -1 immediately when
encountering a non-option argument, requiring the host to always
be placed after all options. Add GNU-style permutation so arguments
like "openfortivpn vpn.example.com -u user -p pass" work correctly.
- Guard pid_t typedef with _MSC_VER (MinGW already defines it)
- Move strcasestr/memmem shims outside _MSC_VER guard for MinGW
- Fix wintun.h typedef indentation (tabs to spaces per astyle)
- Fix tunnel_win.c continuation line indentation
- Move getline shim outside _MSC_VER guard (MinGW also lacks it)
- Fix default case indentation in ppp_process_incoming per astyle
MinGW provides POSIX isatty()/fileno() from <unistd.h>, not MSVC's
_isatty()/_fileno() from <io.h>. Use POSIX names and alias for MSVC.
- Fix case block indentation in ppp.c (astyle expects brace at case level)
- Put default: and { on same line per astyle
- Fix continuation line indentation in ipv4_win.c
Apply astyle --style=linux formatting: use spaces for continuation
indentation, place opening brace on same line as case labels.
- Remove static zero-initialization (ipv4_win.c)
- Join quoted strings split across lines (ipv4_win.c, tunnel_win.c,
  http_server_win.c)
- Fix block comment trailing */ placement (test_ppp.c)
- Simplify FAIL macro to single statement (test_ppp.c)
- Move extern declarations to headers: isatty/fileno to compat_win32.h,
  ipv4_win_set_tun_luid/ipv4_apply_deferred_routes to ipv4.h
- Restructure WINAPI function pointer typedefs in wintun.h to use
  two-step typedefs that avoid confusing checkpatch's spacing rules
- Add typedefs.checkpatch for Windows API types and update checkpatch.sh
  to use --typedefsfile
- Inline wintun LOAD_FUNC macro into explicit GetProcAddress calls
- Replace ASSERT macro do-while with simple conditional
ReadConsoleA fails silently when stdin is a pipe (e.g., when a GUI
redirects stdin). Detect pipe vs console via GetConsoleMode and use
ReadFile for piped input.
pthread_cancel is broken on MinGW-w64 and causes ACCESS_VIOLATION.
Replaced with cooperative shutdown: SSL_shutdown unblocks SSL threads,
EndSession unblocks TUN threads, poison pills unblock pool_pop.
ipv4_add_split_vpn_route now stores routes for deferred application
after adapter creation. Gateway route failure no longer blocks split
routes in split-tunnel mode.
…ndle

Removed broken GetAdapterLUID call with NULL adapter in
wintun_configure_ip. Store adapter handle for proper cleanup.
run_tunnel returns specific exit codes (10=DNS, 13=cert, 20=auth, etc.)
but main() was overwriting them all with EXIT_FAILURE (1).
New --json-events flag emits JSON on stderr for GUI integration.
exit_codes.h defines specific codes for each failure type.
Join split JSON format string literals onto single lines to satisfy
checkpatch SPLIT_STRING without needing an ignore rule. Add event.c,
event.h, and exit_codes.h to Makefile.am for autotools builds.
@iKlsR
Copy link
Copy Markdown

iKlsR commented May 20, 2026

Literally randomly checked in and searched windows to see if there was any update (over a year since I last checked) since I want to bundle a single binary to dump some logs and was pleasantly surprised to see this. cmake .. -G "MinGW Makefiles" && make builds this in like 2 seconds and works out the gate. Thanks for undertaking this.

Capture

@DimitriPapadopoulos
Copy link
Copy Markdown
Collaborator

The thing is that we already have a branch with an in-process PPP engine that has been in test for quite some time:
https://github.com/adrienverge/openfortivpn/tree/tun

I was thinking of eventually merging into the main branch. I'm not sure how easy it is to merge those two branches. And which PPP engine to use? I like that you have added unit tests — we should have more of them and ideally even set up a mockup Fortinet server for tests. The PPP engine should run on POSIX systems too.

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.

4 participants