Go Implementation of WireGuard in userspace
This is an implementation of WireGuard fully in userspace. The repository is a fork of wireguard-go.
Most distribution of WireGuard are implemented in kernel space and rely on interfaces to functions, userspace-wireguard, however, functions fully in userspace and is meant to be used programmatically.
This sections describes the different components of userspace-wireguard and how to use them. Check the Example section for a full example.
A logger
provides logging for a device using two (Printf-style) functions Verbosef
and Errorf
. Logging functions do not require a trailing newline in the format.
A logger can be created using the logger.NewLogger
function. The first argument is the log level, which can be one of logger.LogLevelSilent
, logger.LogLevelError
, or logger.LogLevelVerbose
(for debugging). The second argument is the prepend for the logger.
A tun.Device
is used to send/receive clear text packets on behalf of WireGuard peers. It is responsible for NAT management and routing from the local subnet of a WireGuard device to the internet.
The only available implementation of tun.Device
is tun.UserspaceTun
which is also the main contribution of this repository. The userspace tun makes use of github.com/urnetwork/connect
to send/receive packets fully in userspace.
A tun.Device
can be created using the tun.CreateUserspaceTUN
function which requires a logger
and possibly the public IPs for correct NAT. Additionally, a tun device provides access to an events
channel which can be used to listen for events on the tun device through the functions Events
and AddEvent
. The possible types of events are tun.EventUp
, tun.EventDown
, and tun.EventMTUUpdate
.
Other available functions include Read
, Write
, MTU
, Close
, and BatchSize
.
A device.Device
is used to manage a WireGuard device. It is responsible for creating and managing peers, sending/receiving packets (encrypted and plaintext) and handshakes, and managing the state of the device. Here is stored the static WireGuard identity (public and private key) of the device.
A device.Device
can be created using the device.NewDevice
function which requires a tun.Device
, logger
and conn.Bind
as arguments. The conn.Bind
is used to send handshakes and encrypted packets and a default implementation can be acquired using conn.NewDefaultBind
.
A device also provides access to add events to the underlying events channel of the tun device using the AddEvent
function. Other useful functions inlcude Close
, Wait
, IpcSet
, and IpcGet
.
The official WireGuard projects provides a cross-platform userspace implementation for consistency in configuration and management of WireGuard devices. A device can be configured using this interface using the device.IpcSet
and device.IpcGet
functions. For ease of use in stead of using textual configs we use wgtypes objects.
To get the current configuration of a device, use the device.IpcGet
function which returns a wgtypes.Device
object. To set the configuration of a device, use the device.IpcSet
function which requires a wgtypes.Config
object as an argument.
Below is an example of a simple WireGuard server setup using userspace-wireguard. If you want to view a full example, check EXAMPLE_SETUP.md
and main.go
in the root directory.
// debug logging
logger := logger.NewLogger(logger.LogLevelVerbose, "(userspace)")
// tun device (with public IPv4 but no public IPv6)
utun, err := tun.CreateUserspaceTUN(logger, net.IPv4(1, 1, 1, 1), nil)
if err != nil {
panic(err)
}
// wireguard device
device := device.NewDevice(utun, conn.NewDefaultBind(), logger)
// TODO: set your configuration for server
config := wgtypes.Config{ }
if err := device.IpcSet(&config); err != nil {
panic(err)
}
device.AddEvent(tun.EventUp) // start up the device
// wait for program to terminate
term := make(chan os.Signal, 1)
signal.Notify(term, syscall.SIGTERM)
signal.Notify(term, os.Interrupt)
select {
case <-term:
case <-device.Wait():
}
device.Close() // clean up