mirror of
https://codeberg.org/cunicu/cunicu.git
synced 2025-10-27 22:02:25 +08:00
208 lines
4.5 KiB
Go
208 lines
4.5 KiB
Go
package wice
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
"net/url"
|
||
|
||
"go.uber.org/zap"
|
||
"golang.zx2c4.com/wireguard/wgctrl"
|
||
"riasc.eu/wice/pkg/config"
|
||
"riasc.eu/wice/pkg/device"
|
||
"riasc.eu/wice/pkg/feat"
|
||
"riasc.eu/wice/pkg/feat/epdisc"
|
||
"riasc.eu/wice/pkg/util"
|
||
"riasc.eu/wice/pkg/watcher"
|
||
"riasc.eu/wice/pkg/wg"
|
||
|
||
"riasc.eu/wice/pkg/signaling"
|
||
)
|
||
|
||
type Daemon struct {
|
||
*watcher.Watcher
|
||
|
||
Features []feat.Feature
|
||
|
||
EndpointDiscovery *epdisc.EndpointDiscovery
|
||
|
||
// Shared
|
||
Backend *signaling.MultiBackend
|
||
Client *wgctrl.Client
|
||
Config *config.Config
|
||
|
||
devices []device.Device
|
||
|
||
stop chan any
|
||
reexecOnClose bool
|
||
|
||
logger *zap.Logger
|
||
}
|
||
|
||
func NewDaemon(cfg *config.Config) (*Daemon, error) {
|
||
var err error
|
||
|
||
// Check permissions
|
||
if !util.HasAdminPrivileges() {
|
||
return nil, errors.New("insufficient privileges. Please run ɯice as root user or with NET_ADMIN capabilities")
|
||
}
|
||
|
||
d := &Daemon{
|
||
Config: cfg,
|
||
devices: []device.Device{},
|
||
stop: make(chan any),
|
||
}
|
||
|
||
d.logger = zap.L().Named("daemon")
|
||
|
||
// Create WireGuard netlink socket
|
||
d.Client, err = wgctrl.New()
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to create WireGuard client: %w", err)
|
||
}
|
||
|
||
// Create watcher
|
||
if d.Watcher, err = watcher.New(d.Client, cfg.WatchInterval, cfg.WireGuard.InterfaceFilter.Regexp.MatchString); err != nil {
|
||
return nil, fmt.Errorf("failed to initialize watcher: %w", err)
|
||
}
|
||
|
||
// Create backend
|
||
urls := []*url.URL{}
|
||
for _, u := range cfg.Backends {
|
||
urls = append(urls, &u.URL)
|
||
}
|
||
|
||
d.Backend, err = signaling.NewMultiBackend(urls, &signaling.BackendConfig{
|
||
OnReady: []signaling.BackendReadyHandler{},
|
||
})
|
||
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to initialize signaling backend: %w", err)
|
||
}
|
||
|
||
// Check if WireGuard interface can be created by the kernel
|
||
if !cfg.WireGuard.Userspace {
|
||
cfg.WireGuard.Userspace = !wg.KernelModuleExists()
|
||
}
|
||
|
||
d.Features, d.EndpointDiscovery = feat.NewFeatures(d.Watcher, d.Config, d.Client, d.Backend)
|
||
|
||
for _, feat := range d.Features {
|
||
if err := feat.Start(); err != nil {
|
||
return nil, err
|
||
}
|
||
}
|
||
|
||
return d, nil
|
||
}
|
||
|
||
func (d *Daemon) Run() error {
|
||
if err := wg.CleanupUserSockets(); err != nil {
|
||
return fmt.Errorf("failed to cleanup stale userspace sockets: %w", err)
|
||
}
|
||
|
||
if err := d.CreateDevicesFromArgs(); err != nil {
|
||
return fmt.Errorf("failed to create devices: %w", err)
|
||
}
|
||
|
||
signals := util.SetupSignals(util.SigUpdate)
|
||
|
||
d.logger.Debug("Started initial synchronization")
|
||
if err := d.Watcher.Sync(); err != nil {
|
||
d.logger.Fatal("Initial synchronization failed", zap.Error(err))
|
||
}
|
||
d.logger.Debug("Finished initial synchronization")
|
||
|
||
go d.Watcher.Watch()
|
||
|
||
out:
|
||
for {
|
||
select {
|
||
case sig := <-signals:
|
||
d.logger.Debug("Received signal", zap.String("signal", sig.String()))
|
||
switch sig {
|
||
case util.SigUpdate:
|
||
if err := d.Sync(); err != nil {
|
||
d.logger.Error("Failed to synchronize interfaces", zap.Error(err))
|
||
}
|
||
default:
|
||
break out
|
||
}
|
||
|
||
case <-d.stop:
|
||
break out
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func (d *Daemon) Stop() {
|
||
close(d.stop)
|
||
d.logger.Debug("Stopping daemon")
|
||
}
|
||
|
||
func (d *Daemon) Restart() {
|
||
d.reexecOnClose = true
|
||
close(d.stop)
|
||
d.logger.Debug("Restarting daemon")
|
||
}
|
||
|
||
func (d *Daemon) Close() error {
|
||
for _, dev := range d.devices {
|
||
if err := dev.Close(); err != nil {
|
||
return fmt.Errorf("failed to delete device: %w", err)
|
||
}
|
||
}
|
||
|
||
if err := d.Watcher.Close(); err != nil {
|
||
return fmt.Errorf("failed to close interface: %w", err)
|
||
}
|
||
|
||
for _, feat := range d.Features {
|
||
if err := feat.Close(); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
if err := d.Client.Close(); err != nil {
|
||
return fmt.Errorf("failed to close WireGuard client: %w", err)
|
||
}
|
||
|
||
d.logger.Debug("Closed daemon")
|
||
|
||
if d.reexecOnClose {
|
||
return util.ReexecSelf()
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func (d *Daemon) CreateDevicesFromArgs() error {
|
||
var devs wg.Devices
|
||
var err error
|
||
|
||
if devs, err = d.Client.Devices(); err != nil {
|
||
return fmt.Errorf("failed to get existing WireGuard devices: %w", err)
|
||
}
|
||
|
||
for _, devName := range d.Config.WireGuard.Interfaces {
|
||
if !d.Config.WireGuard.InterfaceFilter.MatchString(devName) {
|
||
return fmt.Errorf("device '%s' is not matched by WireGuard interface filter '%s'",
|
||
devName, d.Config.WireGuard.InterfaceFilter.String())
|
||
}
|
||
|
||
if wgdev := devs.GetByName(devName); wgdev != nil {
|
||
return fmt.Errorf("device '%s' already exists", devName)
|
||
}
|
||
|
||
dev, err := device.NewDevice(devName, d.Config.WireGuard.Userspace)
|
||
if err != nil {
|
||
return fmt.Errorf("failed to create WireGuard device: %w", err)
|
||
}
|
||
|
||
d.devices = append(d.devices, dev)
|
||
}
|
||
|
||
return nil
|
||
}
|