use reconcile instead of updating addresses and routes individually

This commit is contained in:
Siyuan 2025-10-08 17:46:19 +00:00
parent ad0b86c8a6
commit 97844a8caf
9 changed files with 301 additions and 294 deletions

View File

@ -4,9 +4,11 @@ import (
"net"
"net/http"
"net/url"
"slices"
"time"
"github.com/guregu/null/v6"
"github.com/vishvananda/netlink"
)
// IPAddress represents a network interface address
@ -19,14 +21,50 @@ type IPAddress struct {
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"`
valid_lft int `json:"valid_lft"`
prefered_lft int `json:"prefered_lft"`
Scope int `json:"scope"`
}
@ -120,6 +158,7 @@ type InterfaceState struct {
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"`

View File

@ -403,13 +403,23 @@ func (im *InterfaceManager) applyIPv4Static() error {
return fmt.Errorf("IPv4 static configuration is nil")
}
im.logger.Info().Msg("stopping DHCP")
// Disable DHCP
if im.dhcpClient != nil {
im.dhcpClient.SetIPv4(false)
}
// Apply static configuration
return im.staticConfig.ApplyIPv4Static(im.config.IPv4Static)
im.logger.Info().Interface("config", im.config.IPv4Static).Msg("applying IPv4 static configuration")
config, err := im.staticConfig.ToIPv4Static(im.config.IPv4Static)
if err != nil {
return fmt.Errorf("failed to convert IPv4 static configuration: %w", err)
}
im.logger.Info().Interface("config", config).Msg("converted IPv4 static configuration")
return im.ReconcileLinkAddrs(config.Addresses, link.AfInet)
}
// applyIPv4DHCP applies DHCP IPv4 configuration
@ -440,13 +450,20 @@ func (im *InterfaceManager) applyIPv6Static() error {
return fmt.Errorf("IPv6 static configuration is nil")
}
im.logger.Info().Msg("stopping DHCPv6")
// Disable DHCPv6
if im.dhcpClient != nil {
im.dhcpClient.SetIPv6(false)
}
// Apply static configuration
return im.staticConfig.ApplyIPv6Static(im.config.IPv6Static)
config, err := im.staticConfig.ToIPv6Static(im.config.IPv6Static)
if err != nil {
return fmt.Errorf("failed to convert IPv6 static configuration: %w", err)
}
im.logger.Info().Interface("config", config).Msg("converted IPv6 static configuration")
return im.ReconcileLinkAddrs(config.Addresses, link.AfInet6)
}
// applyIPv6DHCP applies DHCPv6 configuration
@ -696,7 +713,7 @@ func (im *InterfaceManager) updateStateFromDHCPLease(lease *types.DHCPLease) {
}
// ReconcileLinkAddrs reconciles the link addresses
func (im *InterfaceManager) ReconcileLinkAddrs(addrs []*types.IPAddress) error {
func (im *InterfaceManager) ReconcileLinkAddrs(addrs []types.IPAddress, family int) error {
nl := getNetlinkManager()
link, err := im.link()
if err != nil {
@ -705,7 +722,7 @@ func (im *InterfaceManager) ReconcileLinkAddrs(addrs []*types.IPAddress) error {
if link == nil {
return fmt.Errorf("failed to get interface: %w", err)
}
return nl.ReconcileLink(link, addrs)
return nl.ReconcileLink(link, addrs, family)
}
// applyDHCPLease applies DHCP lease configuration using ReconcileLinkAddrs
@ -714,7 +731,7 @@ func (im *InterfaceManager) applyDHCPLease(lease *types.DHCPLease) error {
ipv4Config := im.convertDHCPLeaseToIPv4Config(lease)
// Apply the configuration using ReconcileLinkAddrs
return im.ReconcileLinkAddrs([]*types.IPAddress{ipv4Config})
return im.ReconcileLinkAddrs([]types.IPAddress{*ipv4Config}, link.AfInet)
}
// convertDHCPLeaseToIPv4Config converts a DHCP lease to IPv4Config

View File

@ -65,6 +65,8 @@ func (im *InterfaceManager) updateInterfaceState() error {
// updateIPAddresses updates the IP addresses in the state
func (im *InterfaceManager) updateInterfaceStateAddresses(nl *link.Link) (bool, error) {
mgr := getNetlinkManager()
addrs, err := nl.AddrList(link.AfUnspec)
if err != nil {
return false, fmt.Errorf("failed to get addresses: %w", err)
@ -75,14 +77,17 @@ func (im *InterfaceManager) updateInterfaceStateAddresses(nl *link.Link) (bool,
ipv6Addresses []types.IPv6Address
ipv4Addr, ipv6Addr string
ipv6LinkLocal string
ipv6Gateway string
ipv4Ready, ipv6Ready = false, false
stateChanged = false
)
routes, _ := mgr.ListDefaultRoutes(link.AfInet6)
if len(routes) > 0 {
ipv6Gateway = routes[0].Gw.String()
}
for _, addr := range addrs {
im.logger.Debug().
IPAddr("address", addr.IP).
Msg("checking address")
if addr.IP.To4() != nil {
// IPv4 address
ipv4Addresses = append(ipv4Addresses, addr.IPNet.String())
@ -138,6 +143,11 @@ func (im *InterfaceManager) updateInterfaceStateAddresses(nl *link.Link) (bool,
stateChanged = true
}
if im.state.IPv6Gateway != ipv6Gateway {
im.state.IPv6Gateway = ipv6Gateway
stateChanged = true
}
if im.state.IPv4Ready != ipv4Ready {
im.state.IPv4Ready = ipv4Ready
stateChanged = true

View File

@ -279,7 +279,7 @@ func (c *Client) handleLeaseChange(lease *Lease) {
}
// clear all current jobs with the same tags
c.scheduler.RemoveByTags(version)
// c.scheduler.RemoveByTags(version)
// add scheduler job to renew the lease
if lease.RenewalTime > 0 {
@ -357,6 +357,9 @@ func (c *Client) SetIPv4(ipv4 bool) {
}
func (c *Client) SetIPv6(ipv6 bool) {
c.cfgMu.Lock()
defer c.cfgMu.Unlock()
c.cfg.IPv6 = ipv6
}

View File

@ -302,26 +302,28 @@ func (nm *NetlinkManager) RouteReplace(route *netlink.Route) error {
return netlink.RouteReplace(route)
}
// ListDefaultRoutes lists the default routes for the given family
func (nm *NetlinkManager) ListDefaultRoutes(family int) ([]netlink.Route, error) {
routes, err := netlink.RouteListFiltered(
family,
&netlink.Route{Dst: nil, Table: 254},
netlink.RT_FILTER_DST|netlink.RT_FILTER_TABLE,
)
if err != nil {
nm.logger.Error().Err(err).Int("family", family).Msg("failed to list default routes")
return nil, err
}
return routes, nil
}
// HasDefaultRoute checks if a default route exists for the given family
func (nm *NetlinkManager) HasDefaultRoute(family int) bool {
routes, err := netlink.RouteList(nil, family)
routes, err := nm.ListDefaultRoutes(family)
if err != nil {
return false
}
for _, route := range routes {
if route.Dst == nil {
return true
}
if family == AfInet && route.Dst.IP.Equal(net.IPv4zero) && route.Dst.Mask.String() == "0.0.0.0/0" {
return true
}
if family == AfInet6 && route.Dst.IP.Equal(net.IPv6zero) && route.Dst.Mask.String() == "::/0" {
return true
}
}
return false
return len(routes) > 0
}
// AddDefaultRoute adds a default route
@ -370,59 +372,152 @@ func (nm *NetlinkManager) RemoveDefaultRoute(family int) error {
return nil
}
// ReconcileLink reconciles the addresses and routes of a link
func (nm *NetlinkManager) ReconcileLink(link *Link, expected []*types.IPAddress) error {
expectedAddrs := make(map[string]bool)
existingAddrs := make(map[string]bool)
func (nm *NetlinkManager) reconcileDefaultRoute(link *Link, expected map[string]net.IP, family int) error {
linkIndex := link.Attrs().Index
for _, addr := range expected {
ipCidr := addr.Address.IP.String() + "/" + addr.Address.Mask.String()
expectedAddrs[ipCidr] = true
added := 0
toRemove := make([]*netlink.Route, 0)
defaultRoutes, err := nm.ListDefaultRoutes(family)
if err != nil {
return fmt.Errorf("failed to get default routes: %w", err)
}
addrs, err := nm.AddrList(link, AfUnspec)
// check existing default routes
for _, defaultRoute := range defaultRoutes {
// only check the default routes for the current link
// TODO: we should also check others later
if defaultRoute.LinkIndex != linkIndex {
continue
}
key := defaultRoute.Gw.String()
if _, ok := expected[key]; !ok {
toRemove = append(toRemove, &defaultRoute)
continue
}
nm.logger.Warn().Str("gateway", key).Msg("keeping default route")
delete(expected, key)
}
// remove remaining default routes
for _, defaultRoute := range toRemove {
nm.logger.Warn().Str("gateway", defaultRoute.Gw.String()).Msg("removing default route")
if err := nm.RouteDel(defaultRoute); err != nil {
nm.logger.Warn().Err(err).Msg("failed to remove default route")
}
}
// add remaining expected default routes
for _, gateway := range expected {
nm.logger.Warn().Str("gateway", gateway.String()).Msg("adding default route")
route := &netlink.Route{
Dst: &ipv4DefaultRoute,
Gw: gateway,
LinkIndex: linkIndex,
}
if family == AfInet6 {
route.Dst = &ipv6DefaultRoute
}
if err := nm.RouteAdd(route); err != nil {
nm.logger.Warn().Err(err).Interface("route", route).Msg("failed to add default route")
}
added++
}
nm.logger.Info().
Int("added", added).
Int("removed", len(toRemove)).
Msg("default routes reconciled")
return nil
}
// ReconcileLink reconciles the addresses and routes of a link
func (nm *NetlinkManager) ReconcileLink(link *Link, expected []types.IPAddress, family int) error {
toAdd := make([]*types.IPAddress, 0)
toRemove := make([]*netlink.Addr, 0)
toUpdate := make([]*types.IPAddress, 0)
expectedAddrs := make(map[string]*types.IPAddress)
expectedGateways := make(map[string]net.IP)
// add all expected addresses to the map
for _, addr := range expected {
expectedAddrs[addr.String()] = &addr
if addr.Gateway != nil {
expectedGateways[addr.String()] = addr.Gateway
}
}
addrs, err := nm.AddrList(link, family)
if err != nil {
return fmt.Errorf("failed to get addresses: %w", err)
}
// check existing addresses
for _, addr := range addrs {
ipCidr := addr.IP.String() + "/" + addr.IPNet.Mask.String()
existingAddrs[ipCidr] = true
// skip the link-local address
if addr.IP.IsLinkLocalUnicast() {
continue
}
for _, addr := range expected {
family := AfUnspec
if addr.Address.IP.To4() != nil {
family = AfInet
} else if addr.Address.IP.To16() != nil {
family = AfInet6
expectedAddr, ok := expectedAddrs[addr.IPNet.String()]
if !ok {
toRemove = append(toRemove, &addr)
continue
}
ipCidr := addr.Address.IP.String() + "/" + addr.Address.Mask.String()
ipNet := &net.IPNet{
IP: addr.Address.IP,
Mask: addr.Address.Mask,
// if it's not fully equal, we need to update it
if !expectedAddr.Compare(addr) {
toUpdate = append(toUpdate, expectedAddr)
continue
}
l := nm.logger.With().Str("address", ipNet.String()).Logger()
if ok := existingAddrs[ipCidr]; !ok {
l.Trace().Msg("adding address")
if err := nm.AddrAdd(link, &netlink.Addr{IPNet: ipNet}); err != nil {
return fmt.Errorf("failed to add address %s: %w", ipCidr, err)
// remove it from expected addresses
delete(expectedAddrs, addr.IPNet.String())
}
l.Info().Msg("address added")
// add remaining expected addresses
for _, addr := range expectedAddrs {
toAdd = append(toAdd, addr)
}
if addr.Gateway != nil {
gl := l.With().Str("gateway", addr.Gateway.String()).Logger()
gl.Trace().Msg("adding default route")
if err := nm.AddDefaultRoute(link, addr.Gateway, family); err != nil {
return fmt.Errorf("failed to add default route for address %s: %w", ipCidr, err)
for _, addr := range toUpdate {
netlinkAddr := addr.NetlinkAddr()
if err := nm.AddrDel(link, &netlinkAddr); err != nil {
nm.logger.Warn().Err(err).Str("address", addr.Address.String()).Msg("failed to update address")
}
gl.Info().Msg("default route added")
// we'll add it again later
toAdd = append(toAdd, addr)
}
for _, addr := range toAdd {
netlinkAddr := addr.NetlinkAddr()
if err := nm.AddrAdd(link, &netlinkAddr); err != nil {
nm.logger.Warn().Err(err).Str("address", addr.Address.String()).Msg("failed to add address")
}
}
for _, netlinkAddr := range toRemove {
if err := nm.AddrDel(link, netlinkAddr); err != nil {
nm.logger.Warn().Err(err).Str("address", netlinkAddr.IP.String()).Msg("failed to remove address")
}
}
actualToAdd := len(toAdd) - len(toUpdate)
if len(toAdd) > 0 || len(toUpdate) > 0 || len(toRemove) > 0 {
nm.logger.Info().
Int("added", actualToAdd).
Int("updated", len(toUpdate)).
Int("removed", len(toRemove)).
Msg("addresses reconciled")
}
if err := nm.reconcileDefaultRoute(link, expectedGateways, family); err != nil {
nm.logger.Warn().Err(err).Msg("failed to reconcile default route")
}
return nil

View File

@ -11,13 +11,3 @@ type IPv4Address struct {
Secondary bool
Permanent bool
}
// IPv4Config represents the configuration for an IPv4 interface
type IPv4Config struct {
Addresses []IPv4Address
Nameservers []net.IP
SearchList []string
Domain string
MTU int
Interface string
}

View File

@ -7,7 +7,6 @@ import (
"github.com/jetkvm/kvm/internal/network/types"
"github.com/jetkvm/kvm/pkg/nmlite/link"
"github.com/rs/zerolog"
"github.com/vishvananda/netlink"
)
// StaticConfigManager manages static network configuration
@ -32,81 +31,90 @@ func NewStaticConfigManager(ifaceName string, logger *zerolog.Logger) (*StaticCo
}, nil
}
// ApplyIPv4Static applies static IPv4 configuration
func (scm *StaticConfigManager) ApplyIPv4Static(config *types.IPv4StaticConfig) error {
scm.logger.Info().Msg("applying static IPv4 configuration")
// ToIPv4Static applies static IPv4 configuration
func (scm *StaticConfigManager) ToIPv4Static(config *types.IPv4StaticConfig) (*types.ParsedIPConfig, error) {
if config == nil {
return nil, fmt.Errorf("config is nil")
}
// Parse and validate configuration
ipv4Config, err := scm.parseIPv4Config(config)
// Parse IP address and netmask
ipNet, err := link.ParseIPv4Netmask(config.Address.String, config.Netmask.String)
if err != nil {
return fmt.Errorf("failed to parse IPv4 config: %w", err)
return nil, err
}
scm.logger.Info().Str("ipNet", ipNet.String()).Interface("ipc", config).Msg("parsed IPv4 address and netmask")
// Parse gateway
gateway := net.ParseIP(config.Gateway.String)
if gateway == nil {
return nil, fmt.Errorf("invalid gateway: %s", config.Gateway.String)
}
// Get interface
netlinkMgr := getNetlinkManager()
link, err := netlinkMgr.GetLinkByName(scm.ifaceName)
// Parse DNS servers
var dns []net.IP
for _, dnsStr := range config.DNS {
if err := link.ValidateIPAddress(dnsStr, false); err != nil {
return nil, fmt.Errorf("invalid DNS server: %w", err)
}
dns = append(dns, net.ParseIP(dnsStr))
}
address := types.IPAddress{
Family: link.AfInet,
Address: *ipNet,
Gateway: gateway,
Secondary: false,
Permanent: true,
}
return &types.ParsedIPConfig{
Addresses: []types.IPAddress{address},
Nameservers: dns,
Interface: scm.ifaceName,
}, nil
}
// ToIPv6Static applies static IPv6 configuration
func (scm *StaticConfigManager) ToIPv6Static(config *types.IPv6StaticConfig) (*types.ParsedIPConfig, error) {
if config == nil {
return nil, fmt.Errorf("config is nil")
}
// Parse IP address and prefix
ipNet, err := link.ParseIPv6Prefix(config.Prefix.String, 64) // Default to /64 if not specified
if err != nil {
return fmt.Errorf("failed to get interface: %w", err)
return nil, err
}
// Ensure interface is up
if err := netlinkMgr.EnsureInterfaceUp(link); err != nil {
return fmt.Errorf("failed to bring interface up: %w", err)
// Parse gateway
gateway := net.ParseIP(config.Gateway.String)
if gateway == nil {
return nil, fmt.Errorf("invalid gateway: %s", config.Gateway.String)
}
// Apply IP address
if err := scm.applyIPv4Address(link, ipv4Config); err != nil {
return fmt.Errorf("failed to apply IPv4 address: %w", err)
// Parse DNS servers
var dns []net.IP
for _, dnsStr := range config.DNS {
dnsIP := net.ParseIP(dnsStr)
if dnsIP == nil {
return nil, fmt.Errorf("invalid DNS server: %s", dnsStr)
}
dns = append(dns, dnsIP)
}
// Apply default route
if err := scm.applyIPv4Route(link, ipv4Config); err != nil {
return fmt.Errorf("failed to apply IPv4 route: %w", err)
address := types.IPAddress{
Family: link.AfInet6,
Address: *ipNet,
Gateway: gateway,
Secondary: false,
Permanent: true,
}
scm.logger.Info().Msg("static IPv4 configuration applied successfully")
return nil
}
// ApplyIPv6Static applies static IPv6 configuration
func (scm *StaticConfigManager) ApplyIPv6Static(config *types.IPv6StaticConfig) error {
scm.logger.Info().Msg("applying static IPv6 configuration")
// Parse and validate configuration
ipv6Config, err := scm.parseIPv6Config(config)
if err != nil {
return fmt.Errorf("failed to parse IPv6 config: %w", err)
}
// Get interface
netlinkMgr := getNetlinkManager()
link, err := netlinkMgr.GetLinkByName(scm.ifaceName)
if err != nil {
return fmt.Errorf("failed to get interface: %w", err)
}
// Enable IPv6
if err := scm.enableIPv6(); err != nil {
return fmt.Errorf("failed to enable IPv6: %w", err)
}
// Ensure interface is up
if err := netlinkMgr.EnsureInterfaceUp(link); err != nil {
return fmt.Errorf("failed to bring interface up: %w", err)
}
// Apply IP address
if err := scm.applyIPv6Address(link, ipv6Config); err != nil {
return fmt.Errorf("failed to apply IPv6 address: %w", err)
}
// Apply default route
if err := scm.applyIPv6Route(link, ipv6Config); err != nil {
return fmt.Errorf("failed to apply IPv6 route: %w", err)
}
scm.logger.Info().Msg("static IPv6 configuration applied successfully")
return nil
return &types.ParsedIPConfig{
Addresses: []types.IPAddress{address},
Nameservers: dns,
Interface: scm.ifaceName,
}, nil
}
// DisableIPv4 disables IPv4 on the interface
@ -169,156 +177,6 @@ func (scm *StaticConfigManager) EnableIPv6LinkLocal() error {
return netlinkMgr.EnsureInterfaceUp(link)
}
// parseIPv4Config parses and validates IPv4 static configuration
func (scm *StaticConfigManager) parseIPv4Config(config *types.IPv4StaticConfig) (*parsedIPv4Config, error) {
if config == nil {
return nil, fmt.Errorf("config is nil")
}
// Parse IP address and netmask
ipNet, err := link.ParseIPv4Netmask(config.Address.String, config.Netmask.String)
if err != nil {
return nil, err
}
scm.logger.Info().Str("ipNet", ipNet.String()).Interface("ipc", config).Msg("parsed IPv4 address and netmask")
// Parse gateway
gateway := net.ParseIP(config.Gateway.String)
if gateway == nil {
return nil, fmt.Errorf("invalid gateway: %s", config.Gateway.String)
}
// Parse DNS servers
var dns []net.IP
for _, dnsStr := range config.DNS {
if err := link.ValidateIPAddress(dnsStr, false); err != nil {
return nil, fmt.Errorf("invalid DNS server: %w", err)
}
dns = append(dns, net.ParseIP(dnsStr))
}
return &parsedIPv4Config{
network: *ipNet,
gateway: gateway,
dns: dns,
}, nil
}
// parseIPv6Config parses and validates IPv6 static configuration
func (scm *StaticConfigManager) parseIPv6Config(config *types.IPv6StaticConfig) (*parsedIPv6Config, error) {
if config == nil {
return nil, fmt.Errorf("config is nil")
}
// Parse IP address and prefix
ipNet, err := link.ParseIPv6Prefix(config.Prefix.String, 64) // Default to /64 if not specified
if err != nil {
return nil, err
}
// Parse gateway
gateway := net.ParseIP(config.Gateway.String)
if gateway == nil {
return nil, fmt.Errorf("invalid gateway: %s", config.Gateway.String)
}
// Parse DNS servers
var dns []net.IP
for _, dnsStr := range config.DNS {
dnsIP := net.ParseIP(dnsStr)
if dnsIP == nil {
return nil, fmt.Errorf("invalid DNS server: %s", dnsStr)
}
dns = append(dns, dnsIP)
}
return &parsedIPv6Config{
prefix: *ipNet,
gateway: gateway,
dns: dns,
}, nil
}
// applyIPv4Address applies IPv4 address to interface
func (scm *StaticConfigManager) applyIPv4Address(iface *link.Link, config *parsedIPv4Config) error {
netlinkMgr := getNetlinkManager()
// Remove existing IPv4 addresses
if err := netlinkMgr.RemoveAllAddresses(iface, link.AfInet); err != nil {
return fmt.Errorf("failed to remove existing IPv4 addresses: %w", err)
}
// Add new address
addr := &netlink.Addr{
IPNet: &config.network,
}
if err := netlinkMgr.AddrAdd(iface, addr); err != nil {
return fmt.Errorf("failed to add IPv4 address: %w", err)
}
scm.logger.Info().Str("address", config.network.String()).Msg("IPv4 address applied")
return nil
}
// applyIPv6Address applies IPv6 address to interface
func (scm *StaticConfigManager) applyIPv6Address(iface *link.Link, config *parsedIPv6Config) error {
netlinkMgr := getNetlinkManager()
// Remove existing global IPv6 addresses
if err := netlinkMgr.RemoveNonLinkLocalIPv6Addresses(iface); err != nil {
return fmt.Errorf("failed to remove existing IPv6 addresses: %w", err)
}
// Add new address
addr := &netlink.Addr{
IPNet: &config.prefix,
}
if err := netlinkMgr.AddrAdd(iface, addr); err != nil {
return fmt.Errorf("failed to add IPv6 address: %w", err)
}
scm.logger.Info().Str("address", config.prefix.String()).Msg("IPv6 address applied")
return nil
}
// applyIPv4Route applies IPv4 default route
func (scm *StaticConfigManager) applyIPv4Route(iface *link.Link, config *parsedIPv4Config) error {
netlinkMgr := getNetlinkManager()
// Check if default route already exists
if netlinkMgr.HasDefaultRoute(link.AfInet) {
scm.logger.Info().Msg("IPv4 default route already exists")
return nil
}
// Add default route
if err := netlinkMgr.AddDefaultRoute(iface, config.gateway, link.AfInet); err != nil {
return fmt.Errorf("failed to add IPv4 default route: %w", err)
}
scm.logger.Info().Str("gateway", config.gateway.String()).Msg("IPv4 default route applied")
return nil
}
// applyIPv6Route applies IPv6 default route
func (scm *StaticConfigManager) applyIPv6Route(iface *link.Link, config *parsedIPv6Config) error {
netlinkMgr := getNetlinkManager()
// Check if default route already exists
if netlinkMgr.HasDefaultRoute(link.AfInet6) {
scm.logger.Info().Msg("IPv6 default route already exists")
return nil
}
// Add default route
if err := netlinkMgr.AddDefaultRoute(iface, config.gateway, link.AfInet6); err != nil {
return fmt.Errorf("failed to add IPv6 default route: %w", err)
}
scm.logger.Info().Str("gateway", config.gateway.String()).Msg("IPv6 default route applied")
return nil
}
// removeIPv4DefaultRoute removes IPv4 default route
func (scm *StaticConfigManager) removeIPv4DefaultRoute() error {
netlinkMgr := getNetlinkManager()
@ -330,17 +188,3 @@ func (scm *StaticConfigManager) enableIPv6() error {
netlinkMgr := getNetlinkManager()
return netlinkMgr.EnableIPv6(scm.ifaceName)
}
// parsedIPv4Config represents parsed IPv4 configuration
type parsedIPv4Config struct {
network net.IPNet
gateway net.IP
dns []net.IP
}
// parsedIPv6Config represents parsed IPv6 configuration
type parsedIPv6Config struct {
prefix net.IPNet
gateway net.IP
dns []net.IP
}

View File

@ -25,6 +25,14 @@ export default function Ipv6NetworkCard({
{networkState?.ipv6_link_local}
</span>
</div>
<div className="flex flex-col justify-between">
<span className="text-sm text-slate-600 dark:text-slate-400">
Gateway
</span>
<span className="text-sm font-medium">
{networkState?.ipv6_gateway}
</span>
</div>
</div>
<div className="space-y-3 pt-2">

View File

@ -709,6 +709,7 @@ export interface NetworkState {
ipv6?: string;
ipv6_addresses?: IPv6Address[];
ipv6_link_local?: string;
ipv6_gateway?: string;
dhcp_lease?: DhcpLease;
setNetworkState: (state: NetworkState) => void;