dyndns-client/pkg/config/config.go

159 lines
3.4 KiB
Go

package config
import (
_ "embed"
"encoding/json"
"fmt"
"net"
"os"
"path/filepath"
"strings"
"git.cryptic.systems/volker.raschek/dyndns-client/pkg/types"
log "github.com/sirupsen/logrus"
)
//go:embed config.json
var defaultConfig string
// GetDefaultConfiguration returns a default configuration
func GetDefaultConfiguration() (*types.Config, error) {
cnf := new(types.Config)
jsonDecoder := json.NewDecoder(strings.NewReader(defaultConfig))
err := jsonDecoder.Decode(cnf)
if err != nil {
return nil, fmt.Errorf("failed to decode default config: %w", err)
}
defaultInterface, err := getDefaultInterfaceByIP()
if err != nil {
return nil, err
}
cnf.Ifaces = []string{defaultInterface.Name}
return cnf, nil
}
// Read config from a file
func Read(cnfFile string) (*types.Config, error) {
// Load burned in configuration if config not available
if _, err := os.Stat(cnfFile); os.IsNotExist(err) {
// #nosec G301
if err := os.MkdirAll(filepath.Dir(cnfFile), 0755); err != nil {
return nil, fmt.Errorf("failed to create directory: %w", err)
}
cnf, err := GetDefaultConfiguration()
if err != nil {
return nil, err
}
err = cnf.Validate()
if err != nil {
return nil, err
}
log.Infof("use embedded configuration")
return cnf, nil
}
// #nosec G304
f, err := os.Open(cnfFile)
if err != nil {
return nil, fmt.Errorf("failed to open file: %w", err)
}
defer func() { _ = f.Close() }()
cnf := new(types.Config)
jsonDecoder := json.NewDecoder(f)
err = jsonDecoder.Decode(cnf)
if err != nil {
return nil, fmt.Errorf("failed to decode json: %w", err)
}
if len(cnf.Ifaces) <= 0 {
defaultInterface, err := getDefaultInterfaceByIP()
if err != nil {
return nil, err
}
cnf.Ifaces = []string{defaultInterface.Name}
}
for _, iface := range cnf.Ifaces {
if _, err := net.InterfaceByName(iface); err != nil {
return nil, fmt.Errorf("unknown interface: %v", iface)
}
}
err = cnf.Validate()
if err != nil {
return nil, err
}
log.Infof("use configuration from file %v", cnfFile)
return cnf, nil
}
// Write config into a file
func Write(cnf *types.Config, cnfFile string) error {
if _, err := os.Stat(filepath.Dir(cnfFile)); os.IsNotExist(err) {
// #nosec G301
err := os.MkdirAll(filepath.Dir(cnfFile), 0755)
if err != nil {
return err
}
}
// #nosec G304
f, err := os.Create(cnfFile)
if err != nil {
return fmt.Errorf("failed to create file %v: %v", cnfFile, err)
}
defer func() { _ = f.Close() }()
jsonEncoder := json.NewEncoder(f)
jsonEncoder.SetIndent("", " ")
err = jsonEncoder.Encode(cnf)
if err != nil {
return fmt.Errorf("failed to encode json: %w", err)
}
return nil
}
func getDefaultInterfaceByIP() (*net.Interface, error) {
ifaces, err := net.Interfaces()
if err != nil {
return nil, fmt.Errorf("failed to fet network interfaces from kernel: %w", err)
}
defaultIP := getOutboundIP()
for _, iface := range ifaces {
addrs, err := iface.Addrs()
if err != nil {
return nil, fmt.Errorf("failed to list ip addresses for interface %v: %w", iface.Name, err)
}
for _, addr := range addrs {
addrIP := strings.Split(addr.String(), "/")[0]
if addrIP == defaultIP.String() {
return &iface, nil
}
}
}
return nil, fmt.Errorf("no interface found fo ip address %v", defaultIP)
}
func getOutboundIP() net.IP {
conn, err := net.Dial("udp", "8.8.8.8:80")
if err != nil {
log.Fatal(err)
}
defer conn.Close()
localAddr := conn.LocalAddr().(*net.UDPAddr)
return localAddr.IP
}