fix race condition in link manager

This commit is contained in:
Siyuan 2025-10-08 12:01:55 +00:00
parent 8310077af6
commit f128343187
6 changed files with 157 additions and 36 deletions

View File

@ -33,10 +33,8 @@ func Main() {
go runWatchdog() go runWatchdog()
go confirmCurrentSystem() go confirmCurrentSystem()
initNative(systemVersionLocal, appVersionLocal)
// initialize display
initDisplay() initDisplay()
initNative(systemVersionLocal, appVersionLocal)
http.DefaultClient.Timeout = 1 * time.Minute http.DefaultClient.Timeout = 1 * time.Minute

View File

@ -106,6 +106,9 @@ func NewInterfaceManager(ctx context.Context, ifaceName string, config *types.Ne
// Start starts managing the interface // Start starts managing the interface
func (im *InterfaceManager) Start() error { func (im *InterfaceManager) Start() error {
im.stateMu.Lock()
defer im.stateMu.Unlock()
im.logger.Info().Msg("starting interface manager") im.logger.Info().Msg("starting interface manager")
// Start monitoring interface state // Start monitoring interface state
@ -113,6 +116,22 @@ func (im *InterfaceManager) Start() error {
go im.monitorInterfaceState() go im.monitorInterfaceState()
nl := getNetlinkManager() nl := getNetlinkManager()
// Set the link state
linkState, err := nl.GetLinkByName(im.ifaceName)
if err != nil {
return fmt.Errorf("failed to get interface: %w", err)
}
im.linkState = linkState
// Bring the interface up
_, linkUpErr := nl.EnsureInterfaceUpWithTimeout(
im.ctx,
im.linkState,
30*time.Second,
)
// Set callback after the interface is up
nl.AddStateChangeCallback(im.ifaceName, link.StateChangeCallback{ nl.AddStateChangeCallback(im.ifaceName, link.StateChangeCallback{
Async: true, Async: true,
Func: func(link *link.Link) { Func: func(link *link.Link) {
@ -120,10 +139,14 @@ func (im *InterfaceManager) Start() error {
}, },
}) })
// Apply initial configuration if linkUpErr != nil {
if err := im.applyConfiguration(); err != nil { im.logger.Error().Err(linkUpErr).Msg("failed to bring interface up, continuing anyway")
im.logger.Error().Err(err).Msg("failed to apply initial configuration") } else {
return err // Apply initial configuration
if err := im.applyConfiguration(); err != nil {
im.logger.Error().Err(err).Msg("failed to apply initial configuration")
return err
}
} }
im.logger.Info().Msg("interface manager started") im.logger.Info().Msg("interface manager started")
@ -451,12 +474,18 @@ func (im *InterfaceManager) applyIPv6SLAAC() error {
} }
netlinkMgr := getNetlinkManager() netlinkMgr := getNetlinkManager()
// Ensure interface is up
if err := netlinkMgr.EnsureInterfaceUp(l); err != nil {
return fmt.Errorf("failed to bring interface up: %w", err)
}
if err := netlinkMgr.RemoveNonLinkLocalIPv6Addresses(l); err != nil { if err := netlinkMgr.RemoveNonLinkLocalIPv6Addresses(l); err != nil {
return fmt.Errorf("failed to remove non-link-local IPv6 addresses: %w", err) return fmt.Errorf("failed to remove non-link-local IPv6 addresses: %w", err)
} }
if err := im.SendRouterSolicitation(); err != nil { if err := im.SendRouterSolicitation(); err != nil {
return fmt.Errorf("failed to send router solicitation: %w", err) im.logger.Error().Err(err).Msg("failed to send router solicitation, continuing anyway")
} }
// Enable SLAAC // Enable SLAAC
@ -561,6 +590,10 @@ func (im *InterfaceManager) SendRouterSolicitation() error {
return fmt.Errorf("failed to get interface: %w", err) return fmt.Errorf("failed to get interface: %w", err)
} }
if l.Attrs().OperState != netlink.OperUp {
return fmt.Errorf("interface %s is not up", im.ifaceName)
}
iface := l.Interface() iface := l.Interface()
if iface == nil { if iface == nil {
return fmt.Errorf("failed to get net.Interface for %s", im.ifaceName) return fmt.Errorf("failed to get net.Interface for %s", im.ifaceName)
@ -572,7 +605,6 @@ func (im *InterfaceManager) SendRouterSolicitation() error {
} }
c, _, err := ndp.Listen(iface, ndp.LinkLocal) c, _, err := ndp.Listen(iface, ndp.LinkLocal)
defer c.Close()
if err != nil { if err != nil {
return fmt.Errorf("failed to create NDP listener on %s: %w", im.ifaceName, err) return fmt.Errorf("failed to create NDP listener on %s: %w", im.ifaceName, err)
} }
@ -585,10 +617,12 @@ func (im *InterfaceManager) SendRouterSolicitation() error {
targetAddr := netip.MustParseAddr("ff02::2") targetAddr := netip.MustParseAddr("ff02::2")
if err := c.WriteTo(m, nil, targetAddr); err != nil { if err := c.WriteTo(m, nil, targetAddr); err != nil {
c.Close()
return fmt.Errorf("failed to write to %s: %w", targetAddr.String(), err) return fmt.Errorf("failed to write to %s: %w", targetAddr.String(), err)
} }
im.logger.Info().Msg("router solicitation sent") im.logger.Info().Msg("router solicitation sent")
c.Close()
return nil return nil
} }

View File

@ -1,6 +1,8 @@
package jetdhcpc package jetdhcpc
import ( import (
"fmt"
"github.com/insomniacslk/dhcp/dhcpv4" "github.com/insomniacslk/dhcp/dhcpv4"
"github.com/insomniacslk/dhcp/dhcpv4/nclient4" "github.com/insomniacslk/dhcp/dhcpv4/nclient4"
"github.com/vishvananda/netlink" "github.com/vishvananda/netlink"
@ -46,7 +48,11 @@ func (c *Client) requestLease4(iface netlink.Link) (*Lease, error) {
return nil, err return nil, err
} }
l.Info().Msgf("DHCPv4 lease acquired: %s", lease.ACK.Summary()) if lease == nil || lease.ACK == nil {
return nil, fmt.Errorf("failed to acquire DHCPv4 lease")
}
summaryStructured(lease.ACK, &l).Info().Msgf("DHCPv4 lease acquired: %s", lease.ACK.String())
return fromNclient4Lease(lease, ifname), nil return fromNclient4Lease(lease, ifname), nil
} }

View File

@ -21,11 +21,35 @@ func (s dhcpLogger) Printf(format string, v ...interface{}) {
// PrintMessage prints a DHCP message in the short format via predefined Printfer // PrintMessage prints a DHCP message in the short format via predefined Printfer
func (s dhcpLogger) PrintMessage(prefix string, message *dhcpv4.DHCPv4) { func (s dhcpLogger) PrintMessage(prefix string, message *dhcpv4.DHCPv4) {
s.l.Info().Str("prefix", prefix).Str("message", message.String()).Msg("DHCP message") s.l.Info().Msgf("%s: %s", prefix, message.String())
}
func summaryStructured(d *dhcpv4.DHCPv4, l *zerolog.Logger) *zerolog.Logger {
logger := l.With().
Str("opCode", d.OpCode.String()).
Str("hwType", d.HWType.String()).
Int("hopCount", int(d.HopCount)).
Str("transactionID", d.TransactionID.String()).
Int("numSeconds", int(d.NumSeconds)).
Str("flagsString", d.FlagsToString()).
Int("flags", int(d.Flags)).
Str("clientIP", d.ClientIPAddr.String()).
Str("yourIP", d.YourIPAddr.String()).
Str("serverIP", d.ServerIPAddr.String()).
Str("gatewayIP", d.GatewayIPAddr.String()).
Str("clientMAC", d.ClientHWAddr.String()).
Str("serverHostname", d.ServerHostName).
Str("bootFileName", d.BootFileName).
Str("options", d.Options.Summary(nil)).
Logger()
return &logger
} }
func (c *Client) getDHCP4Logger(ifname string) nclient4.ClientOpt { func (c *Client) getDHCP4Logger(ifname string) nclient4.ClientOpt {
logger := c.l.With().Str("interface", ifname).Logger() logger := c.l.With().
Str("interface", ifname).
Str("source", "dhcp4").
Logger()
return nclient4.WithLogger(dhcpLogger{ return nclient4.WithLogger(dhcpLogger{
l: &logger, l: &logger,

View File

@ -163,22 +163,40 @@ func (nm *NetlinkManager) EnsureInterfaceUpWithTimeout(ctx context.Context, ifac
} }
state := link.Attrs().OperState state := link.Attrs().OperState
l = l.With().
Int("attempt", attempt).
Dur("duration", time.Since(start)).
Str("state", state.String()).
Logger()
if state == netlink.OperUp || state == netlink.OperUnknown { if state == netlink.OperUp || state == netlink.OperUnknown {
if attempt > 0 {
l.Info().Int("attempt", attempt-1).Msg("interface is up")
}
return link, nil return link, nil
} }
l.Info().Str("state", state.String()).Msg("bringing up interface") l.Info().Msg("bringing up interface")
// bring up the interface
if err = nm.LinkSetUp(link); err != nil { if err = nm.LinkSetUp(link); err != nil {
l.Error().Err(err).Msg("interface can't make it up") l.Error().Err(err).Msg("interface can't make it up")
} }
l = l.With().Int("attempt", attempt).Dur("duration", time.Since(start)).Logger() // refresh the link attributes
if err = link.Refresh(); err != nil {
if attempt > 0 { l.Error().Err(err).Msg("failed to refresh link attributes")
l.Info().Msg("interface up")
} }
// check the state again
state = link.Attrs().OperState
l = l.With().Str("new_state", state.String()).Logger()
if state == netlink.OperUp {
l.Info().Msg("interface is up")
return link, nil
}
l.Warn().Msg("interface is still down, retrying")
select { select {
case <-time.After(500 * time.Millisecond): case <-time.After(500 * time.Millisecond):
attempt++ attempt++
@ -381,24 +399,29 @@ func (nm *NetlinkManager) ReconcileLink(link *Link, expected []*types.IPAddress)
} }
ipCidr := addr.Address.IP.String() + "/" + addr.Address.Mask.String() ipCidr := addr.Address.IP.String() + "/" + addr.Address.Mask.String()
ipNet := &net.IPNet{
IP: addr.Address.IP,
Mask: addr.Address.Mask,
}
l := nm.logger.With().Str("address", ipNet.String()).Logger()
if ok := existingAddrs[ipCidr]; !ok { if ok := existingAddrs[ipCidr]; !ok {
ipNet := &net.IPNet{ l.Trace().Msg("adding address")
IP: addr.Address.IP,
Mask: addr.Address.Mask,
}
if err := nm.AddrAdd(link, &netlink.Addr{IPNet: ipNet}); err != nil { if err := nm.AddrAdd(link, &netlink.Addr{IPNet: ipNet}); err != nil {
return fmt.Errorf("failed to add address %s: %w", ipCidr, err) return fmt.Errorf("failed to add address %s: %w", ipCidr, err)
} }
nm.logger.Info().Str("address", ipCidr).Msg("added address") l.Info().Msg("address added")
} }
if addr.Gateway != nil { if addr.Gateway != nil {
nm.logger.Trace().Str("address", ipCidr).Str("gateway", addr.Gateway.String()).Msg("adding default route for address") 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 { if err := nm.AddDefaultRoute(link, addr.Gateway, family); err != nil {
return fmt.Errorf("failed to add default route for address %s: %w", ipCidr, err) return fmt.Errorf("failed to add default route for address %s: %w", ipCidr, err)
} }
gl.Info().Msg("default route added")
} }
} }

View File

@ -35,11 +35,14 @@ var (
// Link is a wrapper around netlink.Link // Link is a wrapper around netlink.Link
type Link struct { type Link struct {
netlink.Link netlink.Link
mu sync.Mutex
} }
// Refresh refreshes the link // All lock actions should be done in external functions
func (l *Link) Refresh() error { // and the internal functions should not be called directly
linkName := l.Link.Attrs().Name
func (l *Link) refresh() error {
linkName := l.ifName()
link, err := netlink.LinkByName(linkName) link, err := netlink.LinkByName(linkName)
if err != nil { if err != nil {
return err return err
@ -51,13 +54,44 @@ func (l *Link) Refresh() error {
return nil return nil
} }
func (l *Link) attrs() *netlink.LinkAttrs {
return l.Link.Attrs()
}
func (l *Link) ifName() string {
attrs := l.attrs()
if attrs.Name == "" {
return ""
}
return attrs.Name
}
// Refresh refreshes the link
func (l *Link) Refresh() error {
l.mu.Lock()
defer l.mu.Unlock()
return l.refresh()
}
// Attrs returns the attributes of the link
func (l *Link) Attrs() *netlink.LinkAttrs {
l.mu.Lock()
defer l.mu.Unlock()
return l.attrs()
}
// Interface returns the interface of the link // Interface returns the interface of the link
func (l *Link) Interface() *net.Interface { func (l *Link) Interface() *net.Interface {
attrs := l.Attrs() l.mu.Lock()
if attrs.Name == "" { defer l.mu.Unlock()
ifname := l.ifName()
if ifname == "" {
return nil return nil
} }
iface, err := net.InterfaceByName(attrs.Name) iface, err := net.InterfaceByName(ifname)
if err != nil { if err != nil {
return nil return nil
} }
@ -66,20 +100,22 @@ func (l *Link) Interface() *net.Interface {
// HardwareAddr returns the hardware address of the link // HardwareAddr returns the hardware address of the link
func (l *Link) HardwareAddr() net.HardwareAddr { func (l *Link) HardwareAddr() net.HardwareAddr {
attrs := l.Attrs() l.mu.Lock()
defer l.mu.Unlock()
attrs := l.attrs()
if attrs.HardwareAddr == nil { if attrs.HardwareAddr == nil {
return nil return nil
} }
return attrs.HardwareAddr return attrs.HardwareAddr
} }
// Attrs returns the attributes of the link // AddrList returns the addresses of the link
func (l *Link) Attrs() *netlink.LinkAttrs {
return l.Link.Attrs()
}
func (l *Link) AddrList(family int) ([]netlink.Addr, error) { func (l *Link) AddrList(family int) ([]netlink.Addr, error) {
return netlink.AddrList(l, family) l.mu.Lock()
defer l.mu.Unlock()
return netlink.AddrList(l.Link, family)
} }
func (l *Link) IsSame(other *Link) bool { func (l *Link) IsSame(other *Link) bool {