mirror of https://github.com/jetkvm/kvm.git
313 lines
8.0 KiB
Go
313 lines
8.0 KiB
Go
package kvm
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"reflect"
|
|
|
|
"github.com/jetkvm/kvm/internal/confparser"
|
|
"github.com/jetkvm/kvm/internal/mdns"
|
|
"github.com/jetkvm/kvm/internal/network/types"
|
|
"github.com/jetkvm/kvm/pkg/nmlite"
|
|
)
|
|
|
|
const (
|
|
NetIfName = "eth0"
|
|
)
|
|
|
|
var (
|
|
networkManager *nmlite.NetworkManager
|
|
)
|
|
|
|
type RpcNetworkSettings struct {
|
|
types.NetworkConfig
|
|
}
|
|
|
|
func (s *RpcNetworkSettings) ToNetworkConfig() *types.NetworkConfig {
|
|
return &s.NetworkConfig
|
|
}
|
|
|
|
type PostRebootAction struct {
|
|
HealthCheck string `json:"healthCheck"`
|
|
RedirectUrl string `json:"redirectUrl"`
|
|
}
|
|
|
|
func toRpcNetworkSettings(config *types.NetworkConfig) *RpcNetworkSettings {
|
|
return &RpcNetworkSettings{
|
|
NetworkConfig: *config,
|
|
}
|
|
}
|
|
|
|
func getMdnsOptions() *mdns.MDNSOptions {
|
|
if networkManager == nil {
|
|
return nil
|
|
}
|
|
|
|
var ipv4, ipv6 bool
|
|
switch config.NetworkConfig.MDNSMode.String {
|
|
case "auto":
|
|
ipv4 = true
|
|
ipv6 = true
|
|
case "ipv4_only":
|
|
ipv4 = true
|
|
case "ipv6_only":
|
|
ipv6 = true
|
|
}
|
|
|
|
return &mdns.MDNSOptions{
|
|
LocalNames: []string{
|
|
networkManager.Hostname(),
|
|
networkManager.FQDN(),
|
|
},
|
|
ListenOptions: &mdns.MDNSListenOptions{
|
|
IPv4: ipv4,
|
|
IPv6: ipv6,
|
|
},
|
|
}
|
|
}
|
|
|
|
func restartMdns() {
|
|
if mDNS == nil {
|
|
return
|
|
}
|
|
|
|
options := getMdnsOptions()
|
|
if options == nil {
|
|
return
|
|
}
|
|
|
|
if err := mDNS.SetOptions(options); err != nil {
|
|
networkLogger.Error().Err(err).Msg("failed to restart mDNS")
|
|
}
|
|
}
|
|
|
|
func triggerTimeSyncOnNetworkStateChange() {
|
|
if timeSync == nil {
|
|
return
|
|
}
|
|
|
|
// set the NTP servers from the network manager
|
|
if networkManager != nil {
|
|
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
|
|
go func() {
|
|
if err := timeSync.Sync(); err != nil {
|
|
networkLogger.Error().Err(err).Msg("failed to sync time after network state change")
|
|
}
|
|
}()
|
|
}
|
|
|
|
func networkStateChanged(_ string, state types.InterfaceState) {
|
|
// do not block the main thread
|
|
go waitCtrlAndRequestDisplayUpdate(true, "network_state_changed")
|
|
|
|
if currentSession != nil {
|
|
writeJSONRPCEvent("networkState", state.ToRpcInterfaceState(), currentSession)
|
|
}
|
|
|
|
if state.Online {
|
|
networkLogger.Info().Msg("network state changed to online, triggering time sync")
|
|
triggerTimeSyncOnNetworkStateChange()
|
|
}
|
|
|
|
// always restart mDNS when the network state changes
|
|
if mDNS != nil {
|
|
restartMdns()
|
|
}
|
|
}
|
|
|
|
func validateNetworkConfig() {
|
|
err := confparser.SetDefaultsAndValidate(config.NetworkConfig)
|
|
if err == nil {
|
|
return
|
|
}
|
|
|
|
networkLogger.Error().Err(err).Msg("failed to validate config, reverting to default config")
|
|
if err := SaveBackupConfig(); err != nil {
|
|
networkLogger.Error().Err(err).Msg("failed to save backup config")
|
|
}
|
|
|
|
// do not use a pointer to the default config
|
|
// it has been already changed during LoadConfig
|
|
config.NetworkConfig = &(types.NetworkConfig{})
|
|
if err := SaveConfig(); err != nil {
|
|
networkLogger.Error().Err(err).Msg("failed to save config")
|
|
}
|
|
}
|
|
|
|
func initNetwork() error {
|
|
ensureConfigLoaded()
|
|
|
|
// validate the config, if it's invalid, revert to the default config and save the backup
|
|
validateNetworkConfig()
|
|
|
|
nc := config.NetworkConfig
|
|
|
|
nm := nmlite.NewNetworkManager(context.Background(), networkLogger)
|
|
networkLogger.Info().Interface("networkConfig", nc).Str("hostname", nc.Hostname.String).Str("domain", nc.Domain.String).Msg("initializing network manager")
|
|
_ = 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)
|
|
}
|
|
_ = nm.CleanUpLegacyDHCPClients()
|
|
|
|
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 shouldRebootForNetworkChange(oldConfig, newConfig *types.NetworkConfig) (rebootRequired bool, postRebootAction *PostRebootAction) {
|
|
oldDhcpClient := oldConfig.DHCPClient.String
|
|
|
|
l := networkLogger.With().
|
|
Interface("old", oldConfig).
|
|
Interface("new", newConfig).
|
|
Logger()
|
|
|
|
// DHCP client change always requires reboot
|
|
if newConfig.DHCPClient.String != oldDhcpClient {
|
|
rebootRequired = true
|
|
l.Info().Msg("DHCP client changed, reboot required")
|
|
return rebootRequired, postRebootAction
|
|
}
|
|
|
|
oldIPv4Mode := oldConfig.IPv4Mode.String
|
|
newIPv4Mode := newConfig.IPv4Mode.String
|
|
// IPv4 mode change requires reboot
|
|
if newIPv4Mode != oldIPv4Mode {
|
|
rebootRequired = true
|
|
l.Info().Msg("IPv4 mode changed with udhcpc, reboot required")
|
|
|
|
if newIPv4Mode == "static" && oldIPv4Mode != "static" {
|
|
postRebootAction = &PostRebootAction{
|
|
HealthCheck: fmt.Sprintf("//%s/device/status", newConfig.IPv4Static.Address.String),
|
|
RedirectUrl: fmt.Sprintf("//%s", newConfig.IPv4Static.Address.String),
|
|
}
|
|
l.Info().Interface("postRebootAction", postRebootAction).Msg("IPv4 mode changed to static, reboot required")
|
|
}
|
|
|
|
return rebootRequired, postRebootAction
|
|
}
|
|
|
|
// IPv4 static config changes require reboot
|
|
if !reflect.DeepEqual(oldConfig.IPv4Static, newConfig.IPv4Static) {
|
|
rebootRequired = true
|
|
|
|
// Handle IP change for redirect (only if both are not nil and IP changed)
|
|
if newConfig.IPv4Static != nil && oldConfig.IPv4Static != nil &&
|
|
newConfig.IPv4Static.Address.String != oldConfig.IPv4Static.Address.String {
|
|
postRebootAction = &PostRebootAction{
|
|
HealthCheck: fmt.Sprintf("//%s/device/status", newConfig.IPv4Static.Address.String),
|
|
RedirectUrl: fmt.Sprintf("//%s", newConfig.IPv4Static.Address.String),
|
|
}
|
|
|
|
l.Info().Interface("postRebootAction", postRebootAction).Msg("IPv4 static config changed, reboot required")
|
|
}
|
|
|
|
return rebootRequired, postRebootAction
|
|
}
|
|
|
|
// IPv6 mode change requires reboot when using udhcpc
|
|
if newConfig.IPv6Mode.String != oldConfig.IPv6Mode.String && oldDhcpClient == "udhcpc" {
|
|
rebootRequired = true
|
|
l.Info().Msg("IPv6 mode changed with udhcpc, reboot required")
|
|
}
|
|
|
|
return rebootRequired, postRebootAction
|
|
}
|
|
|
|
func rpcGetNetworkState() *types.RpcInterfaceState {
|
|
state, _ := networkManager.GetInterfaceState(NetIfName)
|
|
return state.ToRpcInterfaceState()
|
|
}
|
|
|
|
func rpcGetNetworkSettings() *RpcNetworkSettings {
|
|
return toRpcNetworkSettings(config.NetworkConfig)
|
|
}
|
|
|
|
func rpcSetNetworkSettings(settings RpcNetworkSettings) (*RpcNetworkSettings, error) {
|
|
netConfig := settings.ToNetworkConfig()
|
|
|
|
l := networkLogger.With().
|
|
Str("interface", NetIfName).
|
|
Interface("newConfig", netConfig).
|
|
Logger()
|
|
|
|
l.Debug().Msg("setting new config")
|
|
|
|
// Check if reboot is needed
|
|
rebootRequired, postRebootAction := shouldRebootForNetworkChange(config.NetworkConfig, netConfig)
|
|
|
|
// If reboot required, send willReboot event before applying network config
|
|
if rebootRequired {
|
|
l.Info().Msg("Sending willReboot event before applying network config")
|
|
writeJSONRPCEvent("willReboot", postRebootAction, currentSession)
|
|
}
|
|
|
|
_ = setHostname(networkManager, netConfig.Hostname.String, netConfig.Domain.String)
|
|
|
|
s := networkManager.SetInterfaceConfig(NetIfName, netConfig)
|
|
if s != nil {
|
|
return nil, s
|
|
}
|
|
l.Debug().Msg("new config applied")
|
|
|
|
newConfig, err := networkManager.GetInterfaceConfig(NetIfName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
config.NetworkConfig = newConfig
|
|
|
|
l.Debug().Msg("saving new config")
|
|
if err := SaveConfig(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if rebootRequired {
|
|
if err := rpcReboot(false); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return toRpcNetworkSettings(newConfig), nil
|
|
}
|
|
|
|
func rpcRenewDHCPLease() error {
|
|
return networkManager.RenewDHCPLease(NetIfName)
|
|
}
|
|
|
|
func rpcToggleDHCPClient() error {
|
|
switch config.NetworkConfig.DHCPClient.String {
|
|
case "jetdhcpc":
|
|
config.NetworkConfig.DHCPClient.String = "udhcpc"
|
|
case "udhcpc":
|
|
config.NetworkConfig.DHCPClient.String = "jetdhcpc"
|
|
}
|
|
|
|
if err := SaveConfig(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return rpcReboot(true)
|
|
}
|