Files
cunicu/pkg/wg/device_config.go
Steffen Vogel 92a7ad2f7f daemon: use per-interface features
Signed-off-by: Steffen Vogel <post@steffenvogel.de>
2022-10-07 18:30:50 +02:00

325 lines
7.1 KiB
Go

package wg
import (
"fmt"
"io"
"net"
"strings"
"time"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"gopkg.in/ini.v1"
)
type Config struct {
wgtypes.Config
PeerEndpoints []string
PeerNames []string
Address []net.IPNet
DNS []net.IPAddr
MTU *int
Table *string
PreUp []string
PreDown []string
PostUp []string
PostDown []string
SaveConfig *bool
}
type config struct {
Interface interfaceConfig
Peers []peerConfig `ini:"Peer,nonunique"`
}
type interfaceConfig struct {
PrivateKey *string `ini:",omitempty"`
ListenPort *int `ini:",omitempty"`
FwMark *int `ini:",omitempty"`
// Settings for wg-quick
Address []string `ini:",omitempty" delim:", "`
DNS []string `ini:",omitempty" delim:", "`
MTU *int `ini:",omitempty"`
Table *string `ini:",omitempty"`
PreUp []string `ini:",omitempty,allowshadow"`
PreDown []string `ini:",omitempty,allowshadow"`
PostUp []string `ini:",omitempty,allowshadow"`
PostDown []string `ini:",omitempty,allowshadow"`
SaveConfig *bool `ini:",omitempty"`
}
type peerConfig struct {
PublicKey string `ini:",omitempty"`
PresharedKey *string `ini:",omitempty"`
AllowedIPs []string `ini:",omitempty" delim:", "`
Endpoint *string `ini:",omitempty"`
PersistentKeepalive *int `ini:",omitempty"`
}
func parseCIDRs(nets []string, ip bool) ([]net.IPNet, error) {
pn := []net.IPNet{}
for _, ne := range nets {
i, n, err := net.ParseCIDR(ne)
if err != nil {
return nil, fmt.Errorf("failed to parse network %s: %w", ne, err)
}
if ip {
n.IP = i
}
pn = append(pn, *n)
}
return pn, nil
}
func parseIPs(ips []string) ([]net.IPAddr, error) {
pips := []net.IPAddr{}
for _, ip := range ips {
i, err := net.ResolveIPAddr("ip", ip)
if err != nil {
return nil, fmt.Errorf("failed to parse ip %s: %w", ip, err)
}
pips = append(pips, *i)
}
return pips, nil
}
func (cfg *Config) Dump(wr io.Writer) error {
iniCfg := &config{
Interface: interfaceConfig{
ListenPort: cfg.ListenPort,
FwMark: cfg.FirewallMark,
},
}
if cfg.PrivateKey != nil {
pk := cfg.PrivateKey.String()
iniCfg.Interface.PrivateKey = &pk
}
// wg-quick settings
iniCfg.Interface.MTU = cfg.MTU
iniCfg.Interface.SaveConfig = cfg.SaveConfig
iniCfg.Interface.Table = cfg.Table
iniCfg.Interface.PreUp = cfg.PreUp
iniCfg.Interface.PreDown = cfg.PreDown
iniCfg.Interface.PostUp = cfg.PostUp
iniCfg.Interface.PostDown = cfg.PostDown
iniCfg.Interface.SaveConfig = cfg.SaveConfig
if len(cfg.Address) > 0 {
iniCfg.Interface.Address = []string{}
for _, addr := range cfg.Address {
iniCfg.Interface.Address = append(iniCfg.Interface.Address, addr.String())
}
}
if len(cfg.DNS) > 0 {
iniCfg.Interface.DNS = []string{}
for _, addr := range cfg.DNS {
iniCfg.Interface.DNS = append(iniCfg.Interface.DNS, addr.String())
}
}
for i, peer := range cfg.Peers {
iniPeer := peerConfig{
PublicKey: peer.PublicKey.String(),
}
if peer.PresharedKey != nil {
psk := peer.PresharedKey.String()
iniPeer.PresharedKey = &psk
}
if cfg.PeerEndpoints[i] != "" {
iniPeer.Endpoint = &cfg.PeerEndpoints[i]
} else if peer.Endpoint != nil {
ep := peer.Endpoint.String()
iniPeer.Endpoint = &ep
}
if peer.PersistentKeepaliveInterval != nil {
ka := int(*peer.PersistentKeepaliveInterval / time.Second)
iniPeer.PersistentKeepalive = &ka
}
if len(peer.AllowedIPs) > 0 {
iniPeer.AllowedIPs = []string{}
for _, aip := range peer.AllowedIPs {
iniPeer.AllowedIPs = append(iniPeer.AllowedIPs, aip.String())
}
}
iniCfg.Peers = append(iniCfg.Peers, iniPeer)
}
iniFile := ini.Empty(ini.LoadOptions{
AllowNonUniqueSections: true,
AllowShadows: true,
})
if err := iniFile.ReflectFrom(iniCfg); err != nil {
return err
}
_, err := iniFile.WriteTo(wr)
return err
}
func ParseConfig(data []byte) (*Config, error) {
var err error
iniFile, err := ini.LoadSources(ini.LoadOptions{
AllowNonUniqueSections: true,
AllowShadows: true,
}, data)
if err != nil {
return nil, err
}
// We add a pseudo peer section just allow mapping via StrictMapTo if there are no peers configured
fakePeer := !iniFile.HasSection("Peer")
if fakePeer {
iniFile.NewSection("Peer")
}
iniCfg := &config{}
if err := iniFile.StrictMapTo(iniCfg); err != nil {
return nil, fmt.Errorf("failed to parse Interface section: %s", err)
}
// Remove fake peer section again
if fakePeer {
iniCfg.Peers = nil
}
peerSects, err := iniFile.SectionsByName("Peer")
if err != nil {
return nil, err
}
cfg, err := iniCfg.Config()
if err != nil {
return nil, err
}
for _, peerSect := range peerSects {
peerName := strings.TrimPrefix(peerSect.Comment, "#")
peerName = strings.TrimSpace(peerName)
cfg.PeerNames = append(cfg.PeerNames, peerName)
}
return cfg, nil
}
func (c *config) Config() (*Config, error) {
var err error
var cfg = &Config{
Config: wgtypes.Config{
Peers: []wgtypes.PeerConfig{},
ListenPort: c.Interface.ListenPort,
FirewallMark: c.Interface.FwMark,
},
}
if c.Interface.Address != nil {
if cfg.Address, err = parseCIDRs(c.Interface.Address, true); err != nil {
return nil, err
}
}
if c.Interface.DNS != nil {
if cfg.DNS, err = parseIPs(c.Interface.DNS); err != nil {
return nil, err
}
}
if c.Interface.PrivateKey != nil {
pk, err := wgtypes.ParseKey(*c.Interface.PrivateKey)
if err != nil {
return nil, fmt.Errorf("failed to parse key: %w", err)
}
cfg.PrivateKey = &pk
}
// wg-quick settings
cfg.MTU = c.Interface.MTU
cfg.Table = c.Interface.Table
cfg.PreUp = c.Interface.PreUp
cfg.PreDown = c.Interface.PreDown
cfg.PostUp = c.Interface.PostUp
cfg.PostDown = c.Interface.PostDown
cfg.SaveConfig = c.Interface.SaveConfig
for _, pSection := range c.Peers {
peerCfg, err := pSection.Config()
if err != nil {
return nil, fmt.Errorf("failed to parse peer config: %w", err)
}
cfg.Peers = append(cfg.Peers, *peerCfg)
if pSection.Endpoint != nil {
cfg.PeerEndpoints = append(cfg.PeerEndpoints, *pSection.Endpoint)
} else {
cfg.PeerEndpoints = append(cfg.PeerEndpoints, "")
}
}
return cfg, nil
}
func (p *peerConfig) Config() (*wgtypes.PeerConfig, error) {
var err error
cfg := &wgtypes.PeerConfig{
AllowedIPs: []net.IPNet{},
}
cfg.PublicKey, err = wgtypes.ParseKey(p.PublicKey)
if err != nil {
return nil, fmt.Errorf("failed to parse key: %w", err)
}
if p.PresharedKey != nil {
psk, err := wgtypes.ParseKey(*p.PresharedKey)
if err != nil {
return nil, fmt.Errorf("failed to parse key: %w", err)
}
cfg.PresharedKey = &psk
}
if p.PersistentKeepalive != nil {
ka := time.Duration(*p.PersistentKeepalive) * time.Second
cfg.PersistentKeepaliveInterval = &ka
}
if p.Endpoint != nil {
addr, err := net.ResolveUDPAddr("udp", *p.Endpoint)
if err != nil {
return nil, fmt.Errorf("failed to resolve endpoint %s: %w", *p.Endpoint, err)
}
cfg.Endpoint = addr
}
if p.AllowedIPs != nil {
if cfg.AllowedIPs, err = parseCIDRs(p.AllowedIPs, false); err != nil {
return nil, fmt.Errorf("failed to parse allowed ips: %w", err)
}
cfg.ReplaceAllowedIPs = true
}
return cfg, nil
}