mirror of
https://github.com/fosrl/olm.git
synced 2026-05-13 19:59:54 +00:00
When the tunnel is forced close an integration may want to manually call cleanup function to fix stale issues without having the knowledge of which configuration to cleanup
245 lines
6.7 KiB
Go
245 lines
6.7 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
|
|
}
|
|
|
|
// CleanupStaleFileDNS removes any stale DNS configuration left by the file-based
|
|
// configurator from a previous unclean shutdown. This is a static function that can be
|
|
// called without creating a configurator instance, useful for cleanup before network operations.
|
|
func CleanupStaleFileDNS() error {
|
|
// Check if backup file exists from a previous session
|
|
if _, err := os.Stat(resolvConfBackupPath); os.IsNotExist(err) {
|
|
// 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
|
|
}
|