wg-quicker/vendor/golang.zx2c4.com/wireguard/wgctrl/internal/wgopenbsd/client_openbsd.go

394 lines
11 KiB
Go

//+build openbsd
package wgopenbsd
import (
"bytes"
"encoding/binary"
"fmt"
"net"
"os"
"runtime"
"time"
"unsafe"
"golang.org/x/sys/unix"
"golang.zx2c4.com/wireguard/wgctrl/internal/wginternal"
"golang.zx2c4.com/wireguard/wgctrl/internal/wgopenbsd/internal/wgh"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
var (
// ifGroupWG is the WireGuard interface group name passed to the kernel.
ifGroupWG = [16]byte{0: 'w', 1: 'g'}
)
var _ wginternal.Client = &Client{}
// A Client provides access to OpenBSD WireGuard ioctl information.
type Client struct {
// Hooks which use system calls by default, but can also be swapped out
// during tests.
close func() error
ioctlIfgroupreq func(ifg *wgh.Ifgroupreq) error
ioctlWGGetServ func(wgs *wgh.WGGetServ) error
ioctlWGGetPeer func(wgp *wgh.WGGetPeer) error
}
// New creates a new Client and returns whether or not the ioctl interface
// is available.
func New() (*Client, bool, error) {
// The OpenBSD ioctl interface operates on a generic AF_INET socket.
fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)
if err != nil {
return nil, false, err
}
// TODO(mdlayher): find a call to invoke here to probe for availability.
// c.Devices won't work because it returns a "not found" error when the
// kernel WireGuard implementation is available but the interface group
// has no members.
// By default, use system call implementations for all hook functions.
return &Client{
close: func() error { return unix.Close(fd) },
ioctlIfgroupreq: ioctlIfgroupreq(fd),
ioctlWGGetServ: ioctlWGGetServ(fd),
ioctlWGGetPeer: ioctlWGGetPeer(fd),
}, true, nil
}
// Close implements wginternal.Client.
func (c *Client) Close() error {
return c.close()
}
// Devices implements wginternal.Client.
func (c *Client) Devices() ([]*wgtypes.Device, error) {
ifg := wgh.Ifgroupreq{
// Query for devices in the "wg" group.
Name: ifGroupWG,
}
// Determine how many device names we must allocate memory for.
if err := c.ioctlIfgroupreq(&ifg); err != nil {
return nil, err
}
// ifg.Len is size in bytes; allocate enough memory for the correct number
// of wgh.Ifgreq and then store a pointer to the memory where the data
// should be written (ifgrs) in ifg.Groups.
//
// From a thread in golang-nuts, this pattern is valid:
// "It would be OK to pass a pointer to a struct to ioctl if the struct
// contains a pointer to other Go memory, but the struct field must have
// pointer type."
// See: https://groups.google.com/forum/#!topic/golang-nuts/FfasFTZvU_o.
ifgrs := make([]wgh.Ifgreq, ifg.Len/wgh.SizeofIfgreq)
ifg.Groups = &ifgrs[0]
// Now actually fetch the device names.
if err := c.ioctlIfgroupreq(&ifg); err != nil {
return nil, err
}
// Keep this alive until we're done doing the ioctl dance.
runtime.KeepAlive(&ifg)
devices := make([]*wgtypes.Device, 0, len(ifgrs))
for _, ifgr := range ifgrs {
// Remove any trailing NULL bytes from the interface names.
d, err := c.Device(string(bytes.TrimRight(ifgr.Ifgrqu[:], "\x00")))
if err != nil {
return nil, err
}
devices = append(devices, d)
}
return devices, nil
}
// Device implements wginternal.Client.
func (c *Client) Device(name string) (*wgtypes.Device, error) {
d, pkeys, err := c.getServ(name)
if err != nil {
return nil, err
}
d.Peers = make([]wgtypes.Peer, 0, len(pkeys))
for _, pk := range pkeys {
p, err := c.getPeer(d.Name, pk)
if err != nil {
return nil, err
}
d.Peers = append(d.Peers, *p)
}
return d, nil
}
// ConfigureDevice implements wginternal.Client.
func (c *Client) ConfigureDevice(name string, cfg wgtypes.Config) error {
// Currently read-only: we must determine if a device belongs to this driver,
// and if it does, return a sentinel so integration tests that configure a
// device can be skipped.
if _, err := c.Device(name); err != nil {
return err
}
return wginternal.ErrReadOnly
}
// getServ fetches a device and the public keys of its peers using an ioctl.
func (c *Client) getServ(name string) (*wgtypes.Device, []wgtypes.Key, error) {
nb, err := deviceName(name)
if err != nil {
return nil, nil, err
}
// Fetch information for the specified device, and indicate that we have
// pre-allocated room for peer public keys. 8 is the initial array size
// value used by ncon's wg fork.
wgs := wgh.WGGetServ{
Name: nb,
Num_peers: 8,
}
var (
// The number of peer public keys we should allocate space for, and
// the slice where keys are allocated.
n int
bkeys [][wgtypes.KeyLen]byte // []wgtypes.Key equivalent
)
for {
// Updated on each loop iteration to provide enough space in case the
// kernel tells us we need to provide more space.
n = int(wgs.Num_peers)
// See the comment in Devices about passing Go pointers within a
// structure to ioctl.
bkeys = make([][wgtypes.KeyLen]byte, n)
wgs.Peers = &bkeys[0]
// Query for a device by its name.
if err := c.ioctlWGGetServ(&wgs); err != nil {
// ioctl functions always return a wrapped unix.Errno value.
// Conform to the wgctrl contract by converting "no such device" and
// "inappropriate ioctl" to "not exist".
switch err.(*os.SyscallError).Err {
case unix.ENXIO, unix.ENOTTY:
return nil, nil, os.ErrNotExist
default:
return nil, nil, err
}
}
// Did the kernel tell us there are more peers than can fit in our
// current memory? If not, we're done.
if int(wgs.Num_peers) <= n {
// Re-slice to the exact size needed.
bkeys = bkeys[:wgs.Num_peers:wgs.Num_peers]
break
}
}
// wgtypes.Key has an identical memory layout with [wgtypes.KeyLen]byte, so
// cast the slice directly.
keys := *(*[]wgtypes.Key)(unsafe.Pointer(&bkeys))
return &wgtypes.Device{
Name: name,
Type: wgtypes.OpenBSDKernel,
PrivateKey: wgs.Privkey,
PublicKey: wgs.Pubkey,
ListenPort: int(wgs.Port),
}, keys, nil
}
// getPeer fetches a peer associated with a device and a public key.
func (c *Client) getPeer(device string, pubkey wgtypes.Key) (*wgtypes.Peer, error) {
nb, err := deviceName(device)
if err != nil {
return nil, err
}
// The algorithm implemented here is the same as the one documented in
// getServ, but we are fetching WGIP allowed IP arrays instead of peer
// public keys. See the more in-depth documentation there.
// 16 is the initial array size value used by ncon's wg fork.
wgp := wgh.WGGetPeer{
Name: nb,
Pubkey: pubkey,
Num_aip: 16,
}
var (
n int
aips []wgh.WGCIDR
)
for {
n = int(wgp.Num_aip)
// See the comment in Devices about passing Go pointers within a
// structure to ioctl.
aips = make([]wgh.WGCIDR, n)
wgp.Aip = &aips[0]
// Query for a peer by its associated device and public key.
if err := c.ioctlWGGetPeer(&wgp); err != nil {
return nil, err
}
// Did the kernel tell us there are more allowed IPs than can fit in our
// current memory? If not, we're done.
if int(wgp.Num_aip) <= n {
// Re-slice to the exact size needed.
aips = aips[:wgp.Num_aip:wgp.Num_aip]
break
}
}
endpoint, err := parseEndpoint(wgp.Ip)
if err != nil {
return nil, err
}
allowedIPs, err := parseAllowedIPs(aips)
if err != nil {
return nil, err
}
return &wgtypes.Peer{
PublicKey: pubkey,
PresharedKey: wgp.Psk,
Endpoint: endpoint,
PersistentKeepaliveInterval: time.Duration(wgp.Pka) * time.Second,
LastHandshakeTime: time.Unix(
wgp.Last_handshake.Sec,
// Conversion required on openbsd/386.
int64(wgp.Last_handshake.Nsec),
),
ReceiveBytes: int64(wgp.Rx_bytes),
TransmitBytes: int64(wgp.Tx_bytes),
AllowedIPs: allowedIPs,
}, nil
}
// deviceName converts an interface name string to the format required to pass
// with wgh.WGGetServ.
func deviceName(name string) ([16]byte, error) {
var out [unix.IFNAMSIZ]byte
if len(name) > unix.IFNAMSIZ {
return out, fmt.Errorf("wgopenbsd: interface name %q too long", name)
}
copy(out[:], name)
return out, nil
}
// parseEndpoint parses a peer endpoint from a wgh.WGIP structure.
func parseEndpoint(ip wgh.WGIP) (*net.UDPAddr, error) {
// sockaddr* structures have family at index 1.
switch ip[1] {
case unix.AF_INET:
sa := *(*unix.RawSockaddrInet4)(unsafe.Pointer(&ip[0]))
ep := &net.UDPAddr{
IP: make(net.IP, net.IPv4len),
Port: bePort(sa.Port),
}
copy(ep.IP, sa.Addr[:])
return ep, nil
case unix.AF_INET6:
sa := *(*unix.RawSockaddrInet6)(unsafe.Pointer(&ip[0]))
// TODO(mdlayher): IPv6 zone?
ep := &net.UDPAddr{
IP: make(net.IP, net.IPv6len),
Port: bePort(sa.Port),
}
copy(ep.IP, sa.Addr[:])
return ep, nil
default:
// No endpoint configured.
return nil, nil
}
}
// bePort interprets a port integer stored in native endianness as a big
// endian value. This is necessary for proper endpoint port handling on
// little endian machines.
func bePort(port uint16) int {
b := *(*[2]byte)(unsafe.Pointer(&port))
return int(binary.BigEndian.Uint16(b[:]))
}
// parseAllowedIPs parses allowed IPs from a slice of wgh.WGCIDR structures.
func parseAllowedIPs(aips []wgh.WGCIDR) ([]net.IPNet, error) {
ipns := make([]net.IPNet, 0, len(aips))
for _, aip := range aips {
var size, masklen int
switch aip.Af {
case unix.AF_INET:
size, masklen = net.IPv4len, 32
case unix.AF_INET6:
size, masklen = net.IPv6len, 128
default:
return nil, fmt.Errorf("wgopenbsd: unrecognized allowed IP address family: %d", aip.Af)
}
// Copy the array from aip to retain it.
ip := make(net.IP, size)
copy(ip, aip.Ip[:size])
ipns = append(ipns, net.IPNet{
IP: ip,
Mask: net.CIDRMask(int(aip.Mask), masklen),
})
}
return ipns, nil
}
// ioctlIfgroupreq returns a function which performs the appropriate ioctl on
// fd to retrieve members of an interface group.
func ioctlIfgroupreq(fd int) func(*wgh.Ifgroupreq) error {
return func(ifg *wgh.Ifgroupreq) error {
return ioctl(fd, wgh.SIOCGIFGMEMB, unsafe.Pointer(ifg))
}
}
// ioctlWGGetServ returns a function which performs the appropriate ioctl on
// fd to fetch information about a WireGuard device.
func ioctlWGGetServ(fd int) func(*wgh.WGGetServ) error {
return func(wgs *wgh.WGGetServ) error {
return ioctl(fd, wgh.SIOCGWGSERV, unsafe.Pointer(wgs))
}
}
// ioctlWGGetPeer returns a function which performs the appropriate ioctl on
// fd to fetch information about a peer associated with a WireGuard device.
func ioctlWGGetPeer(fd int) func(*wgh.WGGetPeer) error {
return func(wgp *wgh.WGGetPeer) error {
return ioctl(fd, wgh.SIOCGWGPEER, unsafe.Pointer(wgp))
}
}
// ioctl is a raw wrapper for the ioctl system call.
func ioctl(fd int, req uint, arg unsafe.Pointer) error {
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(req), uintptr(arg))
if errno != 0 {
return os.NewSyscallError("ioctl", errno)
}
return nil
}