#wireguard #nat #tcp-udp #udp-socket #tcp-socket #slirp #onetun

app wgslirpy

User-space Wireguard to internet router, like SLiRP, but with Wireguard instead of PPP

2 unstable releases

0.2.0 Aug 13, 2023
0.1.0 Aug 13, 2023

#1935 in Network programming

MIT/Apache

78KB
1.5K SLoC

wgslirpy

A command line tool (and a Rust library) for accepting incoming connections within a Wireguard link and routing them to external network using usual opeating system's socket API.
This allows creating a sort of VPN server without root access, e.g. from Android app or when using unprivileged containers.

Diagram depicting operation of Wgslirpy

Features

  • Maintaining Wireguard connection with one peer, using Boringtun library.
  • Decoding incoming TCP or UDP connections using smoltcp library
  • Forwarding TCP and UDP connections from Wireguard to external world, exchanging traffic userspace and real sockets.
  • For UDP, hole punching / NAT traversal should work (not tested though)
  • For TCP, half-closed connections and backpressure should work
  • Crude DNS server for resolving IPv4 and IPv6 addresses using host DNS implementation

Limitations

  • No ICMP (except of pings to specific address for testing). This affects UDP's "port unreachable"s.

Demo session

Setup
peer# wg genkey
4Khaa5tgPI9NJsO2R896Yd6748k9fW4aapGZnIcUM14=
peer# wg pubkey <<< 4Khaa5tgPI9NJsO2R896Yd6748k9fW4aapGZnIcUM14=
rPpCjWzIv/yAtZZi+C/pVprie8D0QaGlPtJXlDi6bmI=

gateway$ wg genkey
SG43Zi0wGp4emfJ/XpTnnmtnK8SSjjIHOc3Zh37c928=
gateway$ wg pubkey <<< SG43Zi0wGp4emfJ/XpTnnmtnK8SSjjIHOc3Zh37c928=
MR2RF5Tp+6BKt9k+deKg1GqR3re3ckJKti+uwZA84DU=

peer# ip link add wgslirpyspeer type wireguard
peer# wg set wgslirpyspeer listen-port 9796 private-key <(echo 4Khaa5tgPI9NJsO2R896Yd6748k9fW4aapGZnIcUM14=) peer MR2RF5Tp+6BKt9k+deKg1GqR3re3ckJKti+uwZA84DU= allowed-ips 0.0.0.0/0,::/0
peer# ip netns add testing-wgslirp
peer# ip link set wgslirpyspeer netns testing-wgslirp
peer# ip netns exec testing-wgslirp ip link set wgslirpyspeer up
peer# ip netns exec testing-wgslirp ip addr add 192.168.76.1/32 dev wgslirpyspeer
peer# ip netns exec testing-wgslirp ip addr add fc00::01/128 dev wgslirpyspeer
peer# ip netns exec testing-wgslirp route -4 add default dev wgslirpyspeer
peer# ip netns exec testing-wgslirp route -6 add default dev wgslirpyspeer
peer# mkdir -p /etc/netns/testing-wgslirp
peer# echo nameserver 192.168.72.2 > /etc/netns/testing-wgslirp/resolv.conf
Activation
gateway$ RUST_LOG=debug wgslirpy --private-key SG43Zi0wGp4emfJ/XpTnnmtnK8SSjjIHOc3Zh37c928= -b 127.0.0.1:9797 --peer-key rPpCjWzIv/yAtZZi+C/pVprie8D0QaGlPtJXlDi6bmI= --keepalive-interval 10 --dns 192.168.72.2:53 --pingable 192.168.72.2
DEBUG boringtun::noise: Sending handshake_initiation
DEBUG boringtun::noise: Received handshake_response local_idx=1 remote_idx=2743606023
DEBUG boringtun::noise: New session session=1
DEBUG boringtun::noise: Sending keepalive

peer# ip netns exec testing-wgslirp wg
interface: wgslirpyspeer
  public key: rPpCjWzIv/yAtZZi+C/pVprie8D0QaGlPtJXlDi6bmI=
  private key: (hidden)
  listening port: 9796

peer: MR2RF5Tp+6BKt9k+deKg1GqR3re3ckJKti+uwZA84DU=
  endpoint: 127.0.0.1:9797
  allowed ips: 0.0.0.0/0, ::/0
  latest handshake: 46 seconds ago
  transfer: 340 B received, 92 B sent
