Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

496 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

pterodapter

Build status

Logo

pterodapter is a userspace IKEv2-to-FortiVPN adapter that acts as an L3 IKEv2 VPN server (a subset of RFC 7296 is implemented) and forwards traffic to a FortiVPN network.

Previous versions also supported a proxy mode, which was deprecated once IKEv2 improved support for DNS-based split routing (NAT64 SIIT with split-horizon DNS64).

The last version supporting proxy mode is 0.5.0.

IKEv2 VPN mode

IKEv2 connection diagram

Root permissions are not required, but unfortunately most IKEv2 clients can only use standard ports 500 and 4500. Listening on port 500 requires elevated permissions or port-forwarding; the IKEv2 server needs to run externally to prevent port conflicts.

To avoid granting root permissions, the following command grants permissions to listen on ports < 1024 without granting full root access:

sudo setcap CAP_NET_BIND_SERVICE=+eip pterodapter

To run pterodapter locally, use WSL on Windows, or add firewall redirection on macOS.

L2 Gateway mode

L2 Gateway connection diagram

The L2 Gateway mode acts as a regular L2 gateway/router. Instead of IKEv2, pterodapter listens to L2 ethernet frames matching the NAT64 prefix, and acts just like a regular network gateway router.

  • On macOS, pterodapter uses the vmnet framework to create a dedicated bridge adapter, similar to a TAP adapter. This works without root permissions in macOS 26+.

  • In Linux (on a real Linux system or a Linux container), the raw socket API is used. ⚠️ In some cases Linux applies segmentation offloading such as GSO/GRO, which requires additional processing in software. Network speed is about 40% lower than IKEv2.

How to use it

Building the Docker container

Run docker build . or a similar build command for podman, container or your build system.

L2 Gateway mode

Run pterodapter with the following arguments:

pterodapter [--log-level=<level>] [--listen-interface=<iface>] [--fix-mtu] [--process-tso] --fortivpn=<hostport> --nat64-prefix=<ip6prefix> [--dns64-tunnel-suffix=<domain>] [--pcap=<filename>] l2gateway

--log-level=<level> is an optional argument to specify the log level, for example --log-level=debug.

--listen-interface=<ip-address> specifies the network interface which the gateway should be listening on, for example --listen-interface=eth0.

--fix-mtu is an optional argument indicating that the network interface (specified in --listen-interface) should have its MTU increased to 1500. Enabled by default in macOS.

--process-tso is an optional argument indicating that incoming TCP traffic uses TSO (TCP segmentation offloading) and that additional processing is needed: refragmentation of TCP packets and recalculation of checksums. Enable if TCP uploads are unusually slow (1 Mbit/s or less); not needed on macOS.

--fortivpn=<hostport> specifies the FortiVPN connection address, for example --fortivpn=fortivpn.example.com:443.

--nat64-prefix=<ip6prefix> specified the NAT64 network prefix to use , for example --nat64-prefix=64:ff9b:: will remap IPv4 addresses to a /96 IPv6 subnet matching 64:ff9b::-64:ff9b::ffff:ffff. In NAT64 mode, pterodapter will intercept DNS responses and remap external IPv4 addresses to IPv6 addresses in the specified subnet. This is done only for domains matching a suffix listed in --tunnel-domain. The IKEv2 client will use IPv6 traffic, which is translated into IPv4 and sent to VPN, based on the SIIT algorithm documented in RFC 7915. This approach simplifies the routing table (IKEv2 Traffic Selector) to use only one network or traffic selector; it also allows to use domain suffixes and handle DNS updates without reconnecting the client. Inspired by ideas from Microsoft DirectAccess.

--dns64-tunnel-suffix=<domain> specifies an optional argument indicating that <domain> and its subdomains should be sent through the VPN using NAT64 (DNS64). To specify multiple domains, add a --dns64-tunnel-suffix argument for each one. If no --dns64-tunnel-suffix arguments are specified, DNS64 won't be used, but will still remain available - for example, to be used with a custom DNS64 server.

--pcap=<filename> specifies an optional argument indicating that all IP traffic passing through pterodapter should be captured into the specified PCAP file.

Network permissions

Linux

To use the l2gateway mode, pterodapter needs to be running in Linux (or a Linux-based container) and needs CAP_NET_RAW permissions:

setcap cap_net_raw+eip pterodapter

For the --fix-mtu option to work, pterodapter also needs CAP_NET_ADMIN permissions:

setcap cap_net_raw,cap_net_admin+eip pterodapter

⚠️ It's best to run the l2gateway mode in a container, as a non-root user and with the minimum available permissions.

If uploads are unusually slow, try using the --process-tso flag to see if Linux is returning packets without full checksums, or if TCP packets exceed the MTU (the kernel sends unfragmented packets). Or alternatively see if there's an option to disable GSO/GRO/TSO.

macOS

On macOS 26+, no additional permissions are required. Use the macos/sign.sh script to add the com.apple.security.virtualization entitlement; it's self-signed and works even without an Apple Developer account.

⚠️ Sometimes copying/moving the binary will lose its signature, and re-running macos/sign.sh might be necessary.

Routing traffic to the VPN

In macOS, starting pterodapter in l2gateway mode will create a new bridge interface, typically bridge100 (if Apple Container is not running) or bridge101. Check to see what's the interface name by running ifconfig -l before and after starting pterodapter.

To send traffic to the VPN, simply add the new bridge interface as a route:

BRIDGE_INTERFACE=bridge100
# Add a route for the NAT64 prefix
sudo route add -inet6 64:ff9b::/64 -interface $BRIDGE_INTERFACE
# Route DNS for gitlab.example.com to the container's DNS64 address
cat << EOF | sudo tee /etc/resolver/gitlab.example.com
nameserver 64:ff9b::808:808
nameserver 64:ff9b::808:404
search_order 1
EOF

macOS Limit IP address tracking needs to be disabled for DNS64 to work (even when iCloud Private Relay is not enabled).

IKEv2 VPN

Run pterodapter with the following arguments:

pterotapter [--log-level=<level>] [--listen-ip=<ip-address>] [--ike-port=<port>] [--nat-port=<port>] --fortivpn=<hostport> [--tunnel-domain=<domain>] [--nat64-prefix=<ip6prefix>] [--dns64-tunnel-suffix=<domain>] [--id-hostname=<hostname>] --cacert=<filename> --cert=<filename> --key=<filename> [--pcap=<filename>] ikev2

--log-level=<level> is an optional argument to specify the log level, for example --log-level=debug.

--listen-ip=<ip-address> is an optional argument to specify the IKEv2 listen IP address, for example --listen-ip=127.0.0.1. If not specified, will use :: as the listen address (all IPv4 and IPv6 addresses). Multiple addresses can be specified.

--ike-port=<port> is an optional argument to specify the IKEv2 listen port, for example --ike-port=9500. If not specified, will use port 500 (the default IKEv2 port).

--nat-port=<port> is an optional argument to specify the NAT port for IKEv2 and ESP, for example --nat-port=9501. If not specified, will use port 4500 (the default IKEv2 and ESP NAT port).

--fortivpn=<hostport> specifies the FortiVPN connection address, for example --fortivpn=fortivpn.example.com:443.

--tunnel-domain=<domain> specifies an optional argument indicating that only <domain> should be sent through the VPN, and all other domains should use a direct connection. To specify multiple domains, add a --tunnel-domain argument for each one; if no --tunnel-domain arguments are specified, all traffic will be sent through the VPN. This is implemented using IKEv2 traffic selectors and works with no extra configuration on macOS; Windows needs routes to be added manually. To ensure that dynamic IPs are handled correctly, pterodapter will send updated routes (IKEv2 Traffic Selectors) when the client rekeys the session. This option only affects IPv4 traffic. For NAT64 split tunnel routing, use the --dns64-tunnel-suffix argument.

--nat64-prefix=<ip6prefix> specifies an optional argument indicating that NAT64 mode should be enabled, for example --nat64-prefix=64:ff9b:: will remap IPv4 addresses to a /96 IPv6 subnet matching 64:ff9b::-64:ff9b::ffff:ffff. In NAT64 mode, pterodapter will intercept DNS responses and remap external IPv4 addresses to IPv6 addresses in the specified subnet. This is done only for domains matching a suffix listed in --tunnel-domain. The IKEv2 client will use IPv6 traffic, which is translated into IPv4 and sent to VPN, based on the SIIT algorithm documented in RFC 7915. This approach simplifies the routing table (IKEv2 Traffic Selector) to use only one network or traffic selector; it also allows to use domain suffixes and handle DNS updates without reconnecting the client. Inspired by ideas from Microsoft DirectAccess.

--dns64-tunnel-suffix=<domain> specifies an optional argument indicating that <domain> and its subdomains should be sent through the VPN using NAT64 (DNS64). To specify multiple domains, add a --dns64-tunnel-suffix argument for each one. If no --dns64-tunnel-suffix arguments are specified, DNS64 won't be used, but will still remain available - for example, to be used with a custom DNS64 server.

--id-hostname=<hostname> specifies the hostname to send to the client when performing a client handshake. If not specified, will use pterodapter as the hostname. Windows refuses to connect if the hostname doesn't match connection settings; macOS prints a warning in the Console.

--cacert=<filename> specifies the path to a root CA PEM file, required for two-way authentication.

--cert=<filename> specifies the path to the server's public cert PEM file, required for two-way authentication.

--key=<filename> specifies the path to the server's private key PEM file (matching the public cert specified in --cert), required for two-way authentication.

--pcap=<filename> specifies an optional argument indicating that all IP traffic passing through pterodapter should be captured into the specified PCAP file.

For example:

./pterodapter --log-level=trace \
    --listen-ip=127.0.0.1 \
    --fortivpn=fortivpn.example.com:443 \
    --tunnel-domain=gitlab.example.com \
    --tunnel-domain=registry.example.com \
    --nat64-prefix=64:ff9b:: \
    --dns64-tunnel-suffix=example.com \
    --id-hostname=pterodapter.home \
    --cacert=vpn-root.cert.pem \
    --cert=vpn-server.cert.pem \
    --key=vpn-server.key.pem \
    ikev2

For more information how to generate certs and configure clients, see the certs.md document.

For information how to run pterodapter as a systemd unit, see the systemd.md document.

For information how to enable split routing in Windows, see the windows-split-routing.md document.

Running on the same host

IKEv2 relies on fixed port numbers (500 and 4500) and most implementations (macOS and Windows built-in VPN clients) don't allow to specify a custom port number. Additionally, IKEv2 uses fixed ports on the client side as well.

This means running pterodapter on the same host as the client could cause issues.

  • For Windows, run pterodapter in WSL with NAT networking mode (mirrored networking might work but is untested).
  • For macOS, create a virtual IP in built-in packet filter .

Reference

FortiVPN implementations

Used to study and re-implement the FortiVPN protocol:

The idea of using a proxy (to run as non-root) was originally suggested in the OpenConnect documentation.

WireGuard gateways

WireGuard's VPN client is available in the App Store, and supports split tunneling out of the box. Could be a possible alternative to proxies - keep split tunneling, but avoid conversion between OSI layers.

These examples show how to do L2/L3 conversion and emulate remote endpoints.

  • onetun - listen on a socket and redirect to a server through WireGuard
  • wgslirpy - runs a WireGuard server and redirects to an external service

About

An all-in-one single-user IKEv2 server, FortiVPN client and NAT64 to bridge them together

Topics

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Used by

Contributors

Languages