Files
cunicu/pkg/core/interface.go
Steffen Vogel 70460f7f7e refactor callback handlers
Signed-off-by: Steffen Vogel <post@steffenvogel.de>
2022-07-27 13:39:18 +02:00

331 lines
6.8 KiB
Go

package core
import (
"fmt"
"io"
"os"
"time"
"go.uber.org/zap"
"golang.zx2c4.com/wireguard/wgctrl"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"riasc.eu/wice/internal/util"
"riasc.eu/wice/internal/wg"
"riasc.eu/wice/pkg/crypto"
"riasc.eu/wice/pkg/device"
"riasc.eu/wice/pkg/pb"
)
type Interface struct {
// Wireguard handle of device
wg.Device
// OS abstractions for kernel device
KernelDevice device.KernelDevice
Peers map[crypto.Key]*Peer
LastSync time.Time
client *wgctrl.Client
onModified []InterfaceModifiedHandler
onPeer []PeerHandler
logger *zap.Logger
}
func (i *Interface) OnModified(h InterfaceModifiedHandler) {
i.onModified = append(i.onModified, h)
}
func (i *Interface) OnPeer(h PeerHandler) {
i.onPeer = append(i.onPeer, h)
}
func (i *Interface) Close() error {
i.logger.Info("Closing interface")
if err := i.KernelDevice.Close(); err != nil {
return err
}
return nil
}
// Name returns the Wireguard interface name
func (i *Interface) Name() string {
return i.Device.Name
}
// PublicKey returns the Curve25519 public key of the Wireguard interface
func (i *Interface) PublicKey() crypto.Key {
return crypto.Key(i.Device.PublicKey)
}
// PublicKey returns the Curve25519 private key of the Wireguard interface
func (i *Interface) PrivateKey() crypto.Key {
return crypto.Key(i.Device.PrivateKey)
}
func (i *Interface) WireguardConfig() *wgtypes.Config {
cfg := &wgtypes.Config{
PrivateKey: (*wgtypes.Key)(i.PrivateKey().Bytes()),
ListenPort: &i.ListenPort,
FirewallMark: &i.FirewallMark,
}
for _, peer := range i.Peers {
cfg.Peers = append(cfg.Peers, *peer.WireguardConfig())
}
return cfg
}
func (i *Interface) DumpConfig(wr io.Writer) error {
cfg := wg.Config{
Config: *i.WireguardConfig(),
}
return cfg.Dump(wr)
}
func (i *Interface) Marshal() *pb.Interface {
return pb.NewInterface((*wgtypes.Device)(&i.Device))
}
func (i *Interface) Sync(new *wgtypes.Device) (InterfaceModifier, []wgtypes.Peer, []wgtypes.Peer) {
old := i.Device
mod := InterfaceModifiedNone
// Compare device properties
if new.Name != old.Name {
i.logger.Info("Name changed",
zap.Any("old", old.Name),
zap.Any("new", new.Name),
)
mod |= InterfaceModifiedName
}
// Compare device properties
if new.Type != old.Type {
i.logger.Info("Type changed",
zap.Any("old", old.Type),
zap.Any("new", new.Type),
)
mod |= InterfaceModifiedType
}
if new.FirewallMark != old.FirewallMark {
i.logger.Info("FirewallMark changed",
zap.Any("old", old.FirewallMark),
zap.Any("new", new.FirewallMark),
)
mod |= InterfaceModifiedFirewallMark
}
if new.PrivateKey != old.PrivateKey {
i.logger.Info("PrivateKey changed",
zap.Any("old", old.PrivateKey),
zap.Any("new", new.PrivateKey),
)
mod |= InterfaceModifiedPrivateKey
}
if new.ListenPort != old.ListenPort {
i.logger.Info("ListenPort changed",
zap.Any("old", old.ListenPort),
zap.Any("new", new.ListenPort),
)
mod |= InterfaceModifiedListenPort
}
peersAdded, peersRemoved, peersKept := util.DiffSliceFunc(old.Peers, new.Peers, wg.CmpPeers)
if len(peersAdded) > 0 || len(peersRemoved) > 0 {
mod |= InterfaceModifiedPeers
}
// Call handlers
i.Device = wg.Device(*new)
i.LastSync = time.Now()
if mod != InterfaceModifiedNone {
i.logger.Info("Interface modified",
zap.Any("modified", mod),
)
for _, h := range i.onModified {
h.OnInterfaceModified(i, &old, mod)
}
}
for _, wgp := range peersRemoved {
p, ok := i.Peers[crypto.Key(wgp.PublicKey)]
if !ok {
i.logger.Warn("Failed to find matching peer", zap.Any("peer", wgp.PublicKey))
continue
}
i.logger.Info("Peer removed", zap.Any("peer", p.PublicKey()))
for _, h := range i.onPeer {
h.OnPeerRemoved(p)
}
delete(i.Peers, p.PublicKey())
}
for _, wgp := range peersAdded {
i.logger.Info("Peer added", zap.Any("peer", wgp.PublicKey))
p, err := NewPeer(&wgp, i)
if err != nil {
i.logger.Fatal("Failed to setup peer",
zap.Error(err),
zap.Any("peer", p.PublicKey),
)
}
i.Peers[p.PublicKey()] = p
for _, h := range i.onPeer {
h.OnPeerAdded(p)
}
p.Sync(&wgp)
}
for _, wgp := range peersKept {
p, ok := i.Peers[crypto.Key(wgp.PublicKey)]
if !ok {
i.logger.Warn("Failed to find matching peer", zap.Any("peer", wgp.PublicKey))
continue
}
p.Sync(&wgp)
}
return mod, peersAdded, peersRemoved
}
func (i *Interface) SyncConfig(cfgFilename string) error {
cfgFile, err := os.Open(cfgFilename)
if err != nil {
return fmt.Errorf("failed to open config file %s: %w", cfgFilename, err)
}
cfg, err := wg.ParseConfig(cfgFile, i.Name())
if err != nil {
return fmt.Errorf("failed to parse configuration: %s", err)
}
if err := i.Configure(cfg.Config); err != nil {
return fmt.Errorf("failed to configure interface: %w", err)
}
i.logger.Info("Synchronized configuration", zap.String("config_file", cfgFilename))
return nil
}
func (i *Interface) Configure(cfg wgtypes.Config) error {
if err := i.client.ConfigureDevice(i.Name(), cfg); err != nil {
return fmt.Errorf("failed to sync interface config: %s", err)
}
// TODO: Emulate wg-quick behavior here?
// E.g. all Pre/Post-Up/Down scripts
return nil
}
func (i *Interface) AddPeer(pk crypto.Key) error {
cfg := wgtypes.Config{
Peers: []wgtypes.PeerConfig{
{
PublicKey: wgtypes.Key(pk),
},
},
}
return i.client.ConfigureDevice(i.Name(), cfg)
}
func (i *Interface) RemovePeer(pk crypto.Key) error {
cfg := wgtypes.Config{
Peers: []wgtypes.PeerConfig{
{
PublicKey: wgtypes.Key(pk),
Remove: true,
},
},
}
return i.client.ConfigureDevice(i.Name(), cfg)
}
func NewInterface(wgDev *wgtypes.Device, kDev device.KernelDevice, client *wgctrl.Client) (*Interface, error) {
var err error
logger := zap.L().Named("interface").With(
zap.String("intf", wgDev.Name),
)
if kDev == nil {
kDev, err = device.FindDevice(wgDev.Name)
if err != nil {
return nil, err
}
}
i := &Interface{
Device: wg.Device(*wgDev),
KernelDevice: kDev,
client: client,
logger: logger,
Peers: map[crypto.Key]*Peer{},
onModified: []InterfaceModifiedHandler{},
onPeer: []PeerHandler{},
}
// We purposefully prune the peer list here for an full initial sync of all peers
i.Device.Peers = nil
i.logger.Info("Added new interface")
return i, nil
}
func CreateInterface(name string, user bool, client *wgctrl.Client) (*Interface, error) {
var newDevice func(name string) (device.KernelDevice, error)
if user {
newDevice = device.NewUserDevice
} else {
newDevice = device.NewKernelDevice
}
kDev, err := newDevice(name)
if err != nil {
return nil, err
}
// Connect to UAPI
wgDev, err := client.Device(name)
if err != nil {
return nil, err
}
i, err := NewInterface(wgDev, kDev, client)
if err != nil {
return nil, err
}
return i, nil
}