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"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/guregu/null/v6"
|
"github.com/guregu/null/v6"
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IPAddress represents a network interface address
|
// IPAddress represents a network interface address
|
||||||
|
|
@ -19,14 +21,50 @@ type IPAddress struct {
|
||||||
Permanent 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
|
// IPv6Address represents an IPv6 address with lifetime information
|
||||||
type IPv6Address struct {
|
type IPv6Address struct {
|
||||||
Address net.IP `json:"address"`
|
Address net.IP `json:"address"`
|
||||||
Prefix net.IPNet `json:"prefix"`
|
Prefix net.IPNet `json:"prefix"`
|
||||||
ValidLifetime *time.Time `json:"valid_lifetime"`
|
ValidLifetime *time.Time `json:"valid_lifetime"`
|
||||||
PreferredLifetime *time.Time `json:"preferred_lifetime"`
|
PreferredLifetime *time.Time `json:"preferred_lifetime"`
|
||||||
valid_lft int `json:"valid_lft"`
|
|
||||||
prefered_lft int `json:"prefered_lft"`
|
|
||||||
Scope int `json:"scope"`
|
Scope int `json:"scope"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -120,6 +158,7 @@ type InterfaceState struct {
|
||||||
IPv4Address string `json:"ipv4_address,omitempty"`
|
IPv4Address string `json:"ipv4_address,omitempty"`
|
||||||
IPv6Address string `json:"ipv6_address,omitempty"`
|
IPv6Address string `json:"ipv6_address,omitempty"`
|
||||||
IPv6LinkLocal string `json:"ipv6_link_local,omitempty"`
|
IPv6LinkLocal string `json:"ipv6_link_local,omitempty"`
|
||||||
|
IPv6Gateway string `json:"ipv6_gateway,omitempty"`
|
||||||
IPv4Addresses []string `json:"ipv4_addresses,omitempty"`
|
IPv4Addresses []string `json:"ipv4_addresses,omitempty"`
|
||||||
IPv6Addresses []IPv6Address `json:"ipv6_addresses,omitempty"`
|
IPv6Addresses []IPv6Address `json:"ipv6_addresses,omitempty"`
|
||||||
NTPServers []net.IP `json:"ntp_servers,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")
|
return fmt.Errorf("IPv4 static configuration is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
im.logger.Info().Msg("stopping DHCP")
|
||||||
|
|
||||||
// Disable DHCP
|
// Disable DHCP
|
||||||
if im.dhcpClient != nil {
|
if im.dhcpClient != nil {
|
||||||
im.dhcpClient.SetIPv4(false)
|
im.dhcpClient.SetIPv4(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply static configuration
|
im.logger.Info().Interface("config", im.config.IPv4Static).Msg("applying IPv4 static configuration")
|
||||||
return im.staticConfig.ApplyIPv4Static(im.config.IPv4Static)
|
|
||||||
|
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
|
// applyIPv4DHCP applies DHCP IPv4 configuration
|
||||||
|
|
@ -440,13 +450,20 @@ func (im *InterfaceManager) applyIPv6Static() error {
|
||||||
return fmt.Errorf("IPv6 static configuration is nil")
|
return fmt.Errorf("IPv6 static configuration is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
im.logger.Info().Msg("stopping DHCPv6")
|
||||||
// Disable DHCPv6
|
// Disable DHCPv6
|
||||||
if im.dhcpClient != nil {
|
if im.dhcpClient != nil {
|
||||||
im.dhcpClient.SetIPv6(false)
|
im.dhcpClient.SetIPv6(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply static configuration
|
// 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
|
// applyIPv6DHCP applies DHCPv6 configuration
|
||||||
|
|
@ -696,7 +713,7 @@ func (im *InterfaceManager) updateStateFromDHCPLease(lease *types.DHCPLease) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReconcileLinkAddrs reconciles the link addresses
|
// 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()
|
nl := getNetlinkManager()
|
||||||
link, err := im.link()
|
link, err := im.link()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -705,7 +722,7 @@ func (im *InterfaceManager) ReconcileLinkAddrs(addrs []*types.IPAddress) error {
|
||||||
if link == nil {
|
if link == nil {
|
||||||
return fmt.Errorf("failed to get interface: %w", err)
|
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
|
// applyDHCPLease applies DHCP lease configuration using ReconcileLinkAddrs
|
||||||
|
|
@ -714,7 +731,7 @@ func (im *InterfaceManager) applyDHCPLease(lease *types.DHCPLease) error {
|
||||||
ipv4Config := im.convertDHCPLeaseToIPv4Config(lease)
|
ipv4Config := im.convertDHCPLeaseToIPv4Config(lease)
|
||||||
|
|
||||||
// Apply the configuration using ReconcileLinkAddrs
|
// 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
|
// convertDHCPLeaseToIPv4Config converts a DHCP lease to IPv4Config
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,8 @@ func (im *InterfaceManager) updateInterfaceState() error {
|
||||||
|
|
||||||
// updateIPAddresses updates the IP addresses in the state
|
// updateIPAddresses updates the IP addresses in the state
|
||||||
func (im *InterfaceManager) updateInterfaceStateAddresses(nl *link.Link) (bool, error) {
|
func (im *InterfaceManager) updateInterfaceStateAddresses(nl *link.Link) (bool, error) {
|
||||||
|
mgr := getNetlinkManager()
|
||||||
|
|
||||||
addrs, err := nl.AddrList(link.AfUnspec)
|
addrs, err := nl.AddrList(link.AfUnspec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("failed to get addresses: %w", err)
|
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
|
ipv6Addresses []types.IPv6Address
|
||||||
ipv4Addr, ipv6Addr string
|
ipv4Addr, ipv6Addr string
|
||||||
ipv6LinkLocal string
|
ipv6LinkLocal string
|
||||||
|
ipv6Gateway string
|
||||||
ipv4Ready, ipv6Ready = false, false
|
ipv4Ready, ipv6Ready = false, false
|
||||||
stateChanged = false
|
stateChanged = false
|
||||||
)
|
)
|
||||||
|
|
||||||
|
routes, _ := mgr.ListDefaultRoutes(link.AfInet6)
|
||||||
|
if len(routes) > 0 {
|
||||||
|
ipv6Gateway = routes[0].Gw.String()
|
||||||
|
}
|
||||||
|
|
||||||
for _, addr := range addrs {
|
for _, addr := range addrs {
|
||||||
im.logger.Debug().
|
|
||||||
IPAddr("address", addr.IP).
|
|
||||||
Msg("checking address")
|
|
||||||
if addr.IP.To4() != nil {
|
if addr.IP.To4() != nil {
|
||||||
// IPv4 address
|
// IPv4 address
|
||||||
ipv4Addresses = append(ipv4Addresses, addr.IPNet.String())
|
ipv4Addresses = append(ipv4Addresses, addr.IPNet.String())
|
||||||
|
|
@ -138,6 +143,11 @@ func (im *InterfaceManager) updateInterfaceStateAddresses(nl *link.Link) (bool,
|
||||||
stateChanged = true
|
stateChanged = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if im.state.IPv6Gateway != ipv6Gateway {
|
||||||
|
im.state.IPv6Gateway = ipv6Gateway
|
||||||
|
stateChanged = true
|
||||||
|
}
|
||||||
|
|
||||||
if im.state.IPv4Ready != ipv4Ready {
|
if im.state.IPv4Ready != ipv4Ready {
|
||||||
im.state.IPv4Ready = ipv4Ready
|
im.state.IPv4Ready = ipv4Ready
|
||||||
stateChanged = true
|
stateChanged = true
|
||||||
|
|
|
||||||
|
|
@ -279,7 +279,7 @@ func (c *Client) handleLeaseChange(lease *Lease) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// clear all current jobs with the same tags
|
// clear all current jobs with the same tags
|
||||||
c.scheduler.RemoveByTags(version)
|
// c.scheduler.RemoveByTags(version)
|
||||||
|
|
||||||
// add scheduler job to renew the lease
|
// add scheduler job to renew the lease
|
||||||
if lease.RenewalTime > 0 {
|
if lease.RenewalTime > 0 {
|
||||||
|
|
@ -357,6 +357,9 @@ func (c *Client) SetIPv4(ipv4 bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) SetIPv6(ipv6 bool) {
|
func (c *Client) SetIPv6(ipv6 bool) {
|
||||||
|
c.cfgMu.Lock()
|
||||||
|
defer c.cfgMu.Unlock()
|
||||||
|
|
||||||
c.cfg.IPv6 = ipv6
|
c.cfg.IPv6 = ipv6
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -302,26 +302,28 @@ func (nm *NetlinkManager) RouteReplace(route *netlink.Route) error {
|
||||||
return netlink.RouteReplace(route)
|
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
|
// HasDefaultRoute checks if a default route exists for the given family
|
||||||
func (nm *NetlinkManager) HasDefaultRoute(family int) bool {
|
func (nm *NetlinkManager) HasDefaultRoute(family int) bool {
|
||||||
routes, err := netlink.RouteList(nil, family)
|
routes, err := nm.ListDefaultRoutes(family)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
return len(routes) > 0
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddDefaultRoute adds a default route
|
// AddDefaultRoute adds a default route
|
||||||
|
|
@ -370,59 +372,152 @@ func (nm *NetlinkManager) RemoveDefaultRoute(family int) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReconcileLink reconciles the addresses and routes of a link
|
func (nm *NetlinkManager) reconcileDefaultRoute(link *Link, expected map[string]net.IP, family int) error {
|
||||||
func (nm *NetlinkManager) ReconcileLink(link *Link, expected []*types.IPAddress) error {
|
linkIndex := link.Attrs().Index
|
||||||
expectedAddrs := make(map[string]bool)
|
|
||||||
existingAddrs := make(map[string]bool)
|
|
||||||
|
|
||||||
for _, addr := range expected {
|
added := 0
|
||||||
ipCidr := addr.Address.IP.String() + "/" + addr.Address.Mask.String()
|
toRemove := make([]*netlink.Route, 0)
|
||||||
expectedAddrs[ipCidr] = true
|
|
||||||
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get addresses: %w", err)
|
return fmt.Errorf("failed to get addresses: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check existing addresses
|
||||||
for _, addr := range addrs {
|
for _, addr := range addrs {
|
||||||
ipCidr := addr.IP.String() + "/" + addr.IPNet.Mask.String()
|
// skip the link-local address
|
||||||
existingAddrs[ipCidr] = true
|
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 {
|
// add remaining expected addresses
|
||||||
family := AfUnspec
|
for _, addr := range expectedAddrs {
|
||||||
if addr.Address.IP.To4() != nil {
|
toAdd = append(toAdd, addr)
|
||||||
family = AfInet
|
}
|
||||||
} else if addr.Address.IP.To16() != nil {
|
|
||||||
family = AfInet6
|
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()
|
for _, addr := range toAdd {
|
||||||
ipNet := &net.IPNet{
|
netlinkAddr := addr.NetlinkAddr()
|
||||||
IP: addr.Address.IP,
|
if err := nm.AddrAdd(link, &netlinkAddr); err != nil {
|
||||||
Mask: addr.Address.Mask,
|
nm.logger.Warn().Err(err).Str("address", addr.Address.String()).Msg("failed to add address")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
l := nm.logger.With().Str("address", ipNet.String()).Logger()
|
for _, netlinkAddr := range toRemove {
|
||||||
if ok := existingAddrs[ipCidr]; !ok {
|
if err := nm.AddrDel(link, netlinkAddr); err != nil {
|
||||||
l.Trace().Msg("adding address")
|
nm.logger.Warn().Err(err).Str("address", netlinkAddr.IP.String()).Msg("failed to remove 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")
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if addr.Gateway != nil {
|
actualToAdd := len(toAdd) - len(toUpdate)
|
||||||
gl := l.With().Str("gateway", addr.Gateway.String()).Logger()
|
if len(toAdd) > 0 || len(toUpdate) > 0 || len(toRemove) > 0 {
|
||||||
gl.Trace().Msg("adding default route")
|
nm.logger.Info().
|
||||||
if err := nm.AddDefaultRoute(link, addr.Gateway, family); err != nil {
|
Int("added", actualToAdd).
|
||||||
return fmt.Errorf("failed to add default route for address %s: %w", ipCidr, err)
|
Int("updated", len(toUpdate)).
|
||||||
}
|
Int("removed", len(toRemove)).
|
||||||
gl.Info().Msg("default route added")
|
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
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,3 @@ type IPv4Address struct {
|
||||||
Secondary bool
|
Secondary bool
|
||||||
Permanent 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/internal/network/types"
|
||||||
"github.com/jetkvm/kvm/pkg/nmlite/link"
|
"github.com/jetkvm/kvm/pkg/nmlite/link"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/vishvananda/netlink"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// StaticConfigManager manages static network configuration
|
// StaticConfigManager manages static network configuration
|
||||||
|
|
@ -32,81 +31,90 @@ func NewStaticConfigManager(ifaceName string, logger *zerolog.Logger) (*StaticCo
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplyIPv4Static applies static IPv4 configuration
|
// ToIPv4Static applies static IPv4 configuration
|
||||||
func (scm *StaticConfigManager) ApplyIPv4Static(config *types.IPv4StaticConfig) error {
|
func (scm *StaticConfigManager) ToIPv4Static(config *types.IPv4StaticConfig) (*types.ParsedIPConfig, error) {
|
||||||
scm.logger.Info().Msg("applying static IPv4 configuration")
|
if config == nil {
|
||||||
|
return nil, fmt.Errorf("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
// Parse and validate configuration
|
// Parse IP address and netmask
|
||||||
ipv4Config, err := scm.parseIPv4Config(config)
|
ipNet, err := link.ParseIPv4Netmask(config.Address.String, config.Netmask.String)
|
||||||
if err != nil {
|
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
|
// Parse DNS servers
|
||||||
netlinkMgr := getNetlinkManager()
|
var dns []net.IP
|
||||||
link, err := netlinkMgr.GetLinkByName(scm.ifaceName)
|
for _, dnsStr := range config.DNS {
|
||||||
if err != nil {
|
if err := link.ValidateIPAddress(dnsStr, false); err != nil {
|
||||||
return fmt.Errorf("failed to get interface: %w", err)
|
return nil, fmt.Errorf("invalid DNS server: %w", err)
|
||||||
|
}
|
||||||
|
dns = append(dns, net.ParseIP(dnsStr))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure interface is up
|
address := types.IPAddress{
|
||||||
if err := netlinkMgr.EnsureInterfaceUp(link); err != nil {
|
Family: link.AfInet,
|
||||||
return fmt.Errorf("failed to bring interface up: %w", err)
|
Address: *ipNet,
|
||||||
|
Gateway: gateway,
|
||||||
|
Secondary: false,
|
||||||
|
Permanent: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply IP address
|
return &types.ParsedIPConfig{
|
||||||
if err := scm.applyIPv4Address(link, ipv4Config); err != nil {
|
Addresses: []types.IPAddress{address},
|
||||||
return fmt.Errorf("failed to apply IPv4 address: %w", err)
|
Nameservers: dns,
|
||||||
}
|
Interface: scm.ifaceName,
|
||||||
|
}, nil
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplyIPv6Static applies static IPv6 configuration
|
// ToIPv6Static applies static IPv6 configuration
|
||||||
func (scm *StaticConfigManager) ApplyIPv6Static(config *types.IPv6StaticConfig) error {
|
func (scm *StaticConfigManager) ToIPv6Static(config *types.IPv6StaticConfig) (*types.ParsedIPConfig, error) {
|
||||||
scm.logger.Info().Msg("applying static IPv6 configuration")
|
if config == nil {
|
||||||
|
return nil, fmt.Errorf("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
// Parse and validate configuration
|
// Parse IP address and prefix
|
||||||
ipv6Config, err := scm.parseIPv6Config(config)
|
ipNet, err := link.ParseIPv6Prefix(config.Prefix.String, 64) // Default to /64 if not specified
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to parse IPv6 config: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get interface
|
// Parse gateway
|
||||||
netlinkMgr := getNetlinkManager()
|
gateway := net.ParseIP(config.Gateway.String)
|
||||||
link, err := netlinkMgr.GetLinkByName(scm.ifaceName)
|
if gateway == nil {
|
||||||
if err != nil {
|
return nil, fmt.Errorf("invalid gateway: %s", config.Gateway.String)
|
||||||
return fmt.Errorf("failed to get interface: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable IPv6
|
// Parse DNS servers
|
||||||
if err := scm.enableIPv6(); err != nil {
|
var dns []net.IP
|
||||||
return fmt.Errorf("failed to enable IPv6: %w", err)
|
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
|
address := types.IPAddress{
|
||||||
if err := netlinkMgr.EnsureInterfaceUp(link); err != nil {
|
Family: link.AfInet6,
|
||||||
return fmt.Errorf("failed to bring interface up: %w", err)
|
Address: *ipNet,
|
||||||
|
Gateway: gateway,
|
||||||
|
Secondary: false,
|
||||||
|
Permanent: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply IP address
|
return &types.ParsedIPConfig{
|
||||||
if err := scm.applyIPv6Address(link, ipv6Config); err != nil {
|
Addresses: []types.IPAddress{address},
|
||||||
return fmt.Errorf("failed to apply IPv6 address: %w", err)
|
Nameservers: dns,
|
||||||
}
|
Interface: scm.ifaceName,
|
||||||
|
}, nil
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DisableIPv4 disables IPv4 on the interface
|
// DisableIPv4 disables IPv4 on the interface
|
||||||
|
|
@ -169,156 +177,6 @@ func (scm *StaticConfigManager) EnableIPv6LinkLocal() error {
|
||||||
return netlinkMgr.EnsureInterfaceUp(link)
|
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
|
// removeIPv4DefaultRoute removes IPv4 default route
|
||||||
func (scm *StaticConfigManager) removeIPv4DefaultRoute() error {
|
func (scm *StaticConfigManager) removeIPv4DefaultRoute() error {
|
||||||
netlinkMgr := getNetlinkManager()
|
netlinkMgr := getNetlinkManager()
|
||||||
|
|
@ -330,17 +188,3 @@ func (scm *StaticConfigManager) enableIPv6() error {
|
||||||
netlinkMgr := getNetlinkManager()
|
netlinkMgr := getNetlinkManager()
|
||||||
return netlinkMgr.EnableIPv6(scm.ifaceName)
|
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}
|
{networkState?.ipv6_link_local}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<div className="space-y-3 pt-2">
|
<div className="space-y-3 pt-2">
|
||||||
|
|
|
||||||
|
|
@ -709,6 +709,7 @@ export interface NetworkState {
|
||||||
ipv6?: string;
|
ipv6?: string;
|
||||||
ipv6_addresses?: IPv6Address[];
|
ipv6_addresses?: IPv6Address[];
|
||||||
ipv6_link_local?: string;
|
ipv6_link_local?: string;
|
||||||
|
ipv6_gateway?: string;
|
||||||
dhcp_lease?: DhcpLease;
|
dhcp_lease?: DhcpLease;
|
||||||
|
|
||||||
setNetworkState: (state: NetworkState) => void;
|
setNetworkState: (state: NetworkState) => void;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue