Files
cunicu/internal/config/config.go
2022-01-06 15:11:25 +01:00

309 lines
9.5 KiB
Go

package config
import (
"flag"
"fmt"
"io"
"net/url"
"os"
"path/filepath"
"regexp"
"strconv"
"time"
"go.uber.org/zap"
"riasc.eu/wice/pkg/proxy"
"riasc.eu/wice/pkg/signaling"
"github.com/pion/ice/v2"
"github.com/spf13/pflag"
"github.com/spf13/viper"
)
// Copied from pion/ice/agent_config.go
const (
// defaultCheckInterval is the interval at which the agent performs candidate checks in the connecting phase
defaultCheckInterval = 200 * time.Millisecond
// keepaliveInterval used to keep candidates alive
defaultKeepaliveInterval = 2 * time.Second
// defaultDisconnectedTimeout is the default time till an Agent transitions disconnected
defaultDisconnectedTimeout = 5 * time.Second
// defaultFailedTimeout is the default time till an Agent transitions to failed after disconnected
defaultFailedTimeout = 25 * time.Second
// max binding request before considering a pair failed
defaultMaxBindingRequests = 7
)
var (
defaultICEUrls = []*ice.URL{
{
Scheme: ice.SchemeTypeSTUN,
Host: "stun.l.google.com",
Port: 19302,
Username: "",
Password: "",
Proto: ice.ProtoTypeUDP,
},
}
defaultBackendURLs = []*url.URL{
{
Scheme: "p2p",
},
}
)
type Config struct {
File string
LogLevel logLevel
Socket string
SocketWait bool
Backends backendURLList
User bool
ProxyType proxyType
ConfigSync bool
ConfigPath string
WatchInterval time.Duration
RestartInterval time.Duration
Interfaces []string
InterfaceFilter regex
InterfaceFilterICE regex
// for ice.AgentConfig
iceURLs iceURLList
iceNat1to1IPs arrayFlags
iceInsecureSkipVerify bool
iceCandidateTypes candidateTypeList
iceNetworkTypes networkTypeList
iceDisconnectedTimeout time.Duration
iceFailedTimeout time.Duration
iceKeepaliveInterval time.Duration
iceCheckInterval time.Duration
iceUsername string
icePassword string
icePortMin uint16
icePortMax uint16
iceMdns bool
iceLite bool
iceMaxBindingRequests uint16
viper *viper.Viper
logger *zap.Logger
}
func ShowUsage() {
fmt.Fprintf(os.Stderr, "usage: %s [OPTIONS] [IFACES ...]\n", os.Args[0])
fmt.Println()
fmt.Println(" IFACES is a list of Wireguard interfaces")
fmt.Println(" (defaults to all available Wireguard interfaces)")
fmt.Println("")
fmt.Println(("Available OPTIONS are:"))
flag.PrintDefaults()
fmt.Println()
fmt.Println(" (**) These options can be specified multiple times")
fmt.Println()
fmt.Println("Available backends types are:")
for name, plugin := range signaling.Backends {
fmt.Printf(" %-7s %s\n", name, plugin.Description)
}
}
func NewConfig(flags *pflag.FlagSet) *Config {
matchAll, _ := regexp.Compile(".*")
// Default arguments
cfg := &Config{
Backends: backendURLList{},
iceCandidateTypes: candidateTypeList{},
InterfaceFilterICE: regex{matchAll},
iceNat1to1IPs: arrayFlags{},
iceNetworkTypes: networkTypeList{},
iceURLs: iceURLList{},
InterfaceFilter: regex{matchAll},
Interfaces: []string{},
LogLevel: logLevel{zap.NewAtomicLevel()},
ProxyType: proxyType{proxy.TypeAuto},
viper: viper.New(),
logger: zap.L().Named("config"),
}
flags.StringVarP(&cfg.File, "config", "c", "", "Path of configuration file")
flags.VarP(&cfg.LogLevel, "log-level", "d", "log level (one of \"panic\", \"fatal\", \"error\", \"warn\", \"info\", \"debug\", \"trace\")")
flags.VarP(&cfg.Backends, "backend", "b", "backend types / URLs")
flags.VarP(&cfg.ProxyType, "proxy", "p", "proxy type to use")
flags.VarP(&cfg.InterfaceFilter, "interface-filter", "f", "regex for filtering Wireguard interfaces (e.g. \"wg-.*\")")
flags.DurationVarP(&cfg.WatchInterval, "watch-interval", "i", time.Second, "interval at which we are polling the kernel for updates on the Wireguard interfaces")
flags.BoolVarP(&cfg.User, "wg-user", "u", false, "start userspace Wireguard daemon")
flags.BoolVarP(&cfg.ConfigSync, "wg-config-sync", "s", false, "sync Wireguard interface with configuration file (see \"wg synconf\"")
flags.StringVarP(&cfg.ConfigPath, "wg-config-path", "w", "/etc/wireguard", "base path to search for Wireguard configuration files")
// ice.AgentConfig fields
flags.VarP(&cfg.iceURLs, "url", "a", "STUN and/or TURN server address (**)")
flags.Var(&cfg.iceCandidateTypes, "ice-candidate-type", "usable candidate types (**, one of \"host\", \"srflx\", \"prflx\", \"relay\")")
flags.Var(&cfg.iceNetworkTypes, "ice-network-type", "usable network types (**, select from \"udp4\", \"udp6\", \"tcp4\", \"tcp6\")")
flags.Var(&cfg.iceNat1to1IPs, "ice-nat-1to1-ip", "list of IP addresses which will be added as local server reflexive candidates (**)")
flags.Uint16Var(&cfg.icePortMin, "ice-port-min", 0, "minimum port for allocation policy (range: 0-65535)")
flags.Uint16Var(&cfg.icePortMax, "ice-port-max", 0, "maximum port for allocation policy (range: 0-65535)")
flags.BoolVarP(&cfg.iceLite, "ice-lite", "l", false, "lite agents do not perform connectivity check and only provide host candidates")
flags.BoolVarP(&cfg.iceMdns, "ice-mdns", "m", false, "enable local Multicast DNS discovery")
flags.Uint16Var(&cfg.iceMaxBindingRequests, "ice-max-binding-requests", defaultMaxBindingRequests, "maximum number of binding request before considering a pair failed")
flags.BoolVarP(&cfg.iceInsecureSkipVerify, "ice-insecure-skip-verify", "k", false, "skip verification of TLS certificates for secure STUN/TURN servers")
flags.Var(&cfg.InterfaceFilterICE, "ice-interface-filter", "regex for filtering local interfaces for ICE candidate gathering (e.g. \"eth[0-9]+\")")
flags.DurationVar(&cfg.iceDisconnectedTimeout, "ice-disconnected-timout", defaultDisconnectedTimeout, "time till an Agent transitions disconnected")
flags.DurationVar(&cfg.iceFailedTimeout, "ice-failed-timeout", defaultFailedTimeout, "time until an Agent transitions to failed after disconnected")
flags.DurationVar(&cfg.iceKeepaliveInterval, "ice-keepalive-interval", defaultKeepaliveInterval, "interval netween STUN keepalives")
flags.DurationVar(&cfg.iceCheckInterval, "ice-check-interval", defaultCheckInterval, "interval at which the agent performs candidate checks in the connecting phase")
flags.DurationVar(&cfg.RestartInterval, "ice-restart-interval", defaultDisconnectedTimeout, "time to wait before ICE restart")
flags.StringVarP(&cfg.iceUsername, "ice-user", "U", "", "username for STUN/TURN credentials")
flags.StringVarP(&cfg.icePassword, "ice-pass", "P", "", "password for STUN/TURN credentials")
// iceMaxBindingRequestTimeout := flag.Duration("ice-max-binding-request-timeout", maxBindingRequestTimeout, "wait time before binding requests can be deleted")
flags.StringVar(&cfg.Socket, "socket", "/var/run/wice.sock", "Unix control and monitoring socket")
flags.BoolVar(&cfg.SocketWait, "socket-wait", false, "wait until first client connected to control socket before continuing start")
cfg.viper.BindPFlags(flags)
return cfg
}
func (c *Config) Setup() {
// Find best proxy method
if c.ProxyType.ProxyType == proxy.TypeAuto {
c.ProxyType.ProxyType = proxy.AutoProxy()
}
// Add default backend
if len(c.Backends) == 0 {
c.Backends = defaultBackendURLs
}
if c.File != "" {
// Use config file from the flag.
viper.SetConfigFile(c.File)
} else {
home, err := os.UserHomeDir()
if err != nil {
c.logger.Warn("Failed to determine home directory", zap.Error(err))
} else {
viper.AddConfigPath(filepath.Join(home, ".config", "wice"))
}
viper.AddConfigPath("/etc/wice")
viper.SetConfigType("ini")
viper.SetConfigName("wicerc")
}
c.viper.AutomaticEnv()
if err := viper.ReadInConfig(); err == nil {
c.logger.Debug("Using config file", zap.String("file", viper.ConfigFileUsed()))
}
}
func (a *Config) AgentConfig() (*ice.AgentConfig, error) {
cfg := &ice.AgentConfig{
InsecureSkipVerify: a.iceInsecureSkipVerify,
NetworkTypes: a.iceNetworkTypes,
CandidateTypes: a.iceCandidateTypes,
Urls: a.iceURLs,
}
// Add default STUN/TURN servers
if len(cfg.Urls) == 0 {
cfg.Urls = defaultICEUrls
} else {
// Set ICE credentials
for _, u := range cfg.Urls {
if a.iceUsername != "" {
u.Username = a.iceUsername
}
if a.icePassword != "" {
u.Password = a.icePassword
}
}
}
if a.iceMaxBindingRequests > 0 {
cfg.MaxBindingRequests = &a.iceMaxBindingRequests
}
if a.iceMdns {
cfg.MulticastDNSMode = ice.MulticastDNSModeQueryAndGather
}
if a.iceDisconnectedTimeout > 0 {
cfg.DisconnectedTimeout = &a.iceDisconnectedTimeout
}
if a.iceFailedTimeout > 0 {
cfg.FailedTimeout = &a.iceFailedTimeout
}
if a.iceKeepaliveInterval > 0 {
cfg.KeepaliveInterval = &a.iceKeepaliveInterval
}
if a.iceCheckInterval > 0 {
cfg.CheckInterval = &a.iceCheckInterval
}
if len(a.iceNat1to1IPs) > 0 {
cfg.NAT1To1IPCandidateType = ice.CandidateTypeServerReflexive
cfg.NAT1To1IPs = a.iceNat1to1IPs
}
// Default network types
if len(a.iceNetworkTypes) == 0 {
cfg.NetworkTypes = append(cfg.NetworkTypes,
ice.NetworkTypeUDP4,
ice.NetworkTypeUDP6,
)
}
return cfg, nil
}
func (c *Config) Dump(wr io.Writer) {
cfg, _ := c.AgentConfig()
fmt.Fprintln(wr, "Options:")
fmt.Fprintln(wr, " URLs:")
for _, u := range cfg.Urls {
fmt.Fprintf(wr, " %s\n", u.String())
}
fmt.Fprintln(wr, " Interfaces:")
for _, d := range c.Interfaces {
fmt.Fprintf(wr, " %s\n", d)
}
fmt.Fprintf(wr, " User: %s\n", strconv.FormatBool(c.User))
fmt.Fprintf(wr, " ProxyType: %s\n", c.ProxyType.String())
fmt.Fprintf(wr, " Signalling Backends:\n")
for _, b := range c.Backends {
fmt.Fprintf(wr, " %s\n", b)
}
}