mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-02-03 22:18:39 +00:00
parse InterfaceIPs for network delta, not just ifs block
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
@@ -11,6 +12,7 @@ import (
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -1309,44 +1311,68 @@ func (p *prog) monitorNetworkChanges(ctx context.Context) error {
|
||||
// Get map of valid interfaces
|
||||
validIfaces := validInterfacesMap()
|
||||
|
||||
// log the delta for debugging
|
||||
// Log the delta for debugging
|
||||
mainLog.Load().Warn().
|
||||
Interface("old_state", delta.Old).
|
||||
Interface("new_state", delta.New).
|
||||
Msg("Network change detected")
|
||||
|
||||
// Parse old and new interface states
|
||||
oldIfs := parseInterfaceState(delta.Old)
|
||||
newIfs := parseInterfaceState(delta.New)
|
||||
// Parse old and new interface states, and extract IPs as well.
|
||||
oldIfs, oldIPs := parseInterfaceState(delta.Old)
|
||||
newIfs, newIPs := parseInterfaceState(delta.New)
|
||||
|
||||
// Check for changes in valid interfaces
|
||||
changed := false
|
||||
var changedIface, changedIfaceState string
|
||||
activeInterfaceExists := false
|
||||
|
||||
// Iterate over valid interfaces.
|
||||
for ifaceName := range validIfaces {
|
||||
oldState, oldExists := oldIfs[strings.ToLower(ifaceName)]
|
||||
newState, newExists := newIfs[strings.ToLower(ifaceName)]
|
||||
lname := strings.ToLower(ifaceName)
|
||||
oldState, oldExists := oldIfs[lname]
|
||||
newState, newExists := newIfs[lname]
|
||||
|
||||
// Check if the interface appears active in the new state.
|
||||
if newState != "" && !strings.Contains(newState, "down") {
|
||||
activeInterfaceExists = true
|
||||
}
|
||||
|
||||
// Compare states directly
|
||||
if oldExists != newExists || oldState != newState {
|
||||
// Compare raw state strings...
|
||||
stateChanged := (oldExists != newExists || oldState != newState)
|
||||
|
||||
// If the interface is up, we need to reinitialize the OS resolver
|
||||
// ... and also compare the parsed IP slices.
|
||||
ipChanged := false
|
||||
oldIPSlice, okOld := oldIPs[lname]
|
||||
newIPSlice, okNew := newIPs[lname]
|
||||
if okOld && okNew {
|
||||
// Create copies and sort them so that order does not matter.
|
||||
sortedOld := append([]string(nil), oldIPSlice...)
|
||||
sortedNew := append([]string(nil), newIPSlice...)
|
||||
sort.Strings(sortedOld)
|
||||
sort.Strings(sortedNew)
|
||||
if !slices.Equal(sortedOld, sortedNew) {
|
||||
ipChanged = true
|
||||
}
|
||||
} else if okOld != okNew {
|
||||
ipChanged = true
|
||||
}
|
||||
|
||||
// If either the state string or the IPs have changed...
|
||||
if stateChanged || ipChanged {
|
||||
if newState != "" && !strings.Contains(newState, "down") {
|
||||
changed = true
|
||||
changedIface = ifaceName
|
||||
changedIfaceState = newState
|
||||
// Prefer newState if present; if not, generate one from the IP slice.
|
||||
if newState == "" && okNew {
|
||||
changedIfaceState = "[" + strings.Join(newIPSlice, " ") + "]"
|
||||
} else {
|
||||
changedIfaceState = newState
|
||||
}
|
||||
}
|
||||
|
||||
mainLog.Load().Warn().
|
||||
Str("interface", ifaceName).
|
||||
Str("old_state", oldState).
|
||||
Str("new_state", newState).
|
||||
Msg("Valid interface changed state")
|
||||
Msg("Valid interface changed state (IP change detected: " + strconv.FormatBool(ipChanged) + ")")
|
||||
break
|
||||
} else {
|
||||
mainLog.Load().Warn().
|
||||
@@ -1367,19 +1393,19 @@ func (p *prog) monitorNetworkChanges(ctx context.Context) error {
|
||||
return
|
||||
}
|
||||
|
||||
// Use the defaultRouteIP() result or fallback to the changed interface's IP from the delta.
|
||||
// Use the defaultRouteIP() result, or fall back to the changed interface's IPv4 from the new state.
|
||||
selfIP := defaultRouteIP()
|
||||
if selfIP == "" && changedIface != "" {
|
||||
selfIP = extractIPv4FromState(changedIfaceState)
|
||||
mainLog.Load().Info().Msgf("defaultRouteIP returned empty, using changed iface '%s' IP: %s", changedIface, selfIP)
|
||||
mainLog.Load().Info().Msgf("defaultRouteIP returned empty, using changed iface '%s' IPv4: %s", changedIface, selfIP)
|
||||
}
|
||||
|
||||
// Extract IPv6 from the changed interface state.
|
||||
// Extract IPv6 from the changed state.
|
||||
ipv6 := extractIPv6FromState(changedIfaceState)
|
||||
|
||||
if ip := net.ParseIP(selfIP); ip != nil {
|
||||
ctrld.SetDefaultLocalIPv4(ip)
|
||||
// if we have a new IP, set the client info to the new IP
|
||||
// If we have a new IP, update the client info.
|
||||
if !isMobile() && p.ciTable != nil {
|
||||
p.ciTable.SetSelfIP(selfIP)
|
||||
}
|
||||
@@ -1396,52 +1422,87 @@ func (p *prog) monitorNetworkChanges(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseInterfaceState parses the interface state string into a map of interface name -> state
|
||||
func parseInterfaceState(state *netmon.State) map[string]string {
|
||||
// parseInterfaceState parses the netmon state into two maps:
|
||||
// 1. stateMap: a mapping from interfaces (lowercase) to their original state string,
|
||||
// formatted in square brackets (e.g. "[192.168.1.200/24 fe80::69f6:e16e:8bdb:0000/64]").
|
||||
// 2. ipMap: a mapping from interfaces (lowercase) to a slice of IP addresses extracted from that state.
|
||||
//
|
||||
// It first attempts JSON parsing to pull out both the "Interface" and "InterfaceIPs" fields.
|
||||
// If JSON parsing fails, it falls back to the legacy parsing logic.
|
||||
func parseInterfaceState(state *netmon.State) (map[string]string, map[string][]string) {
|
||||
result := make(map[string]string) // Interface name -> state string.
|
||||
ipMap := make(map[string][]string) // Interface name -> slice of IP addresses.
|
||||
|
||||
if state == nil {
|
||||
return nil
|
||||
return result, ipMap
|
||||
}
|
||||
|
||||
result := make(map[string]string)
|
||||
|
||||
stateStr := state.String()
|
||||
|
||||
// Extract interface information
|
||||
ifsStart := strings.Index(stateStr, "ifs={")
|
||||
if ifsStart == -1 {
|
||||
return result
|
||||
// Attempt to parse the state string as JSON so we can extract both "Interface" and "InterfaceIPs".
|
||||
var raw map[string]json.RawMessage
|
||||
if err := json.Unmarshal([]byte(stateStr), &raw); err == nil {
|
||||
var interfaces map[string]interface{}
|
||||
var interfaceIPs map[string][]string
|
||||
|
||||
if v, ok := raw["Interface"]; ok {
|
||||
_ = json.Unmarshal(v, &interfaces)
|
||||
}
|
||||
if v, ok := raw["InterfaceIPs"]; ok {
|
||||
_ = json.Unmarshal(v, &interfaceIPs)
|
||||
}
|
||||
// For every interface in the "Interface" section, check for its IPs.
|
||||
for name := range interfaces {
|
||||
lowerName := strings.ToLower(name)
|
||||
if ips, ok := interfaceIPs[name]; ok && len(ips) > 0 {
|
||||
result[lowerName] = "[" + strings.Join(ips, " ") + "]"
|
||||
ipMap[lowerName] = ips
|
||||
} else {
|
||||
result[lowerName] = "[]"
|
||||
ipMap[lowerName] = []string{}
|
||||
}
|
||||
}
|
||||
return result, ipMap
|
||||
}
|
||||
|
||||
// Fallback: try parsing the legacy "ifs={...}" section from the state string.
|
||||
ifsStart := strings.Index(stateStr, "ifs={")
|
||||
if ifsStart == -1 {
|
||||
return result, ipMap
|
||||
}
|
||||
ifsStr := stateStr[ifsStart+5:]
|
||||
ifsEnd := strings.Index(ifsStr, "}")
|
||||
if ifsEnd == -1 {
|
||||
return result
|
||||
return result, ipMap
|
||||
}
|
||||
|
||||
// Get the content between ifs={ }
|
||||
ifsContent := strings.TrimSpace(ifsStr[:ifsEnd])
|
||||
|
||||
// Split on "] " to get each interface entry
|
||||
entries := strings.Split(ifsContent, "] ")
|
||||
|
||||
for _, entry := range entries {
|
||||
if entry == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Split on ":["
|
||||
parts := strings.Split(entry, ":[")
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
name := strings.TrimSpace(parts[0])
|
||||
state := "[" + strings.TrimSuffix(parts[1], "]") + "]"
|
||||
stateEntry := "[" + strings.TrimSuffix(parts[1], "]") + "]"
|
||||
lowerName := strings.ToLower(name)
|
||||
result[lowerName] = stateEntry
|
||||
|
||||
result[strings.ToLower(name)] = state
|
||||
// Attempt to extract IP addresses from stateEntry.
|
||||
ipList := []string{}
|
||||
trimmed := strings.Trim(stateEntry, "[]")
|
||||
fields := strings.Fields(trimmed)
|
||||
for _, f := range fields {
|
||||
// We assume the IP is the part before the "/", if present.
|
||||
candidate := strings.Split(f, "/")[0]
|
||||
if ip := net.ParseIP(candidate); ip != nil {
|
||||
ipList = append(ipList, candidate)
|
||||
}
|
||||
}
|
||||
ipMap[lowerName] = ipList
|
||||
}
|
||||
|
||||
return result
|
||||
return result, ipMap
|
||||
}
|
||||
|
||||
// extractIPv4FromState extracts an IPv4 address from an interface state string.
|
||||
|
||||
Reference in New Issue
Block a user