mirror of
https://github.com/fosrl/olm.git
synced 2026-02-07 21:46:40 +00:00
221 lines
5.8 KiB
Go
221 lines
5.8 KiB
Go
//go:build (linux && !android) || freebsd
|
|
|
|
package dns
|
|
|
|
import (
|
|
"fmt"
|
|
"net/netip"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
resolvConfPath = "/etc/resolv.conf"
|
|
resolvConfBackupPath = "/etc/resolv.conf.olm.backup"
|
|
resolvConfHeader = "# Generated by Olm DNS Manager\n# Original file backed up to " + resolvConfBackupPath + "\n\n"
|
|
)
|
|
|
|
// FileDNSConfigurator manages DNS settings by directly modifying /etc/resolv.conf
|
|
type FileDNSConfigurator struct {
|
|
originalState *DNSState
|
|
}
|
|
|
|
// NewFileDNSConfigurator creates a new file-based DNS configurator
|
|
func NewFileDNSConfigurator() (*FileDNSConfigurator, error) {
|
|
f := &FileDNSConfigurator{}
|
|
if err := f.CleanupUncleanShutdown(); err != nil {
|
|
return nil, fmt.Errorf("cleanup unclean shutdown: %w", err)
|
|
}
|
|
return f, nil
|
|
}
|
|
|
|
// Name returns the configurator name
|
|
func (f *FileDNSConfigurator) Name() string {
|
|
return "file-resolv.conf"
|
|
}
|
|
|
|
// SetDNS sets the DNS servers and returns the original servers
|
|
func (f *FileDNSConfigurator) SetDNS(servers []netip.Addr) ([]netip.Addr, error) {
|
|
// Get current DNS settings before overriding
|
|
originalServers, err := f.GetCurrentDNS()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("get current DNS: %w", err)
|
|
}
|
|
|
|
// Backup original resolv.conf if not already backed up
|
|
if !f.isBackupExists() {
|
|
if err := f.backupResolvConf(); err != nil {
|
|
return nil, fmt.Errorf("backup resolv.conf: %w", err)
|
|
}
|
|
}
|
|
|
|
// Store original state
|
|
f.originalState = &DNSState{
|
|
OriginalServers: originalServers,
|
|
ConfiguratorName: f.Name(),
|
|
}
|
|
|
|
// Write new resolv.conf
|
|
if err := f.writeResolvConf(servers); err != nil {
|
|
return nil, fmt.Errorf("write resolv.conf: %w", err)
|
|
}
|
|
|
|
return originalServers, nil
|
|
}
|
|
|
|
// RestoreDNS restores the original DNS configuration
|
|
func (f *FileDNSConfigurator) RestoreDNS() error {
|
|
if !f.isBackupExists() {
|
|
return fmt.Errorf("no backup file exists")
|
|
}
|
|
|
|
// Copy backup back to original location
|
|
if err := copyFile(resolvConfBackupPath, resolvConfPath); err != nil {
|
|
return fmt.Errorf("restore from backup: %w", err)
|
|
}
|
|
|
|
// Remove backup file
|
|
if err := os.Remove(resolvConfBackupPath); err != nil {
|
|
return fmt.Errorf("remove backup file: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CleanupUncleanShutdown removes any DNS configuration left over from a previous crash
|
|
// For the file-based configurator, we check if a backup file exists (indicating a crash
|
|
// happened while DNS was configured) and restore from it if so.
|
|
func (f *FileDNSConfigurator) CleanupUncleanShutdown() error {
|
|
// Check if backup file exists from a previous session
|
|
if !f.isBackupExists() {
|
|
// No backup file, nothing to clean up
|
|
return nil
|
|
}
|
|
|
|
// A backup exists, which means we crashed while DNS was configured
|
|
// Restore the original resolv.conf
|
|
if err := copyFile(resolvConfBackupPath, resolvConfPath); err != nil {
|
|
return fmt.Errorf("restore from backup during cleanup: %w", err)
|
|
}
|
|
|
|
// Remove backup file
|
|
if err := os.Remove(resolvConfBackupPath); err != nil {
|
|
return fmt.Errorf("remove backup file during cleanup: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetCurrentDNS returns the currently configured DNS servers
|
|
func (f *FileDNSConfigurator) GetCurrentDNS() ([]netip.Addr, error) {
|
|
content, err := os.ReadFile(resolvConfPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read resolv.conf: %w", err)
|
|
}
|
|
|
|
return f.parseNameservers(string(content)), nil
|
|
}
|
|
|
|
// backupResolvConf creates a backup of the current resolv.conf
|
|
func (f *FileDNSConfigurator) backupResolvConf() error {
|
|
// Get file info for permissions
|
|
info, err := os.Stat(resolvConfPath)
|
|
if err != nil {
|
|
return fmt.Errorf("stat resolv.conf: %w", err)
|
|
}
|
|
|
|
if err := copyFile(resolvConfPath, resolvConfBackupPath); err != nil {
|
|
return fmt.Errorf("copy file: %w", err)
|
|
}
|
|
|
|
// Preserve permissions
|
|
if err := os.Chmod(resolvConfBackupPath, info.Mode()); err != nil {
|
|
return fmt.Errorf("chmod backup: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// writeResolvConf writes a new resolv.conf with the specified DNS servers
|
|
func (f *FileDNSConfigurator) writeResolvConf(servers []netip.Addr) error {
|
|
if len(servers) == 0 {
|
|
return fmt.Errorf("no DNS servers provided")
|
|
}
|
|
|
|
// Get file info for permissions
|
|
info, err := os.Stat(resolvConfPath)
|
|
if err != nil {
|
|
return fmt.Errorf("stat resolv.conf: %w", err)
|
|
}
|
|
|
|
var content strings.Builder
|
|
content.WriteString(resolvConfHeader)
|
|
|
|
// Write nameservers
|
|
for _, server := range servers {
|
|
content.WriteString("nameserver ")
|
|
content.WriteString(server.String())
|
|
content.WriteString("\n")
|
|
}
|
|
|
|
// Write the file
|
|
if err := os.WriteFile(resolvConfPath, []byte(content.String()), info.Mode()); err != nil {
|
|
return fmt.Errorf("write resolv.conf: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// isBackupExists checks if a backup file exists
|
|
func (f *FileDNSConfigurator) isBackupExists() bool {
|
|
_, err := os.Stat(resolvConfBackupPath)
|
|
return err == nil
|
|
}
|
|
|
|
// parseNameservers extracts nameserver entries from resolv.conf content
|
|
func (f *FileDNSConfigurator) parseNameservers(content string) []netip.Addr {
|
|
var servers []netip.Addr
|
|
|
|
lines := strings.Split(content, "\n")
|
|
for _, line := range lines {
|
|
line = strings.TrimSpace(line)
|
|
|
|
// Skip comments and empty lines
|
|
if line == "" || strings.HasPrefix(line, "#") {
|
|
continue
|
|
}
|
|
|
|
// Look for nameserver lines
|
|
if strings.HasPrefix(line, "nameserver") {
|
|
fields := strings.Fields(line)
|
|
if len(fields) >= 2 {
|
|
if addr, err := netip.ParseAddr(fields[1]); err == nil {
|
|
servers = append(servers, addr)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return servers
|
|
}
|
|
|
|
// copyFile copies a file from src to dst
|
|
func copyFile(src, dst string) error {
|
|
content, err := os.ReadFile(src)
|
|
if err != nil {
|
|
return fmt.Errorf("read source: %w", err)
|
|
}
|
|
|
|
// Get source file permissions
|
|
info, err := os.Stat(src)
|
|
if err != nil {
|
|
return fmt.Errorf("stat source: %w", err)
|
|
}
|
|
|
|
if err := os.WriteFile(dst, content, info.Mode()); err != nil {
|
|
return fmt.Errorf("write destination: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|