Testing
peer# # ip netns exec testing-wgslirp ping -c 2 192.168.72.2
64 bytes from 192.168.72.2: icmp_seq=1 ttl=64 time=0.705 ms
64 bytes from 192.168.72.2: icmp_seq=2 ttl=64 time=0.435 ms

gateway$ 
INFO wgslirpy::router: New NAT entry for Pingable
DEBUG wgslirpy::router: Finished serving Pingable

peer# ip netns exec testing-wgslirp curl http://example.com/
<!doctype html>
...
</html>

gateway$
DEBUG wgslirpy::router::serve_dns: DNS query example.com:0
DEBUG wgslirpy::router: Sending DNS reply

INFO wgslirpy::router: New NAT entry for Tcp { client_side: Endpoint { addr: Ipv4(Address([192, 168, 76, 1])), port: 48004 }, external_side: Endpoint { addr: Ipv4(Address([93, 184, 216, 34])), port: 80 } }
DEBUG wgslirpy::router::serve_tcp: Connected to upstream TCP
DEBUG wgslirpy::router::serve_tcp: Accepted the connection
DEBUG wgslirpy::router::serve_tcp: EOF received from client
DEBUG wgslirpy::router::serve_tcp: Shutdown finished
DEBUG wgslirpy::router::serve_tcp: EOF
DEBUG wgslirpy::router::serve_tcp: Client TCP socket no longer active
DEBUG boringtun::noise::timers: KEEPALIVE(PERSISTENT_KEEPALIVE)
DEBUG wgslirpy::router: Finished serving Tcp { client_side: Endpoint { addr: Ipv4(Address([192, 168, 76, 1])), port: 48004 }, external_side: Endpoint { addr: Ipv4(Address([93, 184, 216, 34])), port: 80 } }

peer# ip netns exec testing-wgslirp dig +short github.com @8.8.8.8
140.82.112.3

gateway$
01:26:14 INFO wgslirpy::router: New NAT entry for Udp
...
01:27:46 DEBUG wgslirpy::router::serve_udp: Timed out a UDP connection

Installation

Download a pre-built executable from Github releases or install from source code with cargo install --path . or cargo install wgslirpy.

CLI options

wgslirpy --help output
Usage: wgslirpy [-k <private-key>] [-f <private-key-file>] -K <peer-key> [-p <peer-endpoint>] [-a <keepalive-interval>] -b <bind-ip-port> [-D <dns>] [-P <pingable>] [--mtu <mtu>] [--tcp-buffer-size <tcp-buffer-size>] [--transmit-queue-capacity <transmit-queue-capacity>] [-u <incoming-udp...>] [-t <incoming-tcp...>]

Expose internet access without root using Wireguard

Options:
  -k, --private-key main private key of this Wireguard node, base64-encoded
  -f, --private-key-file
                    main private key of this Wireguard node (content of a
                    specified file), base64-encoded
  -K, --peer-key    peer's public key
  -p, --peer-endpoint
                    address of the peer's UDP socket, where to send keepalives
  -a, --keepalive-interval
                    keepalive interval, in seconds
  -b, --bind-ip-port
                    where to bind our own UDP socket for Wireguard connection
  -D, --dns         use this UDP socket address as a simple A/AAAA-only DNS
                    server within Wireguard network
  -P, --pingable    reply to ICMP pings on this single address within Wireguard
                    network
  --mtu             maximum transfer unit to use for TCP. Default is 1420.
  --tcp-buffer-size in-application socket TCP buffer size. Note that operating
                    system socket buffer also applies.
  --transmit-queue-capacity
                    nubmer of outgoing (to wireguard) packets to hold in a queue
  -u, --incoming-udp
                    forward this host UDP port into Wireguard network. You need
                    to specify triplet of socket addresses: host, source
                    (optional) and dest. Host address is address to bind
                    operating system socket to. source and dest addreses are
                    used within Wireguard network. Example: -u
                    0.0.0.0:1234,10.0.2.1:1234,10.0.2.15:1234
  -t, --incoming-tcp
                    forward this host TCP port into Wireguard network. You need
                    to specify triplet of socket addresses: host, source
                    (optional) and dest. Host address is address to bind
                    operating system socket to. source and dest addreses are
                    used within Wireguard network. If source port is 0,
                    roundrobin is used. Example: -t 0.0.0.0:1234,,10.0.2.15:1234
  --help            display usage information

See also

  • onetun - Similar idea, but is designed to forward connections to Wireguard instead of from Wireguard.
  • SLiRP - Similar idea, but with PPP (a stream-based connection) instead of packet-based Wireguard.

Dependencies

~18–30MB
~550K SLoC