mirror of https://github.com/jetkvm/kvm.git
use reconcile instead of updating addresses and routes individually
This commit is contained in:
parent
ad0b86c8a6
commit
97844a8caf
|
|
@ -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"`
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
expectedAddr, ok := expectedAddrs[addr.IPNet.String()]
|
||||
if !ok {
|
||||
toRemove = append(toRemove, &addr)
|
||||
continue
|
||||
}
|
||||
|
||||
// if it's not fully equal, we need to update it
|
||||
if !expectedAddr.Compare(addr) {
|
||||
toUpdate = append(toUpdate, expectedAddr)
|
||||
continue
|
||||
}
|
||||
|
||||
// remove it from expected addresses
|
||||
delete(expectedAddrs, addr.IPNet.String())
|
||||
}
|
||||
|
||||
for _, addr := range expected {
|
||||
family := AfUnspec
|
||||
if addr.Address.IP.To4() != nil {
|
||||
family = AfInet
|
||||
} else if addr.Address.IP.To16() != nil {
|
||||
family = AfInet6
|
||||
// add remaining expected addresses
|
||||
for _, addr := range expectedAddrs {
|
||||
toAdd = append(toAdd, addr)
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
// we'll add it again later
|
||||
toAdd = append(toAdd, addr)
|
||||
}
|
||||
|
||||
ipCidr := addr.Address.IP.String() + "/" + addr.Address.Mask.String()
|
||||
ipNet := &net.IPNet{
|
||||
IP: addr.Address.IP,
|
||||
Mask: addr.Address.Mask,
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
l.Info().Msg("address added")
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
gl.Info().Msg("default route added")
|
||||
}
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get interface: %w", err)
|
||||
// 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))
|
||||
}
|
||||
|
||||
// Ensure interface is up
|
||||
if err := netlinkMgr.EnsureInterfaceUp(link); err != nil {
|
||||
return fmt.Errorf("failed to bring interface up: %w", err)
|
||||
address := types.IPAddress{
|
||||
Family: link.AfInet,
|
||||
Address: *ipNet,
|
||||
Gateway: gateway,
|
||||
Secondary: false,
|
||||
Permanent: true,
|
||||
}
|
||||
|
||||
// Apply IP address
|
||||
if err := scm.applyIPv4Address(link, ipv4Config); err != nil {
|
||||
return fmt.Errorf("failed to apply IPv4 address: %w", err)
|
||||
}
|
||||
|
||||
// Apply default route
|
||||
if err := scm.applyIPv4Route(link, ipv4Config); err != nil {
|
||||
return fmt.Errorf("failed to apply IPv4 route: %w", err)
|
||||
}
|
||||
|
||||
scm.logger.Info().Msg("static IPv4 configuration applied successfully")
|
||||
return nil
|
||||
return &types.ParsedIPConfig{
|
||||
Addresses: []types.IPAddress{address},
|
||||
Nameservers: dns,
|
||||
Interface: scm.ifaceName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ApplyIPv6Static applies static IPv6 configuration
|
||||
func (scm *StaticConfigManager) ApplyIPv6Static(config *types.IPv6StaticConfig) error {
|
||||
scm.logger.Info().Msg("applying static IPv6 configuration")
|
||||
// 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 and validate configuration
|
||||
ipv6Config, err := scm.parseIPv6Config(config)
|
||||
// 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 parse IPv6 config: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get interface
|
||||
netlinkMgr := getNetlinkManager()
|
||||
link, err := netlinkMgr.GetLinkByName(scm.ifaceName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get interface: %w", err)
|
||||
// Parse gateway
|
||||
gateway := net.ParseIP(config.Gateway.String)
|
||||
if gateway == nil {
|
||||
return nil, fmt.Errorf("invalid gateway: %s", config.Gateway.String)
|
||||
}
|
||||
|
||||
// Enable IPv6
|
||||
if err := scm.enableIPv6(); err != nil {
|
||||
return fmt.Errorf("failed to enable IPv6: %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)
|
||||
}
|
||||
|
||||
// Ensure interface is up
|
||||
if err := netlinkMgr.EnsureInterfaceUp(link); err != nil {
|
||||
return fmt.Errorf("failed to bring interface up: %w", err)
|
||||
address := types.IPAddress{
|
||||
Family: link.AfInet6,
|
||||
Address: *ipNet,
|
||||
Gateway: gateway,
|
||||
Secondary: false,
|
||||
Permanent: true,
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue