mirror of
https://codeberg.org/cunicu/cunicu.git
synced 2025-10-05 16:57:01 +08:00
325 lines
7.1 KiB
Go
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
|
|
}
|