mirror of https://github.com/jetkvm/kvm.git
refactor & fix hostname
This commit is contained in:
parent
6ff4f37a36
commit
579345e5b4
|
|
@ -1,74 +1,12 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/guregu/null/v6"
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
// IPAddress represents a network interface address
|
||||
type IPAddress struct {
|
||||
Family int
|
||||
Address net.IPNet
|
||||
Gateway net.IP
|
||||
MTU int
|
||||
Secondary bool
|
||||
Permanent bool
|
||||
}
|
||||
|
||||
func (a *IPAddress) String() string {
|
||||
return a.Address.String()
|
||||
}
|
||||
|
||||
func (a *IPAddress) Compare(n netlink.Addr) bool {
|
||||
if !a.Address.IP.Equal(n.IP) {
|
||||
return false
|
||||
}
|
||||
if slices.Compare(a.Address.Mask, n.IPNet.Mask) != 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (a *IPAddress) NetlinkAddr() netlink.Addr {
|
||||
return netlink.Addr{
|
||||
IPNet: &a.Address,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *IPAddress) DefaultRoute(linkIndex int) netlink.Route {
|
||||
return netlink.Route{
|
||||
Dst: nil,
|
||||
Gw: a.Gateway,
|
||||
LinkIndex: linkIndex,
|
||||
}
|
||||
}
|
||||
|
||||
// ParsedIPConfig represents the parsed IP configuration
|
||||
type ParsedIPConfig struct {
|
||||
Addresses []IPAddress
|
||||
Nameservers []net.IP
|
||||
SearchList []string
|
||||
Domain string
|
||||
MTU int
|
||||
Interface string
|
||||
}
|
||||
|
||||
// IPv6Address represents an IPv6 address with lifetime information
|
||||
type IPv6Address struct {
|
||||
Address net.IP `json:"address"`
|
||||
Prefix net.IPNet `json:"prefix"`
|
||||
ValidLifetime *time.Time `json:"valid_lifetime"`
|
||||
PreferredLifetime *time.Time `json:"preferred_lifetime"`
|
||||
Flags int `json:"flags"`
|
||||
Scope int `json:"scope"`
|
||||
}
|
||||
|
||||
// IPv4StaticConfig represents static IPv4 configuration
|
||||
type IPv4StaticConfig struct {
|
||||
Address null.String `json:"address,omitempty" validate_type:"ipv4" required:"true"`
|
||||
|
|
@ -84,6 +22,12 @@ type IPv6StaticConfig struct {
|
|||
DNS []string `json:"dns,omitempty" validate_type:"ipv6" required:"true"`
|
||||
}
|
||||
|
||||
// MDNSListenOptions represents MDNS listening options
|
||||
type MDNSListenOptions struct {
|
||||
IPv4 bool
|
||||
IPv6 bool
|
||||
}
|
||||
|
||||
// NetworkConfig represents the complete network configuration for an interface
|
||||
type NetworkConfig struct {
|
||||
DHCPClient null.String `json:"dhcp_client,omitempty" one_of:"jetdhcpc,udhcpc" default:"jetdhcpc"`
|
||||
|
|
@ -130,44 +74,18 @@ func (c *NetworkConfig) GetMDNSMode() *MDNSListenOptions {
|
|||
return listenOptions
|
||||
}
|
||||
|
||||
// MDNSListenOptions represents MDNS listening options
|
||||
type MDNSListenOptions struct {
|
||||
IPv4 bool
|
||||
IPv6 bool
|
||||
}
|
||||
|
||||
// GetTransportProxyFunc returns a function for HTTP proxy configuration
|
||||
func (c *NetworkConfig) GetTransportProxyFunc() func(*http.Request) (*url.URL, error) {
|
||||
return func(*http.Request) (*url.URL, error) {
|
||||
if c.HTTPProxy.String == "" {
|
||||
return nil, nil
|
||||
} else {
|
||||
proxyUrl, _ := url.Parse(c.HTTPProxy.String)
|
||||
return proxyUrl, nil
|
||||
proxyURL, _ := url.Parse(c.HTTPProxy.String)
|
||||
return proxyURL, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// InterfaceState represents the current state of a network interface
|
||||
type InterfaceState struct {
|
||||
InterfaceName string `json:"interface_name"`
|
||||
MACAddress string `json:"mac_address"`
|
||||
Up bool `json:"up"`
|
||||
Online bool `json:"online"`
|
||||
IPv4Ready bool `json:"ipv4_ready"`
|
||||
IPv6Ready bool `json:"ipv6_ready"`
|
||||
IPv4Address string `json:"ipv4_address,omitempty"`
|
||||
IPv6Address string `json:"ipv6_address,omitempty"`
|
||||
IPv6LinkLocal string `json:"ipv6_link_local,omitempty"`
|
||||
IPv6Gateway string `json:"ipv6_gateway,omitempty"`
|
||||
IPv4Addresses []string `json:"ipv4_addresses,omitempty"`
|
||||
IPv6Addresses []IPv6Address `json:"ipv6_addresses,omitempty"`
|
||||
NTPServers []net.IP `json:"ntp_servers,omitempty"`
|
||||
DHCPLease4 *DHCPLease `json:"dhcp_lease,omitempty"`
|
||||
DHCPLease6 *DHCPLease `json:"dhcp_lease6,omitempty"`
|
||||
LastUpdated time.Time `json:"last_updated"`
|
||||
}
|
||||
|
||||
// NetworkConfig interface for backward compatibility
|
||||
type NetworkConfigInterface interface {
|
||||
InterfaceName() string
|
||||
|
|
@ -1,27 +1,30 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// RpcIPv6Address is the RPC representation of an IPv6 address
|
||||
type RpcIPv6Address struct {
|
||||
Address string `json:"address"`
|
||||
Prefix string `json:"prefix"`
|
||||
ValidLifetime *time.Time `json:"valid_lifetime"`
|
||||
PreferredLifetime *time.Time `json:"preferred_lifetime"`
|
||||
Scope int `json:"scope"`
|
||||
Flags int `json:"flags"`
|
||||
FlagSecondary bool `json:"flag_secondary"`
|
||||
FlagPermanent bool `json:"flag_permanent"`
|
||||
FlagTemporary bool `json:"flag_temporary"`
|
||||
FlagStablePrivacy bool `json:"flag_stable_privacy"`
|
||||
FlagDeprecated bool `json:"flag_deprecated"`
|
||||
FlagOptimistic bool `json:"flag_optimistic"`
|
||||
FlagDADFailed bool `json:"flag_dad_failed"`
|
||||
FlagTentative bool `json:"flag_tentative"`
|
||||
// InterfaceState represents the current state of a network interface
|
||||
type InterfaceState struct {
|
||||
InterfaceName string `json:"interface_name"`
|
||||
MACAddress string `json:"mac_address"`
|
||||
Up bool `json:"up"`
|
||||
Online bool `json:"online"`
|
||||
IPv4Ready bool `json:"ipv4_ready"`
|
||||
IPv6Ready bool `json:"ipv6_ready"`
|
||||
IPv4Address string `json:"ipv4_address,omitempty"`
|
||||
IPv6Address string `json:"ipv6_address,omitempty"`
|
||||
IPv6LinkLocal string `json:"ipv6_link_local,omitempty"`
|
||||
IPv6Gateway string `json:"ipv6_gateway,omitempty"`
|
||||
IPv4Addresses []string `json:"ipv4_addresses,omitempty"`
|
||||
IPv6Addresses []IPv6Address `json:"ipv6_addresses,omitempty"`
|
||||
NTPServers []net.IP `json:"ntp_servers,omitempty"`
|
||||
DHCPLease4 *DHCPLease `json:"dhcp_lease,omitempty"`
|
||||
DHCPLease6 *DHCPLease `json:"dhcp_lease6,omitempty"`
|
||||
LastUpdated time.Time `json:"last_updated"`
|
||||
}
|
||||
|
||||
// RpcInterfaceState is the RPC representation of an interface state
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"net"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
// IPAddress represents a network interface address
|
||||
type IPAddress struct {
|
||||
Family int
|
||||
Address net.IPNet
|
||||
Gateway net.IP
|
||||
MTU int
|
||||
Secondary bool
|
||||
Permanent bool
|
||||
}
|
||||
|
||||
func (a *IPAddress) String() string {
|
||||
return a.Address.String()
|
||||
}
|
||||
|
||||
func (a *IPAddress) Compare(n netlink.Addr) bool {
|
||||
if !a.Address.IP.Equal(n.IP) {
|
||||
return false
|
||||
}
|
||||
if slices.Compare(a.Address.Mask, n.IPNet.Mask) != 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (a *IPAddress) NetlinkAddr() netlink.Addr {
|
||||
return netlink.Addr{
|
||||
IPNet: &a.Address,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *IPAddress) DefaultRoute(linkIndex int) netlink.Route {
|
||||
return netlink.Route{
|
||||
Dst: nil,
|
||||
Gw: a.Gateway,
|
||||
LinkIndex: linkIndex,
|
||||
}
|
||||
}
|
||||
|
||||
// ParsedIPConfig represents the parsed IP configuration
|
||||
type ParsedIPConfig struct {
|
||||
Addresses []IPAddress
|
||||
Nameservers []net.IP
|
||||
SearchList []string
|
||||
Domain string
|
||||
MTU int
|
||||
Interface string
|
||||
}
|
||||
|
||||
// IPv6Address represents an IPv6 address with lifetime information
|
||||
type IPv6Address struct {
|
||||
Address net.IP `json:"address"`
|
||||
Prefix net.IPNet `json:"prefix"`
|
||||
ValidLifetime *time.Time `json:"valid_lifetime"`
|
||||
PreferredLifetime *time.Time `json:"preferred_lifetime"`
|
||||
Flags int `json:"flags"`
|
||||
Scope int `json:"scope"`
|
||||
}
|
||||
|
||||
// RpcIPv6Address is the RPC representation of an IPv6 address
|
||||
type RpcIPv6Address struct {
|
||||
Address string `json:"address"`
|
||||
Prefix string `json:"prefix"`
|
||||
ValidLifetime *time.Time `json:"valid_lifetime"`
|
||||
PreferredLifetime *time.Time `json:"preferred_lifetime"`
|
||||
Scope int `json:"scope"`
|
||||
Flags int `json:"flags"`
|
||||
FlagSecondary bool `json:"flag_secondary"`
|
||||
FlagPermanent bool `json:"flag_permanent"`
|
||||
FlagTemporary bool `json:"flag_temporary"`
|
||||
FlagStablePrivacy bool `json:"flag_stable_privacy"`
|
||||
FlagDeprecated bool `json:"flag_deprecated"`
|
||||
FlagOptimistic bool `json:"flag_optimistic"`
|
||||
FlagDADFailed bool `json:"flag_dad_failed"`
|
||||
FlagTentative bool `json:"flag_tentative"`
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package types
|
||||
|
||||
import "net"
|
||||
|
||||
// InterfaceResolvConf represents the DNS configuration for a network interface
|
||||
type InterfaceResolvConf struct {
|
||||
NameServers []net.IP `json:"nameservers"`
|
||||
SearchList []string `json:"search_list"`
|
||||
Domain string `json:"domain,omitempty"` // TODO: remove this once we have a better way to handle the domain
|
||||
Source string `json:"source,omitempty"`
|
||||
}
|
||||
|
||||
// InterfaceResolvConfMap ..
|
||||
type InterfaceResolvConfMap map[string]InterfaceResolvConf
|
||||
|
||||
// ResolvConf represents the DNS configuration for the system
|
||||
type ResolvConf struct {
|
||||
ConfigIPv4 InterfaceResolvConfMap `json:"config_ipv4"`
|
||||
ConfigIPv6 InterfaceResolvConfMap `json:"config_ipv6"`
|
||||
Domain string `json:"domain"`
|
||||
HostName string `json:"host_name"`
|
||||
}
|
||||
36
network.go
36
network.go
|
|
@ -42,8 +42,8 @@ func restartMdns() {
|
|||
IPv6: config.NetworkConfig.MDNSMode.String != "disabled",
|
||||
})
|
||||
_ = mDNS.SetLocalNames([]string{
|
||||
networkManager.GetHostname(),
|
||||
networkManager.GetFQDN(),
|
||||
networkManager.Hostname(),
|
||||
networkManager.FQDN(),
|
||||
}, true)
|
||||
}
|
||||
|
||||
|
|
@ -54,7 +54,12 @@ func triggerTimeSyncOnNetworkStateChange() {
|
|||
|
||||
// set the NTP servers from the network manager
|
||||
if networkManager != nil {
|
||||
timeSync.SetDhcpNtpAddresses(networkManager.NTPServerStrings())
|
||||
ntpServers := make([]string, len(networkManager.NTPServers()))
|
||||
for i, server := range networkManager.NTPServers() {
|
||||
ntpServers[i] = server.String()
|
||||
}
|
||||
networkLogger.Info().Strs("ntpServers", ntpServers).Msg("setting NTP servers from network manager")
|
||||
timeSync.SetDhcpNtpAddresses(ntpServers)
|
||||
}
|
||||
|
||||
// sync time
|
||||
|
|
@ -103,15 +108,32 @@ func initNetwork() error {
|
|||
// validate the config, if it's invalid, revert to the default config and save the backup
|
||||
validateNetworkConfig()
|
||||
|
||||
networkManager = nmlite.NewNetworkManager(context.Background(), networkLogger)
|
||||
networkManager.SetOnInterfaceStateChange(networkStateChanged)
|
||||
if err := networkManager.AddInterface(NetIfName, config.NetworkConfig); err != nil {
|
||||
nc := config.NetworkConfig
|
||||
|
||||
nm := nmlite.NewNetworkManager(context.Background(), networkLogger)
|
||||
_ = setHostname(nm, nc.Hostname.String, nc.Domain.String)
|
||||
nm.SetOnInterfaceStateChange(networkStateChanged)
|
||||
if err := nm.AddInterface(NetIfName, nc); err != nil {
|
||||
return fmt.Errorf("failed to add interface: %w", err)
|
||||
}
|
||||
|
||||
networkManager = nm
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setHostname(nm *nmlite.NetworkManager, hostname, domain string) error {
|
||||
if nm == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if hostname == "" {
|
||||
hostname = GetDefaultHostname()
|
||||
}
|
||||
|
||||
return nm.SetHostname(hostname, domain)
|
||||
}
|
||||
|
||||
func rpcGetNetworkState() *types.RpcInterfaceState {
|
||||
state, _ := networkManager.GetInterfaceState(NetIfName)
|
||||
return state.ToRpcInterfaceState()
|
||||
|
|
@ -131,6 +153,8 @@ func rpcSetNetworkSettings(settings RpcNetworkSettings) (*RpcNetworkSettings, er
|
|||
|
||||
l.Debug().Msg("setting new config")
|
||||
|
||||
_ = setHostname(networkManager, netConfig.Hostname.String, netConfig.Domain.String)
|
||||
|
||||
s := networkManager.SetInterfaceConfig(NetIfName, netConfig)
|
||||
if s != nil {
|
||||
return nil, s
|
||||
|
|
|
|||
|
|
@ -81,8 +81,9 @@ func (dc *DHCPClient) initClient() (types.DHCPClient, error) {
|
|||
|
||||
func (dc *DHCPClient) initJetDHCPC() (types.DHCPClient, error) {
|
||||
return jetdhcpc.NewClient(dc.ctx, []string{dc.ifaceName}, &jetdhcpc.Config{
|
||||
IPv4: dc.ipv4Enabled,
|
||||
IPv6: dc.ipv6Enabled,
|
||||
IPv4: dc.ipv4Enabled,
|
||||
IPv6: dc.ipv6Enabled,
|
||||
V4ClientIdentifier: true,
|
||||
OnLease4Change: func(lease *types.DHCPLease) {
|
||||
dc.handleLeaseChange(lease, false)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -7,9 +7,6 @@ import (
|
|||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/jetkvm/kvm/internal/sync"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"golang.org/x/net/idna"
|
||||
)
|
||||
|
||||
|
|
@ -18,38 +15,89 @@ const (
|
|||
hostsPath = "/etc/hosts"
|
||||
)
|
||||
|
||||
// HostnameManager manages system hostname and /etc/hosts
|
||||
type HostnameManager struct {
|
||||
logger *zerolog.Logger
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// NewHostnameManager creates a new hostname manager
|
||||
func NewHostnameManager(logger *zerolog.Logger) *HostnameManager {
|
||||
if logger == nil {
|
||||
// Create a no-op logger if none provided
|
||||
logger = &zerolog.Logger{}
|
||||
}
|
||||
|
||||
return &HostnameManager{
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// SetHostname sets the system hostname and updates /etc/hosts
|
||||
func (hm *HostnameManager) SetHostname(hostname, fqdn string) error {
|
||||
hm.mu.Lock()
|
||||
defer hm.mu.Unlock()
|
||||
|
||||
func (hm *ResolvConfManager) SetHostname(hostname, domain string) error {
|
||||
hostname = ToValidHostname(strings.TrimSpace(hostname))
|
||||
fqdn = ToValidHostname(strings.TrimSpace(fqdn))
|
||||
domain = ToValidHostname(strings.TrimSpace(domain))
|
||||
|
||||
if hostname == "" {
|
||||
return fmt.Errorf("invalid hostname: %s", hostname)
|
||||
}
|
||||
|
||||
if fqdn == "" {
|
||||
fqdn = hostname
|
||||
hm.hostname = hostname
|
||||
hm.domain = domain
|
||||
|
||||
return hm.reconcileHostname()
|
||||
}
|
||||
|
||||
func (hm *ResolvConfManager) Domain() string {
|
||||
hm.mu.Lock()
|
||||
defer hm.mu.Unlock()
|
||||
return hm.getDomain()
|
||||
}
|
||||
|
||||
func (hm *ResolvConfManager) Hostname() string {
|
||||
hm.mu.Lock()
|
||||
defer hm.mu.Unlock()
|
||||
return hm.getHostname()
|
||||
}
|
||||
|
||||
func (hm *ResolvConfManager) FQDN() string {
|
||||
hm.mu.Lock()
|
||||
defer hm.mu.Unlock()
|
||||
return hm.getFQDN()
|
||||
}
|
||||
|
||||
func (hm *ResolvConfManager) getFQDN() string {
|
||||
hostname := hm.getHostname()
|
||||
domain := hm.getDomain()
|
||||
|
||||
if domain == "" {
|
||||
return hostname
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s.%s", hostname, domain)
|
||||
}
|
||||
|
||||
func (hm *ResolvConfManager) getHostname() string {
|
||||
if hm.hostname != "" {
|
||||
return hm.hostname
|
||||
}
|
||||
return "jetkvm"
|
||||
}
|
||||
|
||||
func (hm *ResolvConfManager) getDomain() string {
|
||||
if hm.domain != "" {
|
||||
return hm.domain
|
||||
}
|
||||
|
||||
for _, iface := range hm.conf.ConfigIPv4 {
|
||||
if iface.Domain != "" {
|
||||
return iface.Domain
|
||||
}
|
||||
}
|
||||
|
||||
for _, iface := range hm.conf.ConfigIPv6 {
|
||||
if iface.Domain != "" {
|
||||
return iface.Domain
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (hm *ResolvConfManager) reconcileHostname() error {
|
||||
hm.mu.Lock()
|
||||
domain := hm.getDomain()
|
||||
hostname := hm.hostname
|
||||
if hostname == "" {
|
||||
hostname = "jetkvm"
|
||||
}
|
||||
hm.mu.Unlock()
|
||||
|
||||
fqdn := hostname
|
||||
if fqdn != "" {
|
||||
fqdn = fmt.Sprintf("%s.%s", hostname, domain)
|
||||
}
|
||||
|
||||
hm.logger.Info().
|
||||
|
|
@ -81,12 +129,12 @@ func (hm *HostnameManager) SetHostname(hostname, fqdn string) error {
|
|||
}
|
||||
|
||||
// GetCurrentHostname returns the current system hostname
|
||||
func (hm *HostnameManager) GetCurrentHostname() (string, error) {
|
||||
func (hm *ResolvConfManager) GetCurrentHostname() (string, error) {
|
||||
return os.Hostname()
|
||||
}
|
||||
|
||||
// GetCurrentFQDN returns the current FQDN
|
||||
func (hm *HostnameManager) GetCurrentFQDN() (string, error) {
|
||||
func (hm *ResolvConfManager) GetCurrentFQDN() (string, error) {
|
||||
hostname, err := hm.GetCurrentHostname()
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
|
@ -97,7 +145,7 @@ func (hm *HostnameManager) GetCurrentFQDN() (string, error) {
|
|||
}
|
||||
|
||||
// updateEtcHostname updates the /etc/hostname file
|
||||
func (hm *HostnameManager) updateEtcHostname(hostname string) error {
|
||||
func (hm *ResolvConfManager) updateEtcHostname(hostname string) error {
|
||||
if err := os.WriteFile(hostnamePath, []byte(hostname), 0644); err != nil {
|
||||
return fmt.Errorf("failed to write %s: %w", hostnamePath, err)
|
||||
}
|
||||
|
|
@ -107,7 +155,7 @@ func (hm *HostnameManager) updateEtcHostname(hostname string) error {
|
|||
}
|
||||
|
||||
// updateEtcHosts updates the /etc/hosts file
|
||||
func (hm *HostnameManager) updateEtcHosts(hostname, fqdn string) error {
|
||||
func (hm *ResolvConfManager) updateEtcHosts(hostname, fqdn string) error {
|
||||
// Open /etc/hosts for reading and writing
|
||||
hostsFile, err := os.OpenFile(hostsPath, os.O_RDWR|os.O_SYNC, os.ModeExclusive)
|
||||
if err != nil {
|
||||
|
|
@ -166,7 +214,7 @@ func (hm *HostnameManager) updateEtcHosts(hostname, fqdn string) error {
|
|||
}
|
||||
|
||||
// setSystemHostname sets the system hostname using the hostname command
|
||||
func (hm *HostnameManager) setSystemHostname(hostname string) error {
|
||||
func (hm *ResolvConfManager) setSystemHostname(hostname string) error {
|
||||
cmd := exec.Command("hostname", "-F", hostnamePath)
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to run hostname command: %w", err)
|
||||
|
|
@ -177,7 +225,7 @@ func (hm *HostnameManager) setSystemHostname(hostname string) error {
|
|||
}
|
||||
|
||||
// getFQDNFromHosts tries to get the FQDN from /etc/hosts
|
||||
func (hm *HostnameManager) getFQDNFromHosts(hostname string) (string, error) {
|
||||
func (hm *ResolvConfManager) getFQDNFromHosts(hostname string) (string, error) {
|
||||
content, err := os.ReadFile(hostsPath)
|
||||
if err != nil {
|
||||
return hostname, nil // Return hostname as fallback
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ import (
|
|||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
type ResolvConfChangeCallback func(family int, resolvConf *types.InterfaceResolvConf) error
|
||||
|
||||
// InterfaceManager manages a single network interface
|
||||
type InterfaceManager struct {
|
||||
ctx context.Context
|
||||
|
|
@ -32,13 +34,12 @@ type InterfaceManager struct {
|
|||
// Network components
|
||||
staticConfig *StaticConfigManager
|
||||
dhcpClient *DHCPClient
|
||||
resolvConf *ResolvConfManager
|
||||
hostname *HostnameManager
|
||||
|
||||
// Callbacks
|
||||
onStateChange func(state types.InterfaceState)
|
||||
onConfigChange func(config *types.NetworkConfig)
|
||||
onDHCPLeaseChange func(lease *types.DHCPLease)
|
||||
onStateChange func(state types.InterfaceState)
|
||||
onConfigChange func(config *types.NetworkConfig)
|
||||
onDHCPLeaseChange func(lease *types.DHCPLease)
|
||||
onResolvConfChange ResolvConfChangeCallback
|
||||
|
||||
// Control
|
||||
stopCh chan struct{}
|
||||
|
|
@ -87,9 +88,6 @@ func NewInterfaceManager(ctx context.Context, ifaceName string, config *types.Ne
|
|||
return nil, fmt.Errorf("failed to create DHCP client: %w", err)
|
||||
}
|
||||
|
||||
im.resolvConf = NewResolvConfManager(&scopedLogger)
|
||||
im.hostname = NewHostnameManager(&scopedLogger)
|
||||
|
||||
// Set up DHCP client callbacks
|
||||
im.dhcpClient.SetOnLeaseChange(func(lease *types.DHCPLease) {
|
||||
if err := im.applyDHCPLease(lease); err != nil {
|
||||
|
|
@ -248,6 +246,9 @@ func (im *InterfaceManager) GetIPv6Addresses() []string {
|
|||
|
||||
// GetMACAddress returns the MAC address of the interface
|
||||
func (im *InterfaceManager) GetMACAddress() string {
|
||||
im.stateMu.RLock()
|
||||
defer im.stateMu.RUnlock()
|
||||
|
||||
return im.state.MACAddress
|
||||
}
|
||||
|
||||
|
|
@ -271,6 +272,21 @@ func (im *InterfaceManager) NTPServers() []net.IP {
|
|||
return im.state.NTPServers
|
||||
}
|
||||
|
||||
func (im *InterfaceManager) Domain() string {
|
||||
im.stateMu.RLock()
|
||||
defer im.stateMu.RUnlock()
|
||||
|
||||
if im.state.DHCPLease4 != nil {
|
||||
return im.state.DHCPLease4.Domain
|
||||
}
|
||||
|
||||
if im.state.DHCPLease6 != nil {
|
||||
return im.state.DHCPLease6.Domain
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetConfig returns the current interface configuration
|
||||
func (im *InterfaceManager) GetConfig() *types.NetworkConfig {
|
||||
// Return a copy to avoid race conditions
|
||||
|
|
@ -335,6 +351,11 @@ func (im *InterfaceManager) SetOnDHCPLeaseChange(callback func(lease *types.DHCP
|
|||
im.onDHCPLeaseChange = callback
|
||||
}
|
||||
|
||||
// SetOnResolvConfChange sets the callback for resolv.conf changes
|
||||
func (im *InterfaceManager) SetOnResolvConfChange(callback ResolvConfChangeCallback) {
|
||||
im.onResolvConfChange = callback
|
||||
}
|
||||
|
||||
// applyConfiguration applies the current configuration to the interface
|
||||
func (im *InterfaceManager) applyConfiguration() error {
|
||||
im.logger.Info().Msg("applying configuration")
|
||||
|
|
@ -349,11 +370,6 @@ func (im *InterfaceManager) applyConfiguration() error {
|
|||
return fmt.Errorf("failed to apply IPv6 config: %w", err)
|
||||
}
|
||||
|
||||
// Update hostname
|
||||
if err := im.updateHostname(); err != nil {
|
||||
im.logger.Warn().Err(err).Msg("failed to update hostname")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -419,6 +435,13 @@ func (im *InterfaceManager) applyIPv4Static() error {
|
|||
|
||||
im.logger.Info().Interface("config", config).Msg("converted IPv4 static configuration")
|
||||
|
||||
if err := im.onResolvConfChange(link.AfInet, &types.InterfaceResolvConf{
|
||||
NameServers: config.Nameservers,
|
||||
Source: "static",
|
||||
}); err != nil {
|
||||
im.logger.Warn().Err(err).Msg("failed to update resolv.conf")
|
||||
}
|
||||
|
||||
return im.ReconcileLinkAddrs(config.Addresses, link.AfInet)
|
||||
}
|
||||
|
||||
|
|
@ -463,6 +486,13 @@ func (im *InterfaceManager) applyIPv6Static() error {
|
|||
}
|
||||
im.logger.Info().Interface("config", config).Msg("converted IPv6 static configuration")
|
||||
|
||||
if err := im.onResolvConfChange(link.AfInet6, &types.InterfaceResolvConf{
|
||||
NameServers: config.Nameservers,
|
||||
Source: "static",
|
||||
}); err != nil {
|
||||
im.logger.Warn().Err(err).Msg("failed to update resolv.conf")
|
||||
}
|
||||
|
||||
return im.ReconcileLinkAddrs(config.Addresses, link.AfInet6)
|
||||
}
|
||||
|
||||
|
|
@ -542,39 +572,6 @@ func (im *InterfaceManager) disableIPv6() error {
|
|||
return im.staticConfig.DisableIPv6()
|
||||
}
|
||||
|
||||
// updateHostname updates the system hostname
|
||||
func (im *InterfaceManager) updateHostname() error {
|
||||
hostname := im.getHostname()
|
||||
domain := im.getDomain()
|
||||
fqdn := fmt.Sprintf("%s.%s", hostname, domain)
|
||||
|
||||
return im.hostname.SetHostname(hostname, fqdn)
|
||||
}
|
||||
|
||||
// getHostname returns the configured hostname or default
|
||||
func (im *InterfaceManager) getHostname() string {
|
||||
if im.config.Hostname.String != "" {
|
||||
return im.config.Hostname.String
|
||||
}
|
||||
return "jetkvm"
|
||||
}
|
||||
|
||||
// getDomain returns the configured domain or default
|
||||
func (im *InterfaceManager) getDomain() string {
|
||||
if im.config.Domain.String != "" {
|
||||
return im.config.Domain.String
|
||||
}
|
||||
|
||||
// Try to get domain from DHCP lease
|
||||
if im.dhcpClient != nil {
|
||||
if lease := im.dhcpClient.Lease4(); lease != nil && lease.Domain != "" {
|
||||
return lease.Domain
|
||||
}
|
||||
}
|
||||
|
||||
return "local"
|
||||
}
|
||||
|
||||
func (im *InterfaceManager) handleLinkStateChange(link *link.Link) {
|
||||
{
|
||||
im.stateMu.Lock()
|
||||
|
|
@ -702,13 +699,34 @@ func (im *InterfaceManager) monitorInterfaceState() {
|
|||
|
||||
// updateStateFromDHCPLease updates the state from a DHCP lease
|
||||
func (im *InterfaceManager) updateStateFromDHCPLease(lease *types.DHCPLease) {
|
||||
family := link.AfInet
|
||||
|
||||
im.stateMu.Lock()
|
||||
im.state.DHCPLease4 = lease
|
||||
if lease.IsIPv6() {
|
||||
im.state.DHCPLease6 = lease
|
||||
family = link.AfInet6
|
||||
} else {
|
||||
im.state.DHCPLease4 = lease
|
||||
family = link.AfInet
|
||||
}
|
||||
im.stateMu.Unlock()
|
||||
|
||||
// Update resolv.conf with DNS information
|
||||
if im.resolvConf != nil {
|
||||
im.resolvConf.UpdateFromLease(lease)
|
||||
if im.onResolvConfChange == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if im.ifaceName == "" {
|
||||
im.logger.Warn().Msg("interface name is empty, skipping resolv.conf update")
|
||||
return
|
||||
}
|
||||
|
||||
if err := im.onResolvConfChange(family, &types.InterfaceResolvConf{
|
||||
NameServers: lease.DNS,
|
||||
SearchList: lease.SearchList,
|
||||
Source: "dhcp",
|
||||
}); err != nil {
|
||||
im.logger.Warn().Err(err).Msg("failed to update resolv.conf")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -75,6 +75,8 @@ type Config struct {
|
|||
// If true, add Client Identifier (61) option to the IPv4 request.
|
||||
V4ClientIdentifier bool
|
||||
|
||||
Hostname string
|
||||
|
||||
OnLease4Change LeaseChangeHandler
|
||||
OnLease6Change LeaseChangeHandler
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,14 @@ func (c *Client) requestLease4(ifname string) (*Lease, error) {
|
|||
reqmods := append(
|
||||
[]dhcpv4.Modifier{
|
||||
dhcpv4.WithOption(dhcpv4.OptClassIdentifier(VendorIdentifier)),
|
||||
dhcpv4.WithRequestedOptions(dhcpv4.OptionSubnetMask),
|
||||
dhcpv4.WithRequestedOptions(
|
||||
dhcpv4.OptionSubnetMask,
|
||||
dhcpv4.OptionInterfaceMTU,
|
||||
dhcpv4.OptionNTPServers,
|
||||
dhcpv4.OptionDomainName,
|
||||
dhcpv4.OptionDomainNameServer,
|
||||
dhcpv4.OptionDNSDomainSearchList,
|
||||
),
|
||||
},
|
||||
c.cfg.Modifiers4...)
|
||||
|
||||
|
|
@ -46,6 +53,10 @@ func (c *Client) requestLease4(ifname string) (*Lease, error) {
|
|||
reqmods = append(reqmods, dhcpv4.WithOption(dhcpv4.OptClientIdentifier(ident)))
|
||||
}
|
||||
|
||||
if c.cfg.Hostname != "" {
|
||||
reqmods = append(reqmods, dhcpv4.WithOption(dhcpv4.OptHostName(c.cfg.Hostname)))
|
||||
}
|
||||
|
||||
l.Info().Msg("attempting to get DHCPv4 lease")
|
||||
var (
|
||||
lease *nclient4.Lease
|
||||
|
|
@ -68,6 +79,7 @@ func (c *Client) requestLease4(ifname string) (*Lease, error) {
|
|||
}
|
||||
|
||||
summaryStructured(lease.ACK, &l).Info().Msgf("DHCPv4 lease acquired: %s", lease.ACK.String())
|
||||
l.Trace().Interface("options", lease.ACK.Options.String()).Msg("DHCPv4 lease options")
|
||||
|
||||
return fromNclient4Lease(lease, ifname), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package jetdhcpc
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
|
|
@ -11,6 +12,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4/nclient4"
|
||||
"github.com/insomniacslk/dhcp/dhcpv6"
|
||||
"github.com/jetkvm/kvm/internal/network/types"
|
||||
|
|
@ -66,6 +68,11 @@ func fromNclient4Lease(l *nclient4.Lease, iface string) *Lease {
|
|||
lease.ClassIdentifier = l.ACK.ClassIdentifier()
|
||||
lease.ServerID = l.ACK.ServerIdentifier().String()
|
||||
|
||||
mtu := l.ACK.Options.Get(dhcpv4.OptionInterfaceMTU)
|
||||
if mtu != nil {
|
||||
lease.MTU = int(binary.BigEndian.Uint16(mtu))
|
||||
}
|
||||
|
||||
lease.Message = l.ACK.Message()
|
||||
lease.LeaseTime = l.ACK.IPAddressLeaseTime(defaultLeaseTime)
|
||||
lease.RenewalTime = l.ACK.IPAddressRenewalTime(defaultRenewalTime)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ type NetworkManager struct {
|
|||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
resolvConf *ResolvConfManager
|
||||
|
||||
// Callback functions for state changes
|
||||
onInterfaceStateChange func(iface string, state types.InterfaceState)
|
||||
onConfigChange func(iface string, config *types.NetworkConfig)
|
||||
|
|
@ -45,9 +47,20 @@ func NewNetworkManager(ctx context.Context, logger *zerolog.Logger) *NetworkMana
|
|||
logger: logger,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
resolvConf: NewResolvConfManager(logger),
|
||||
}
|
||||
}
|
||||
|
||||
// SetHostname sets the hostname and domain for the network manager
|
||||
func (nm *NetworkManager) SetHostname(hostname string, domain string) error {
|
||||
return nm.resolvConf.SetHostname(hostname, domain)
|
||||
}
|
||||
|
||||
// Domain returns the effective domain for the network manager
|
||||
func (nm *NetworkManager) Domain() string {
|
||||
return nm.resolvConf.Domain()
|
||||
}
|
||||
|
||||
// AddInterface adds a new network interface to be managed
|
||||
func (nm *NetworkManager) AddInterface(iface string, config *types.NetworkConfig) error {
|
||||
nm.mu.Lock()
|
||||
|
|
@ -81,6 +94,11 @@ func (nm *NetworkManager) AddInterface(iface string, config *types.NetworkConfig
|
|||
}
|
||||
})
|
||||
|
||||
im.SetOnResolvConfChange(func(family int, resolvConf *types.InterfaceResolvConf) error {
|
||||
nm.resolvConf.SetInterfaceConfig(iface, family, *resolvConf)
|
||||
return nil
|
||||
})
|
||||
|
||||
nm.interfaces[iface] = im
|
||||
|
||||
// Start monitoring the interface
|
||||
|
|
|
|||
|
|
@ -4,28 +4,29 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/jetkvm/kvm/internal/network/types"
|
||||
"github.com/jetkvm/kvm/internal/sync"
|
||||
"github.com/jetkvm/kvm/pkg/nmlite/link"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
const (
|
||||
resolvConfPath = "/etc/resolv.conf"
|
||||
resolvConfFileMode = 0644
|
||||
resolvConfTemplate = `# the resolv.conf file is managed by the jetkvm network manager
|
||||
resolvConfTemplate = `# the resolv.conf file is managed by JetKVM
|
||||
# DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
|
||||
|
||||
{{ if .searchList }}
|
||||
search {{ join .searchList " " }} # {{ .iface }}
|
||||
search {{ join .searchList " " }}
|
||||
{{- end -}}
|
||||
{{ if .domain }}
|
||||
domain {{ .domain }} # {{ .iface }}
|
||||
domain {{ .domain }}
|
||||
{{- end -}}
|
||||
{{ range .nameservers }}
|
||||
nameserver {{ printf "%s" . }} # {{ $.iface }}
|
||||
{{ range $ns, $comment := .nameservers }}
|
||||
nameserver {{ printf "%s" $ns }} # {{ join $comment ", " }}
|
||||
{{- end }}
|
||||
`
|
||||
)
|
||||
|
|
@ -39,6 +40,11 @@ var (
|
|||
// ResolvConfManager manages the resolv.conf file
|
||||
type ResolvConfManager struct {
|
||||
logger *zerolog.Logger
|
||||
mu sync.Mutex
|
||||
conf *types.ResolvConf
|
||||
|
||||
hostname string
|
||||
domain string
|
||||
}
|
||||
|
||||
// NewResolvConfManager creates a new resolv.conf manager
|
||||
|
|
@ -50,148 +56,150 @@ func NewResolvConfManager(logger *zerolog.Logger) *ResolvConfManager {
|
|||
|
||||
return &ResolvConfManager{
|
||||
logger: logger,
|
||||
mu: sync.Mutex{},
|
||||
conf: &types.ResolvConf{
|
||||
ConfigIPv4: make(map[string]types.InterfaceResolvConf),
|
||||
ConfigIPv6: make(map[string]types.InterfaceResolvConf),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateFromLease updates resolv.conf from a DHCP lease
|
||||
func (rcm *ResolvConfManager) UpdateFromLease(lease *types.DHCPLease) error {
|
||||
if lease == nil {
|
||||
return fmt.Errorf("lease cannot be nil")
|
||||
// SetInterfaceConfig sets the resolv.conf configuration for a specific interface
|
||||
func (rcm *ResolvConfManager) SetInterfaceConfig(iface string, family int, config types.InterfaceResolvConf) error {
|
||||
// DO NOT USE defer HERE, rcm.update() also locks the mutex
|
||||
rcm.mu.Lock()
|
||||
switch family {
|
||||
case link.AfInet:
|
||||
rcm.conf.ConfigIPv4[iface] = config
|
||||
case link.AfInet6:
|
||||
rcm.conf.ConfigIPv6[iface] = config
|
||||
default:
|
||||
rcm.mu.Unlock()
|
||||
return fmt.Errorf("invalid family: %d", family)
|
||||
}
|
||||
rcm.mu.Unlock()
|
||||
|
||||
rcm.logger.Info().
|
||||
Str("interface", lease.InterfaceName).
|
||||
Msg("updating resolv.conf from DHCP lease")
|
||||
rcm.reconcileHostname()
|
||||
|
||||
return rcm.Update(lease.InterfaceName, lease.DNS, lease.SearchList, lease.Domain)
|
||||
return rcm.update()
|
||||
}
|
||||
|
||||
// UpdateFromStaticConfig updates resolv.conf from static configuration
|
||||
func (rcm *ResolvConfManager) UpdateFromStaticConfig(iface string, dns []string) error {
|
||||
if len(dns) == 0 {
|
||||
rcm.logger.Debug().Str("interface", iface).Msg("no DNS servers in static config")
|
||||
return nil
|
||||
// SetConfig sets the resolv.conf configuration
|
||||
func (rcm *ResolvConfManager) SetConfig(resolvConf *types.ResolvConf) error {
|
||||
if resolvConf == nil {
|
||||
return fmt.Errorf("resolvConf cannot be nil")
|
||||
}
|
||||
|
||||
// Parse DNS servers
|
||||
var dnsIPs []net.IP
|
||||
for _, dnsStr := range dns {
|
||||
dnsIP := net.ParseIP(dnsStr)
|
||||
if dnsIP == nil {
|
||||
rcm.logger.Warn().Str("dns", dnsStr).Msg("invalid DNS server, skipping")
|
||||
continue
|
||||
}
|
||||
dnsIPs = append(dnsIPs, dnsIP)
|
||||
}
|
||||
rcm.mu.Lock()
|
||||
rcm.conf = resolvConf
|
||||
defer rcm.mu.Unlock()
|
||||
|
||||
if len(dnsIPs) == 0 {
|
||||
rcm.logger.Debug().Str("interface", iface).Msg("no valid DNS servers in static config")
|
||||
return nil
|
||||
}
|
||||
return rcm.update()
|
||||
}
|
||||
|
||||
rcm.logger.Info().
|
||||
Str("interface", iface).
|
||||
Interface("dns", dnsIPs).
|
||||
Msg("updating resolv.conf from static config")
|
||||
|
||||
return rcm.Update(iface, dnsIPs, nil, "")
|
||||
// Reconcile reconciles the resolv.conf configuration
|
||||
func (rcm *ResolvConfManager) Reconcile() error {
|
||||
rcm.reconcileHostname()
|
||||
return rcm.update()
|
||||
}
|
||||
|
||||
// Update updates the resolv.conf file
|
||||
func (rcm *ResolvConfManager) Update(iface string, nameservers []net.IP, searchList []string, domain string) error {
|
||||
rcm.logger.Debug().
|
||||
Str("interface", iface).
|
||||
Interface("nameservers", nameservers).
|
||||
Interface("searchList", searchList).
|
||||
Str("domain", domain).
|
||||
Msg("updating resolv.conf")
|
||||
func (rcm *ResolvConfManager) update() error {
|
||||
rcm.mu.Lock()
|
||||
defer rcm.mu.Unlock()
|
||||
|
||||
rcm.logger.Debug().Msg("updating resolv.conf")
|
||||
|
||||
// Generate resolv.conf content
|
||||
content, err := rcm.generateResolvConf(iface, nameservers, searchList, domain)
|
||||
content, err := rcm.generateResolvConf(rcm.conf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate resolv.conf: %w", err)
|
||||
}
|
||||
|
||||
// Check if the file is the same
|
||||
if _, err := os.Stat(resolvConfPath); err == nil {
|
||||
existingContent, err := os.ReadFile(resolvConfPath)
|
||||
if err != nil {
|
||||
rcm.logger.Warn().Err(err).Msg("failed to read existing resolv.conf")
|
||||
}
|
||||
|
||||
if bytes.Equal(existingContent, content) {
|
||||
rcm.logger.Debug().Msg("resolv.conf is the same, skipping write")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Write to file
|
||||
if err := os.WriteFile(resolvConfPath, content, resolvConfFileMode); err != nil {
|
||||
return fmt.Errorf("failed to write resolv.conf: %w", err)
|
||||
}
|
||||
|
||||
rcm.logger.Info().
|
||||
Str("interface", iface).
|
||||
Int("nameservers", len(nameservers)).
|
||||
Interface("config", rcm.conf).
|
||||
Msg("resolv.conf updated successfully")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type configMap map[string][]string
|
||||
|
||||
func mergeConfig(nameservers *configMap, searchList *configMap, config *types.InterfaceResolvConfMap) {
|
||||
localNameservers := *nameservers
|
||||
localSearchList := *searchList
|
||||
|
||||
for ifname, iface := range *config {
|
||||
comment := ifname
|
||||
if iface.Source != "" {
|
||||
comment += fmt.Sprintf(" (%s)", iface.Source)
|
||||
}
|
||||
|
||||
for _, ip := range iface.NameServers {
|
||||
ns := ip.String()
|
||||
if _, ok := localNameservers[ns]; !ok {
|
||||
localNameservers[ns] = []string{}
|
||||
}
|
||||
localNameservers[ns] = append(localNameservers[ns], comment)
|
||||
}
|
||||
|
||||
for _, search := range iface.SearchList {
|
||||
search = strings.Trim(search, ".")
|
||||
if _, ok := localSearchList[search]; !ok {
|
||||
localSearchList[search] = []string{}
|
||||
}
|
||||
localSearchList[search] = append(localSearchList[search], comment)
|
||||
}
|
||||
}
|
||||
|
||||
*nameservers = localNameservers
|
||||
*searchList = localSearchList
|
||||
}
|
||||
|
||||
// generateResolvConf generates resolv.conf content
|
||||
func (rcm *ResolvConfManager) generateResolvConf(iface string, nameservers []net.IP, searchList []string, domain string) ([]byte, error) {
|
||||
func (rcm *ResolvConfManager) generateResolvConf(conf *types.ResolvConf) ([]byte, error) {
|
||||
tmpl, err := template.New("resolv.conf").Funcs(tplFuncMap).Parse(resolvConfTemplate)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse template: %w", err)
|
||||
}
|
||||
|
||||
// merge the nameservers and searchList
|
||||
nameservers := configMap{}
|
||||
searchList := configMap{}
|
||||
|
||||
mergeConfig(&nameservers, &searchList, &conf.ConfigIPv4)
|
||||
mergeConfig(&nameservers, &searchList, &conf.ConfigIPv6)
|
||||
|
||||
flattenedSearchList := []string{}
|
||||
for search := range searchList {
|
||||
flattenedSearchList = append(flattenedSearchList, search)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := tmpl.Execute(&buf, map[string]any{
|
||||
"iface": iface,
|
||||
"nameservers": nameservers,
|
||||
"searchList": searchList,
|
||||
"domain": domain,
|
||||
"searchList": flattenedSearchList,
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("failed to execute template: %w", err)
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// Clear clears the resolv.conf file (removes all entries)
|
||||
func (rcm *ResolvConfManager) Clear() error {
|
||||
rcm.logger.Info().Msg("clearing resolv.conf")
|
||||
|
||||
content := []byte("# the resolv.conf file is managed by the jetkvm network manager\n# DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN\n")
|
||||
|
||||
if err := os.WriteFile(resolvConfPath, content, resolvConfFileMode); err != nil {
|
||||
return fmt.Errorf("failed to clear resolv.conf: %w", err)
|
||||
}
|
||||
|
||||
rcm.logger.Info().Msg("resolv.conf cleared")
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCurrentContent returns the current content of resolv.conf
|
||||
func (rcm *ResolvConfManager) GetCurrentContent() ([]byte, error) {
|
||||
return os.ReadFile(resolvConfPath)
|
||||
}
|
||||
|
||||
// Backup creates a backup of the current resolv.conf
|
||||
func (rcm *ResolvConfManager) Backup() error {
|
||||
content, err := rcm.GetCurrentContent()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read current resolv.conf: %w", err)
|
||||
}
|
||||
|
||||
backupPath := resolvConfPath + ".backup"
|
||||
if err := os.WriteFile(backupPath, content, resolvConfFileMode); err != nil {
|
||||
return fmt.Errorf("failed to create backup: %w", err)
|
||||
}
|
||||
|
||||
rcm.logger.Info().Str("backup", backupPath).Msg("resolv.conf backed up")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Restore restores resolv.conf from backup
|
||||
func (rcm *ResolvConfManager) Restore() error {
|
||||
backupPath := resolvConfPath + ".backup"
|
||||
content, err := os.ReadFile(backupPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read backup: %w", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(resolvConfPath, content, resolvConfFileMode); err != nil {
|
||||
return fmt.Errorf("failed to restore resolv.conf: %w", err)
|
||||
}
|
||||
|
||||
rcm.logger.Info().Str("backup", backupPath).Msg("resolv.conf restored from backup")
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
package nmlite
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestToResolvConf(t *testing.T) {
|
||||
rc, err := ResolvConfManager{}.generateResolvConf(
|
||||
"eth0",
|
||||
[]net.IP{
|
||||
net.ParseIP("198.51.100.53"),
|
||||
net.ParseIP("203.0.113.53"),
|
||||
},
|
||||
[]string{"example.com"},
|
||||
"example.com",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
want := `# the resolv.conf file is managed by the jetkvm network manager
|
||||
# DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
|
||||
|
||||
|
||||
search example.com # eth0
|
||||
domain example.com # eth0
|
||||
nameserver 198.51.100.53 # eth0
|
||||
nameserver 203.0.113.53 # eth0
|
||||
`
|
||||
|
||||
assert.Equal(t, want, rc.String())
|
||||
}
|
||||
|
|
@ -20,12 +20,12 @@ func (nm *NetworkManager) IsUp() bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func (nm *NetworkManager) GetHostname() string {
|
||||
return "jetkvm"
|
||||
func (nm *NetworkManager) Hostname() string {
|
||||
return nm.resolvConf.Hostname()
|
||||
}
|
||||
|
||||
func (nm *NetworkManager) GetFQDN() string {
|
||||
return "jetkvm.local"
|
||||
func (nm *NetworkManager) FQDN() string {
|
||||
return nm.resolvConf.FQDN()
|
||||
}
|
||||
|
||||
func (nm *NetworkManager) NTPServers() []net.IP {
|
||||
|
|
|
|||
Loading…
Reference in New Issue