Detect interfaces per platform; add macOS and single-interface support (closes #2)
check / check (push) Failing after 1s
check / check (push) Failing after 1s
Linux runs exactly as before when both bridge interfaces exist. When they do not, the lone default-route interface is monitored, and a single interface now draws a single UI pane instead of an empty second one. macOS is newly supported: a running VPN tunnel (utun) is monitored as the primary pane alongside the physical default-route interface, or the physical interface alone when no VPN is up. Detection lives in internal/netdetect: interface/route data types, pure selection logic keyed on OS name, and route parsers, all unit-tested on Linux for both platforms. Only the real route query and the per-platform TCP dial binding (source address on Linux, IP_BOUND_IF on macOS) are build-tagged. Ping argument construction is a pure, OS-keyed function. NewMonitor now takes a list of interfaces. Model: opus-4-8
This commit is contained in:
@@ -1,5 +1,3 @@
|
||||
//go:build linux
|
||||
|
||||
package cli
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
+52
-10
@@ -1,5 +1,3 @@
|
||||
//go:build linux
|
||||
|
||||
// Package cli wires command-line flags to the network monitor and runs it.
|
||||
package cli
|
||||
|
||||
@@ -7,12 +5,14 @@ import (
|
||||
"context"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"syscall"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"git.eeqj.de/sneak/rtnetmon/internal/monitor"
|
||||
"git.eeqj.de/sneak/rtnetmon/internal/netdetect"
|
||||
)
|
||||
|
||||
// config holds the application configuration.
|
||||
@@ -59,8 +59,8 @@ func newRootCmd() *cobra.Command {
|
||||
Short: "Real-time network monitoring dashboard",
|
||||
Long: `rtnetmon is a dual-interface network monitoring dashboard that provides
|
||||
real-time visibility into network health, packet loss, and latency.`,
|
||||
RunE: func(_ *cobra.Command, _ []string) error {
|
||||
return runMonitor(cfg)
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
return runMonitor(cmd, cfg)
|
||||
},
|
||||
}
|
||||
registerFlags(cmd, cfg)
|
||||
@@ -86,14 +86,17 @@ func registerFlags(cmd *cobra.Command, cfg *config) {
|
||||
}
|
||||
}
|
||||
|
||||
// runMonitor constructs and runs the monitor from cfg.
|
||||
func runMonitor(cfg *config) error {
|
||||
// runMonitor detects the interfaces to monitor, then constructs and runs the
|
||||
// monitor from cfg.
|
||||
func runMonitor(cmd *cobra.Command, cfg *config) error {
|
||||
monitor.Logf(cfg.LogFile, "Starting rtnetmon")
|
||||
monitor.Logf(cfg.LogFile, "Monitoring interfaces %s and %s",
|
||||
cfg.IfaceA, cfg.IfaceB)
|
||||
|
||||
mon := monitor.NewMonitor(cfg.IfaceA, cfg.LabelA, cfg.IfaceB, cfg.LabelB,
|
||||
cfg.LogFile)
|
||||
specs, err := detectInterfaces(cmd, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mon := monitor.NewMonitor(specs, cfg.LogFile)
|
||||
|
||||
for _, host := range cfg.Hosts {
|
||||
mon.AddReachabilityHost(host)
|
||||
@@ -122,6 +125,45 @@ func runMonitor(cfg *config) error {
|
||||
return mon.Run(ctx)
|
||||
}
|
||||
|
||||
// detectInterfaces enumerates the host, reads its default routes, and applies
|
||||
// the platform selection rules to decide which interfaces to monitor.
|
||||
func detectInterfaces(
|
||||
cmd *cobra.Command, cfg *config,
|
||||
) ([]monitor.IfaceSpec, error) {
|
||||
ifaces, err := netdetect.Interfaces()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
routes, err := netdetect.DefaultRoutes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
flags := netdetect.Flags{
|
||||
IfaceA: cfg.IfaceA,
|
||||
LabelA: cfg.LabelA,
|
||||
IfaceB: cfg.IfaceB,
|
||||
LabelB: cfg.LabelB,
|
||||
LabelASet: cmd.Flags().Changed("labelA"),
|
||||
LabelBSet: cmd.Flags().Changed("labelB"),
|
||||
}
|
||||
|
||||
panes, err := netdetect.Select(runtime.GOOS, ifaces, routes, flags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
specs := make([]monitor.IfaceSpec, len(panes))
|
||||
for i, p := range panes {
|
||||
specs[i] = monitor.IfaceSpec{Name: p.Name, Label: p.Label}
|
||||
}
|
||||
|
||||
monitor.Logf(cfg.LogFile, "Monitoring %d interface(s)", len(specs))
|
||||
|
||||
return specs, nil
|
||||
}
|
||||
|
||||
// Execute builds the root command and runs it.
|
||||
func Execute() error {
|
||||
return newRootCmd().Execute()
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:build linux
|
||||
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
|
||||
Reference in New Issue
Block a user