Skip to content

Instantly share code, notes, and snippets.

@4np
Last active March 29, 2024 11:06
Show Gist options
  • Save 4np/a2ba4631c1cfe7326ecc6dbc82b95dec to your computer and use it in GitHub Desktop.
Save 4np/a2ba4631c1cfe7326ecc6dbc82b95dec to your computer and use it in GitHub Desktop.
Setting up WireGuard to secure iPhone traffic

WireGuard

How to set up WireGuard as an iOS VPN configuration in order to tunnel all traffic from an iOS or iPadOS device through a Linux server.

While there are many configurations possible, my intentation was to secure the traffic from my iOS device by routing it securely through a Linux server. As such, this HowTo will show you how to set-up an initial working configuration, which you can customize afterward.

Most likely things will be missing, such as how to configure the firewall or the init scripts on your Linux distribution. As it won't be possible to cover all Linux flavors this HowTo will attempt to give you a working WireGuard configuration, leaving it up to you to fill-in the blanks. Feel free to write a comment to touch upon your specific situation to help others that might stumble upon this HowTo :)

Note: In these configuration examples I use 10.8.0.1/24 and 10.8.0.2/24. However, as WireGuard IPs may not overlap your other network interfaces. If your existing interfaces use these ranges, you need to use different ranges for your WireGuard configurations._

What is WireGuard

WireGuard® describes it self as an extremely simple yet fast and modern VPN that utilizes state-of-the-art cryptography. It aims to be faster, simpler, leaner, and more useful than IPsec, while avoiding the massive headache. It intends to be considerably more performant than OpenVPN. WireGuard is designed as a general purpose VPN for running on embedded interfaces and super computers alike, fit for many different circumstances. Initially released for the Linux kernel, it is now cross-platform (Windows, macOS, BSD, iOS, Android) and widely deployable. It is currently under heavy development, but already it might be regarded as the most secure, easiest to use, and simplest VPN solution in the industry.

General concept

Traditionally, you would set up a VPN servers and connect clients to the server to route (some of) their traffic via the VPN.

WireGuard is different as there is no real separation between clients and servers. Instead, in WireGuard all devices are peers, using pre-defined public / private keys to connect to eachother and to encrypt traffic between them. Configuring IPs (e.g. AllowdIPs) defines what traffic will be routed through the tunnel (from the sending end) and what traffic will be accepted on the receiving end. The [Interface] section of a peer's configuration configures the WireGuard interface for that peer, and the one or more [Peer] configuration sections in a peer's configuration configures all the peers it will connect to / accept connections from.

For your understanding, WireGuard is more like setting up an SSH tunnel and routing traffic over the tunnel, than setting up a VPN (like OpenVPN).

Configuring the Linux Server (the Linux peer)

Kernel Configuration

By design WireGuard does not log anything, so it can be a bit harder to debug. In order to debug WireGuard, firstly, your kernel need to be configured with dynamic debugging support. Secondly you need to use WireGuard as a kernel module. On Ubuntu, this is already the case. On other Linux flavors where you compile the kernel yourself, such as Gentoo Linux, you may need to customize your kernel configuration.

Starting with kernel 5.6, Wireguard is included in the upstream kernel sources. It is enabled via the following menuconfig option:

Enable CONFIG_WIREGUARD:

Device Drivers  --->
    [*] Network device support  --->
        [*] Network core driver support
        <M>   WireGuard secure network tunnel
            [*] Debugging checks and verbose messages

Enable CONFIG_DYNAMIC_DEBUG dynamic debugging:

Kernel Hacking --->
    printk and dmesg options  --->
        [*] Enable dynamic printk() support

Debugging

As mentioned above, your kernel needs to support dynamic debugging and the WireGuard kernel extension needs to be used as a module.

Enable debugging:

$ echo 'module wireguard +p' | sudo tee /sys/kernel/debug/dynamic_debug/control

You can inspect the debug output using dmesg (or your system log):

$ dmesg

Disable debugging:

$ echo 'module wireguard -p' | sudo tee /sys/kernel/debug/dynamic_debug/control

Installing WireGuard

Use your package manager to install WireGuard (e.g. apt install wireguard on Debian derived installations or emerge wireguard on Gentoo Linux).

