How do I make a REST call over the WireGuard protocol only using UDP?

718 views Asked by At

I need to call a REST API which is inside a WireGuard VPN. The WireGuard VPN is client managed, so I can't modify it. I am just provided with a configuration file to access the VPN.

I require a code-only solution that can function with only the capability of sending UDP packets. My application cannot modify the network configuration of either my network, or the REST API server's network.

This code solution may use Go, Python or JavaScript, as our existing codebase already uses all of these languages.

If there is a sufficiently useful solution in Java or C#, we can add those languages to our build chain.

2

There are 2 answers

2
Justin Ludwig On

WireGuard is designed to be used as a network interface, not as an application programming interface. It does not have an API.

If you have routing conflicts between a WireGuard network and the other networks to which a client is connected, and the client is running Linux, you could use policy routing rules on the client to ensure that only the traffic of a specific application or user is routed to the client's WireGuard interface. Alternately, you could run your application in a separate network namespace on the client, and attach the WireGuard interface just to that namespace.

If you really want to send and receive WireGuard traffic without setting up a network interface, you could create a custom application with the wireguard-go client, and use Go's virtual network stack (from the gVisor project) to set up a virtual WireGuard interface that can be accessed only from within that application. The wireguard-go source code includes a neat example of doing this to GET an HTTP resource:

root/tun/netstack/examples/http_client.go


it's certain that client WireGuard IPs will conflict with each other

Whatever you do, you must supply each client with its own IP address within the WireGuard network, as well as its own key pair. If the WireGuard server on the REST API side of the connection receives a request from two different clients using the same IP address or key pair at approximately the same time (i.e., receives a request from one before it can send a response to the other), it will try to send both responses to the client from which it received the last request (and nothing to the other client).

0
yeerk On

I independently found the same library Justin Ludwig referenced, and used it to implement a full solution.

This seems to allow programmatically calling any REST service that is behind a WireGuard VPN.

It sounds this might be overkill (gVisor "implements a substantial portion of the Linux system surface"?!), however it works and is more than fast enough for my use case.

package main

import (
    "io"
    "log"
    "net/http"
    "net/netip"

    "golang.zx2c4.com/wireguard/conn"
    "golang.zx2c4.com/wireguard/device"
    "golang.zx2c4.com/wireguard/tun/netstack"
)

func restCall(
    privateKey string,
    publicKey string,
    allowedIP string,
    endpoint string,
    selfIP string,
    dnsIP string,
    url string,
) []byte {
    tun, tnet, err := netstack.CreateNetTUN(
        []netip.Addr{netip.MustParseAddr(selfIP)},
        []netip.Addr{netip.MustParseAddr(dnsIP)},
        1420)
    if err != nil {
        log.Panic(err)
    }
    dev := device.NewDevice(tun, conn.NewDefaultBind(), device.NewLogger(device.LogLevelVerbose, ""))

    err = dev.IpcSet(`private_key=` + privateKey + `
public_key=` + publicKey + `
allowed_ip=` + allowedIP + `
endpoint=` + endpoint + `
`)
    if err != nil {
        log.Panic(err)
    }
    err = dev.Up()
    if err != nil {
        log.Panic(err)
    }

    client := http.Client{
        Transport: &http.Transport{
            DialContext: tnet.DialContext,
        },
    }
    resp, err := client.Get(url)
    if err != nil {
        log.Panic(err)
    }
    body, err := io.ReadAll(resp.Body)
    if err != nil {
        log.Panic(err)
    }
    print("Finished request", string(body)[:100])

    return body
}

func main() {
    restCall(
        "389fb0fbadd9880e09b7278f1712f0...",
        "679d50a357ebc91e602c07d9af88f4...",
        "0.0.0.0/0",
        "X.X.X.131:21576",
        "192.168.X.X",
        "10.0.0.254",
        "http://X/apicall",
    )
}