Creating Linux Server (Peer) Configuration:

Configuring your server, or better, your Linux peer:

$ cd /etc/wireguard

And set the directory mask:

$ umask 077

First generate the public and private key pair for your Linux peer:

$ wg genkey | tee privatekey | wg pubkey > publickey

Copy your privatekey and create a new wg0.conf file in /etc/wireguard:

/etc/wireguard/wg0.conf:

[Interface]
Address = 10.8.0.1/24
SaveConfig = false
ListenPort = 51820
PrivateKey = <your private key>

And set up your init script and enable the WireGuard interface wg0 to be enabled at boot:

Gentoo Linux:

$ ln -s /etc/init.d/wg-quick /etc/init.d/wg-quick.wg0
$ rc-update add wg-quick.wg0 default

At this point you can bring up your wg0 interface:

Gentoo Linux:

$ /etc/init.d/wg-quick.wg0 start

Your Linux server now has a new wg0 interface with IP address 10.8.0.1, which you can ping.

$ wg
interface: wg0
  public key: <the public key of your linux peer>
  private key: (hidden)
  listening port: 51820
$ ifconfig
...
wg0: flags=209<UP,POINTOPOINT,RUNNING,NOARP>  mtu 1420
        inet 10.8.0.1  netmask 255.255.255.0  destination 10.8.0.1
        unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00  txqueuelen 1000  (UNSPEC)
        RX packets 37689  bytes 5130384 (4.8 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 141857  bytes 175519208 (167.3 MiB)
        TX errors 0  dropped 21 overruns 0  carrier 0  collisions 0
$ ping -c 1 10.8.0.1
PING 10.8.0.1 (10.8.0.1) 56(84) bytes of data.
64 bytes from 10.8.0.1: icmp_seq=1 ttl=64 time=0.124 ms

--- 10.8.0.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.124/0.124/0.124/0.000 ms

Creating iOS Client Configuration (Peer) called FooPhone:

The iOS App is easiest set up using a QR code. Personally I prefer to keep the peer configuration stored on the server for easy safe keeping and QR-code generation, so here I will do the same.

On your Linux server, create a new directory to hold your peer configurations. This directory or the files stored within are not used by the server configuration (your Linux peer), but just by you.

Start by creating a new peers configuration directory:

$ cd /etc/wireguard
$ mkdir -p peers/FooPhone
$ cd peers/FooPhone

Then in the new peers/FooPhone directory, create a public / private key pair for your iOS client (or iOS peer) FooPhone:

$ wg genkey | tee privatekey | wg pubkey > publickey

Create a new configuration, using the newly created FooPhone private key, and the public key of your Linux server (Linux peer):

/etc/wireguard/peers/FooPhone/FooPhone.conf:

[Interface]
PrivateKey = <FooPhone private key>
Address = 10.8.0.2/24
DNS = 1.1.1.1, 1.0.0.1

[Peer]
PublicKey = <Linux peer public key>
Endpoint = your-linux-peer.com:51820
AllowedIPs = 0.0.0.0/0
#AllowedIPs = 10.8.0.0/24
PersistentKeepalive = 30

Now head back to your Linux peer's wg0.conf and add the iOS Peer to the configuration as a new peer:

/etc/wireguard/wg0.conf:

[Interface]
Address = 10.8.0.1/24
SaveConfig = false
ListenPort = 51820
PrivateKey = <linux peer's private key>

[Peer]
PublicKey = <FooPhone public key>
AllowedIPs = 10.8.0.2/32

Restart wireguard to enable the peer and confirm that you see the peer configuration:

$ /etc/init.d/wg-quick.wg0 restart
$ wg
interface: wg0
  public key: <Linux peer public key>
  private key: (hidden)
  listening port: 51820

peer: <FooPhone / iOS peer public key>
  allowed ips: 10.8.0.2/32

Lastly, ensure the firewall on your Linux server will allow UDP connections to port 51820. For example, using NFT you could use something like this:

table inet filter {
	chain input {
		...
		udp dport 51820 meta nftrace set 1
		iifname "eno0" udp dport 51820 accept comment "accept all connections from WAN to WireGuard"
		iifname "wg0" tcp dport 53 ip saddr 10.0.0.1 accept comment "Allow DNS for VPN"
		iifname "wg0" udp dport 53 ip saddr 10.0.0.1 accept comment "Allow DNS for VPN"
	}

	chain output {
		...
	}

	chain forward {
		...
		iifname "wg0" oifname "eno0" counter packets 0 bytes 0 goto wg0_to_internet
		oifname "wg0" iifname "eno0" counter packets 0 bytes 0 goto internet_to_wg0
		iifname "wg0" oifname "wg0" counter packets 0 bytes 0 goto wg0_to_wg0
	}

	chain wg0_to_internet {
		counter packets 0 bytes 0 accept comment "policy"
	}

	chain internet_to_wg0 {
		counter packets 0 bytes 0 jump from_any_to_tunnel_peers
		counter packets 0 bytes 0 drop comment "policy"
	}

	chain wg0_to_wg0 {
		counter packets 0 bytes 0 jump from_any_to_tunnel_peers
		counter packets 0 bytes 0 drop comment "policy"
	}

	chain from_any_to_tunnel_peers {
	}
}

Now your configuration is done.

Configuring the WireGuard iOS app:

Install the WireGuard iOS app if you have not yet done so.

Then, on your Linux server (the Linux peer) execute the following command to generate a QR code in your terminal emulator for your FooPhone iOS Peer:

$ qrencode -t ansiutf8 -r /etc/wireguard/peers/FooPhone/FooPhone.conf

In the WireGuard iOS app, tap the + item in the navigation bar, select Create from QR code and scan the generated QR code. Give the new configuration some name that makes sense to you (for example home) and perform the next steps to have the new configuration saved as a VPN profile on your FooPhone iPhone.

Establishing the connection

If you want to debug the connection, this is probably where you would want to enable dynamic debugging:

$ echo 'module wireguard +p' | sudo tee /sys/kernel/debug/dynamic_debug/control

If you toggle the newly created configuration on your FooPhone iPhone to On, WireGuard on your iPhone should perform a handshake with your Linux peer.

Ensure you see the new entries endpoint, latest handshake and transfer on your Linux server.

$ wg
interface: wg0
  public key: <Linux peer public key>
  private key: (hidden)
  listening port: 51820

peer: <FooPhone / iOS peer public key>
  endpoint: <public ip of your FooPhone>:20109
  allowed ips: 10.8.0.2/32
  latest handshake: 0 hour, 1 minutes, 48 seconds ago
  transfer: 0.89 MiB received, 1.39 MiB sent
$ dmesg
...
[140710.234202] wireguard: wg0: Receiving handshake initiation from peer 1 <FooPhone public IP>:20108)
[140710.234220] wireguard: wg0: Sending handshake response to peer 1 (<FooPhone public IP>:20108)
[140710.236466] wireguard: wg0: Keypair 29 destroyed for peer 1
[140710.236477] wireguard: wg0: Keypair 31 created for peer 1
[140710.276811] wireguard: wg0: Receiving keepalive packet from peer 1 (<FooPhone public IP>:20108)
[140728.961078] wireguard: wg0: Receiving keepalive packet from peer 1 (<FooPhone public IP>:20108)

Now you should be able to ping the IP address of FooPhone from your Linux server (peer):

$ ping -c 1 10.8.0.2
PING 10.8.0.2 (10.8.0.2) 56(84) bytes of data.
64 bytes from 10.8.0.2: icmp_seq=1 ttl=64 time=268 ms

--- 10.8.0.2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 268.309/268.309/268.309/0.000 ms

Routing and Firewall

This is where I will stop as the connection has been established. As FooPhone has been configured with AllowedIPs = 0.0.0.0/0, all traffic from your iPhone will be routed via the WireGuard tunnel. The server does not limit this, but you could defined a smaller range or even a single IP on the server (e.g. linux peer)'s configuration.

For the rest ensure your firewall, routing and NAT configuration on your Linux server are configured to allow connections to UDP 51820, and to forward / masquerade traffic from wg0.

Network speed

WireGuard is very fast, but it's limited by your Linux Server's upstream speed. In case you use an asynchronous connection, like Cable or DSL, the bandwidth of your iPhone peer is limited by the maximum upstream speed of your Linux peer's Cable / DSL connection.

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