From fad812c0f35ca85e579f12ede52f94e039b2be02 Mon Sep 17 00:00:00 2001 From: motylkov Date: Fri, 11 Jul 2025 12:39:50 +0300 Subject: [PATCH 1/2] Add route management --- README.md | 29 ++++++++++++ internal/network/bareos/route.go | 75 ++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 internal/network/bareos/route.go diff --git a/README.md b/README.md index d05356b..6c8b86d 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ A comprehensive Go CLI tool for managing FreeBSD infrastructure resources. - OS level - [Interfaces Management](#network-management) - [IP Address Management](#ip-address-management) + - [Route Management](#route-management) ## Examples @@ -223,6 +224,34 @@ A comprehensive Go CLI tool for managing FreeBSD infrastructure resources. ./fcom ip delete --iface em0 --ip 2001:db8::1 --mask 64 --family inet6 ``` +### Route Management + +Manage IPv4 and IPv6 routes, including default route safety. + +```bash +# Add IPv4 route +./fcom route add --family inet --net 10.0.0.0/24 --gw 10.0.0.1 + +# Add IPv6 route +./fcom route add --family inet6 --net 2001:db8::/64 --gw 2001:db8::1 + +# Delete IPv4 route +./fcom route del --family inet --net 10.0.0.0/24 + +# Delete IPv6 route +./fcom route del --family inet6 --net 2001:db8::/64 + +# List IPv4 routes +./fcom route list --family inet + +# List IPv6 routes +./fcom route list --family inet6 + +# Attempting to delete the last default route will fail: +./fcom route del --family inet --net default +# Output: cannot delete the last default route +``` + ### Version Information ```bash diff --git a/internal/network/bareos/route.go b/internal/network/bareos/route.go new file mode 100644 index 0000000..5fdcf70 --- /dev/null +++ b/internal/network/bareos/route.go @@ -0,0 +1,75 @@ +package bareos + +import ( + "fmt" + "os/exec" + "strings" +) + +// AddRoute adds a route for the given network and gateway (IPv4 or IPv6). +func AddRoute(family, network, gw string) error { + fam := family + if fam == "" { + fam = "inet" + } + cmd := exec.Command("route", "-n", "add", "-"+fam, network, gw) + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("route add error: %v, output: %s", err, string(output)) + } + return nil +} + +// DelRoute deletes a route for the given network (IPv4 or IPv6). +// For default route, prevents deleting the last default route. +func DelRoute(family, network string) error { + fam := family + if fam == "" { + fam = "inet" + } + if network == "default" { + count, err := countDefaultRoutes(fam) + if err != nil { + return err + } + if count <= 1 { + return fmt.Errorf("cannot delete the last default route") + } + } + cmd := exec.Command("route", "-n", "delete", "-"+fam, network) + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("route delete error: %v, output: %s", err, string(output)) + } + return nil +} + +// ListRoutes lists all routes for the given family (IPv4 or IPv6). +func ListRoutes(family string) (string, error) { + fam := family + if fam == "" { + fam = "inet" + } + cmd := exec.Command("netstat", "-rn", "-f", fam) + output, err := cmd.CombinedOutput() + if err != nil { + return "", fmt.Errorf("route list error: %v, output: %s", err, string(output)) + } + return string(output), nil +} + +// countDefaultRoutes returns the number of default routes for the given family. +func countDefaultRoutes(family string) (int, error) { + out, err := ListRoutes(family) + if err != nil { + return 0, err + } + count := 0 + for _, line := range strings.Split(out, "\n") { + fields := strings.Fields(line) + if len(fields) > 0 && fields[0] == "default" { + count++ + } + } + return count, nil +} From 426b38343de73193b60d4bc29ceec0054ef6a704 Mon Sep 17 00:00:00 2001 From: motylkov Date: Fri, 11 Jul 2025 14:18:47 +0300 Subject: [PATCH 2/2] update route management --- README.md | 53 ++++- cmd/network.go | 2 + cmd/route.go | 86 ++++++++ coverage.txt | 213 +++++++++++-------- internal/network/bareos/manager_test.go | 261 +++++++++++++++++------- internal/network/bareos/route.go | 64 ++++-- 6 files changed, 505 insertions(+), 174 deletions(-) create mode 100644 cmd/route.go diff --git a/README.md b/README.md index 6c8b86d..677dc26 100644 --- a/README.md +++ b/README.md @@ -226,32 +226,73 @@ A comprehensive Go CLI tool for managing FreeBSD infrastructure resources. ### Route Management -Manage IPv4 and IPv6 routes, including default route safety. +Easily manage IPv4 and IPv6 routes, including adding, deleting, and listing routes. The CLI ensures you cannot accidentally remove the last default route, protecting system connectivity. + +#### Add a Route ```bash -# Add IPv4 route +# Add an IPv4 network route ./fcom route add --family inet --net 10.0.0.0/24 --gw 10.0.0.1 -# Add IPv6 route +# Add an IPv6 network route ./fcom route add --family inet6 --net 2001:db8::/64 --gw 2001:db8::1 -# Delete IPv4 route +# Add an IPv4 network route via a specific interface +./fcom route add --family inet --net 10.0.0.0/24 --gw 10.0.0.1 --iface em0 + +# Add an IPv6 network route via a specific interface +./fcom route add --family inet6 --net 2001:db8::/64 --gw 2001:db8::1 --iface em1 + +# Add a default IPv4 route +./fcom route add --family inet --net default --gw 192.168.1.1 + +# Add a host route (single IP) +./fcom route add --family inet --net 192.168.1.50/32 --gw 10.0.0.1 +``` + +> **Note:** The `--iface` flag sets the outgoing interface for the route (uses FreeBSD's `-ifp` option). It is optional. + +#### Delete a Route + +```bash +# Delete an IPv4 network route ./fcom route del --family inet --net 10.0.0.0/24 -# Delete IPv6 route +# Delete an IPv6 network route ./fcom route del --family inet6 --net 2001:db8::/64 -# List IPv4 routes +# Delete a default route (will fail if it's the last default route) +./fcom route del --family inet --net default +# Output: cannot delete the last default route +``` + +#### List Routes + +```bash +# List all IPv4 routes ./fcom route list --family inet # List IPv6 routes ./fcom route list --family inet6 + +# List all routes +./fcom route list +``` + +#### Example: Safe Default Route Handling + +```bash # Attempting to delete the last default route will fail: ./fcom route del --family inet --net default # Output: cannot delete the last default route ``` +#### Troubleshooting +- Ensure you specify the correct `--family` (either `inet` for IPv4 or `inet6` for IPv6). +- The `--net` flag accepts both network prefixes (e.g., `10.0.0.0/24`) and `default`. +- You cannot delete the last default route for a family; this is a safety feature. + ### Version Information ```bash diff --git a/cmd/network.go b/cmd/network.go index 84d93d9..58e444f 100644 --- a/cmd/network.go +++ b/cmd/network.go @@ -477,5 +477,7 @@ func init() { //nolint os.Exit(1) } + networkCmd.AddCommand(routeCmd) + cmd.AddCommand(networkCmd) } diff --git a/cmd/route.go b/cmd/route.go new file mode 100644 index 0000000..e13109d --- /dev/null +++ b/cmd/route.go @@ -0,0 +1,86 @@ +package cmd + +import ( + "FreeBSD-Command-manager/internal/network/bareos" + "fmt" + "os" + + "github.com/spf13/cobra" +) + +var ( + routeFamily string + routeNet string + routeGW string + routeIface string // new flag for interface +) + +var routeCmd = &cobra.Command{ + Use: "route", + Short: "Manage routes (add, del, list)", +} + +var routeAddCmd = &cobra.Command{ + Use: "add", + Short: "Add a route", + Run: func(_ *cobra.Command, _ []string) { + if routeNet == "" || routeGW == "" { + fmt.Fprintln(os.Stderr, "--net and --gw are required") + os.Exit(1) + } + err := bareos.AddRouteWithIface(routeFamily, routeNet, routeGW, routeIface) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + fmt.Println("Route added successfully") + }, +} + +var routeDelCmd = &cobra.Command{ + Use: "del", + Short: "Delete a route", + Run: func(cmd *cobra.Command, args []string) { //nolint:revive // cmd is required by cobra interface + if routeNet == "" { + fmt.Fprintln(os.Stderr, "--net is required") + os.Exit(1) + } + err := bareos.DelRoute(routeFamily, routeNet) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + fmt.Println("Route deleted successfully") + }, +} + +var routeListCmd = &cobra.Command{ + Use: "list", + Short: "List routes", + Run: func(cmd *cobra.Command, args []string) { //nolint:revive // cmd is required by cobra interface + out, err := bareos.ListAllRoutes(routeFamily) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + fmt.Print(out) + }, +} + +func init() { //nolint + routeAddCmd.Flags().StringVar(&routeFamily, "family", "inet", "Address family (inet or inet6)") + routeAddCmd.Flags().StringVar(&routeNet, "net", "", "Network or destination (required)") + routeAddCmd.Flags().StringVar(&routeGW, "gw", "", "Gateway (required)") + routeAddCmd.Flags().StringVar(&routeIface, "iface", "", "Outgoing interface (optional)") + + routeDelCmd.Flags().StringVar(&routeFamily, "family", "inet", "Address family (inet or inet6)") + routeDelCmd.Flags().StringVar(&routeNet, "net", "", "Network or destination (required)") + + routeListCmd.Flags().StringVar(&routeFamily, "family", "", "Address family (inet, inet6, or empty for all)") + + routeCmd.AddCommand(routeAddCmd) + routeCmd.AddCommand(routeDelCmd) + routeCmd.AddCommand(routeListCmd) + + cmd.AddCommand(routeCmd) +} diff --git a/coverage.txt b/coverage.txt index 73d9138..137bff3 100644 --- a/coverage.txt +++ b/coverage.txt @@ -2,7 +2,6 @@ mode: atomic FreeBSD-Command-manager/internal/output.go:11.37,14.41 3 0 FreeBSD-Command-manager/internal/output.go:14.41,17.3 2 0 FreeBSD-Command-manager/internal/output.go:18.2,18.12 1 0 -FreeBSD-Command-manager/main.go:17.13,19.2 1 0 FreeBSD-Command-manager/cmd/cmd.go:18.33,23.38 5 0 FreeBSD-Command-manager/cmd/cmd.go:23.38,26.3 2 0 FreeBSD-Command-manager/cmd/jail.go:22.47,32.45 3 0 @@ -144,90 +143,24 @@ FreeBSD-Command-manager/cmd/network.go:463.2,465.61 3 0 FreeBSD-Command-manager/cmd/network.go:465.61,468.3 2 0 FreeBSD-Command-manager/cmd/network.go:471.2,475.64 4 0 FreeBSD-Command-manager/cmd/network.go:475.64,478.3 2 0 -FreeBSD-Command-manager/cmd/network.go:480.2,480.28 1 0 +FreeBSD-Command-manager/cmd/network.go:480.2,482.28 2 0 +FreeBSD-Command-manager/cmd/route.go:25.47,26.38 1 0 +FreeBSD-Command-manager/cmd/route.go:26.38,29.4 2 0 +FreeBSD-Command-manager/cmd/route.go:30.3,31.17 2 0 +FreeBSD-Command-manager/cmd/route.go:31.17,34.4 2 0 +FreeBSD-Command-manager/cmd/route.go:35.3,35.42 1 0 +FreeBSD-Command-manager/cmd/route.go:42.47,43.21 1 0 +FreeBSD-Command-manager/cmd/route.go:43.21,46.4 2 0 +FreeBSD-Command-manager/cmd/route.go:47.3,48.17 2 0 +FreeBSD-Command-manager/cmd/route.go:48.17,51.4 2 0 +FreeBSD-Command-manager/cmd/route.go:52.3,52.44 1 0 +FreeBSD-Command-manager/cmd/route.go:59.47,61.17 2 0 +FreeBSD-Command-manager/cmd/route.go:61.17,64.4 2 0 +FreeBSD-Command-manager/cmd/route.go:65.3,65.17 1 0 +FreeBSD-Command-manager/cmd/route.go:69.13,84.2 10 0 FreeBSD-Command-manager/cmd/version.go:20.47,22.3 1 0 FreeBSD-Command-manager/cmd/version.go:25.13,27.2 1 0 -FreeBSD-Command-manager/internal/jail/manager.go:54.102,59.2 1 15 -FreeBSD-Command-manager/internal/jail/manager.go:62.55,64.54 1 5 -FreeBSD-Command-manager/internal/jail/manager.go:64.54,66.3 1 1 -FreeBSD-Command-manager/internal/jail/manager.go:69.2,69.57 1 4 -FreeBSD-Command-manager/internal/jail/manager.go:69.57,71.3 1 1 -FreeBSD-Command-manager/internal/jail/manager.go:74.2,74.21 1 3 -FreeBSD-Command-manager/internal/jail/manager.go:74.21,75.64 1 0 -FreeBSD-Command-manager/internal/jail/manager.go:75.64,77.4 1 0 -FreeBSD-Command-manager/internal/jail/manager.go:81.2,87.16 2 3 -FreeBSD-Command-manager/internal/jail/manager.go:87.16,89.3 1 1 -FreeBSD-Command-manager/internal/jail/manager.go:91.2,91.12 1 2 -FreeBSD-Command-manager/internal/jail/manager.go:95.55,96.16 1 4 -FreeBSD-Command-manager/internal/jail/manager.go:96.16,98.3 1 1 -FreeBSD-Command-manager/internal/jail/manager.go:100.2,101.16 2 3 -FreeBSD-Command-manager/internal/jail/manager.go:101.16,103.3 1 1 -FreeBSD-Command-manager/internal/jail/manager.go:105.2,105.12 1 2 -FreeBSD-Command-manager/internal/jail/manager.go:109.54,110.16 1 8 -FreeBSD-Command-manager/internal/jail/manager.go:110.16,112.3 1 1 -FreeBSD-Command-manager/internal/jail/manager.go:114.2,115.16 2 7 -FreeBSD-Command-manager/internal/jail/manager.go:115.16,117.3 1 2 -FreeBSD-Command-manager/internal/jail/manager.go:119.2,119.12 1 5 -FreeBSD-Command-manager/internal/jail/manager.go:123.57,124.16 1 5 -FreeBSD-Command-manager/internal/jail/manager.go:124.16,126.3 1 1 -FreeBSD-Command-manager/internal/jail/manager.go:129.2,129.37 1 4 -FreeBSD-Command-manager/internal/jail/manager.go:129.37,132.3 1 1 -FreeBSD-Command-manager/internal/jail/manager.go:135.2,136.16 2 4 -FreeBSD-Command-manager/internal/jail/manager.go:136.16,138.3 1 1 -FreeBSD-Command-manager/internal/jail/manager.go:140.2,140.12 1 3 -FreeBSD-Command-manager/internal/jail/manager.go:144.61,146.16 2 0 -FreeBSD-Command-manager/internal/jail/manager.go:146.16,148.3 1 0 -FreeBSD-Command-manager/internal/jail/manager.go:149.2,150.16 2 0 -FreeBSD-Command-manager/internal/jail/manager.go:150.16,152.3 1 0 -FreeBSD-Command-manager/internal/jail/manager.go:153.2,153.19 1 0 -FreeBSD-Command-manager/internal/jail/manager.go:157.74,158.16 1 0 -FreeBSD-Command-manager/internal/jail/manager.go:158.16,160.3 1 0 -FreeBSD-Command-manager/internal/jail/manager.go:161.2,162.16 2 0 -FreeBSD-Command-manager/internal/jail/manager.go:162.16,164.3 1 0 -FreeBSD-Command-manager/internal/jail/manager.go:165.2,166.16 2 0 -FreeBSD-Command-manager/internal/jail/manager.go:166.16,168.3 1 0 -FreeBSD-Command-manager/internal/jail/manager.go:169.2,169.29 1 0 -FreeBSD-Command-manager/internal/jail/manager.go:169.29,170.24 1 0 -FreeBSD-Command-manager/internal/jail/manager.go:170.24,172.4 1 0 -FreeBSD-Command-manager/internal/jail/manager.go:174.2,174.51 1 0 -FreeBSD-Command-manager/internal/jail/manager.go:181.56,183.2 1 0 -FreeBSD-Command-manager/internal/jail/manager.go:186.63,187.49 1 0 -FreeBSD-Command-manager/internal/jail/manager.go:187.49,188.72 1 0 -FreeBSD-Command-manager/internal/jail/manager.go:188.72,190.4 1 0 -FreeBSD-Command-manager/internal/jail/manager.go:192.2,192.12 1 0 -FreeBSD-Command-manager/internal/jail/manager.go:196.68,198.50 2 0 -FreeBSD-Command-manager/internal/jail/manager.go:198.50,200.3 1 0 -FreeBSD-Command-manager/internal/jail/manager.go:201.2,201.12 1 0 -FreeBSD-Command-manager/internal/jail/manager.go:205.62,207.50 2 0 -FreeBSD-Command-manager/internal/jail/manager.go:207.50,209.3 1 0 -FreeBSD-Command-manager/internal/jail/manager.go:210.2,210.12 1 0 -FreeBSD-Command-manager/internal/jail/manager.go:217.52,219.2 1 0 -FreeBSD-Command-manager/internal/jail/manager.go:222.84,225.16 3 0 -FreeBSD-Command-manager/internal/jail/manager.go:225.16,227.3 1 0 -FreeBSD-Command-manager/internal/jail/manager.go:228.2,228.28 1 0 -FreeBSD-Command-manager/internal/jail/manager.go:232.31,236.2 3 0 -FreeBSD-Command-manager/internal/jail/testing.go:30.63,34.2 3 4 -FreeBSD-Command-manager/internal/jail/testing.go:37.68,42.2 4 0 -FreeBSD-Command-manager/internal/jail/testing.go:45.62,49.2 3 0 -FreeBSD-Command-manager/internal/jail/testing.go:61.84,66.2 4 11 -FreeBSD-Command-manager/internal/jail/testing.go:78.86,83.19 3 6 -FreeBSD-Command-manager/internal/jail/testing.go:83.19,85.3 1 6 -FreeBSD-Command-manager/internal/jail/testing.go:86.2,89.42 2 6 -FreeBSD-Command-manager/internal/jail/testing.go:89.42,90.18 1 6 -FreeBSD-Command-manager/internal/jail/testing.go:91.15,92.38 1 0 -FreeBSD-Command-manager/internal/jail/testing.go:93.18,94.47 1 0 -FreeBSD-Command-manager/internal/jail/testing.go:95.16,96.30 1 0 -FreeBSD-Command-manager/internal/jail/testing.go:97.17,98.30 1 0 -FreeBSD-Command-manager/internal/jail/testing.go:99.15,100.37 1 0 -FreeBSD-Command-manager/internal/jail/testing.go:101.15,102.34 1 0 -FreeBSD-Command-manager/internal/jail/testing.go:103.13,104.23 1 6 -FreeBSD-Command-manager/internal/jail/testing.go:104.23,106.5 1 6 -FreeBSD-Command-manager/internal/jail/testing.go:108.4,108.24 1 0 -FreeBSD-Command-manager/internal/jail/testing.go:108.24,110.5 1 0 -FreeBSD-Command-manager/internal/jail/testing.go:111.4,111.47 1 0 -FreeBSD-Command-manager/internal/jail/testing.go:116.2,116.50 1 0 -FreeBSD-Command-manager/internal/jail/testing.go:120.83,122.2 1 0 -FreeBSD-Command-manager/internal/jail/testing.go:125.31,129.2 3 1 +FreeBSD-Command-manager/main.go:17.13,19.2 1 0 FreeBSD-Command-manager/internal/network/bareos/basic.go:9.54,10.16 1 3 FreeBSD-Command-manager/internal/network/bareos/basic.go:10.16,12.3 1 1 FreeBSD-Command-manager/internal/network/bareos/basic.go:14.2,15.16 2 2 @@ -317,6 +250,39 @@ FreeBSD-Command-manager/internal/network/bareos/manager.go:76.2,76.23 1 1 FreeBSD-Command-manager/internal/network/bareos/manager.go:83.52,85.2 1 0 FreeBSD-Command-manager/internal/network/bareos/manager.go:88.84,92.2 3 0 FreeBSD-Command-manager/internal/network/bareos/manager.go:95.40,98.2 2 0 +FreeBSD-Command-manager/internal/network/bareos/route.go:11.52,12.18 1 0 +FreeBSD-Command-manager/internal/network/bareos/route.go:12.18,14.3 1 0 +FreeBSD-Command-manager/internal/network/bareos/route.go:15.2,17.16 3 0 +FreeBSD-Command-manager/internal/network/bareos/route.go:17.16,19.3 1 0 +FreeBSD-Command-manager/internal/network/bareos/route.go:20.2,20.28 1 0 +FreeBSD-Command-manager/internal/network/bareos/route.go:26.49,28.15 2 2 +FreeBSD-Command-manager/internal/network/bareos/route.go:28.15,30.3 1 0 +FreeBSD-Command-manager/internal/network/bareos/route.go:31.2,33.16 3 2 +FreeBSD-Command-manager/internal/network/bareos/route.go:33.16,35.3 1 1 +FreeBSD-Command-manager/internal/network/bareos/route.go:36.2,36.12 1 1 +FreeBSD-Command-manager/internal/network/bareos/route.go:41.45,43.15 2 2 +FreeBSD-Command-manager/internal/network/bareos/route.go:43.15,45.3 1 0 +FreeBSD-Command-manager/internal/network/bareos/route.go:46.2,46.26 1 2 +FreeBSD-Command-manager/internal/network/bareos/route.go:46.26,48.17 2 2 +FreeBSD-Command-manager/internal/network/bareos/route.go:48.17,50.4 1 0 +FreeBSD-Command-manager/internal/network/bareos/route.go:51.3,51.17 1 2 +FreeBSD-Command-manager/internal/network/bareos/route.go:51.17,53.4 1 1 +FreeBSD-Command-manager/internal/network/bareos/route.go:55.2,57.16 3 1 +FreeBSD-Command-manager/internal/network/bareos/route.go:57.16,59.3 1 0 +FreeBSD-Command-manager/internal/network/bareos/route.go:60.2,60.12 1 1 +FreeBSD-Command-manager/internal/network/bareos/route.go:64.51,65.18 1 2 +FreeBSD-Command-manager/internal/network/bareos/route.go:65.18,67.3 1 1 +FreeBSD-Command-manager/internal/network/bareos/route.go:68.2,69.16 2 1 +FreeBSD-Command-manager/internal/network/bareos/route.go:69.16,71.3 1 0 +FreeBSD-Command-manager/internal/network/bareos/route.go:72.2,73.16 2 1 +FreeBSD-Command-manager/internal/network/bareos/route.go:73.16,75.3 1 0 +FreeBSD-Command-manager/internal/network/bareos/route.go:76.2,76.75 1 1 +FreeBSD-Command-manager/internal/network/bareos/route.go:80.53,82.16 2 2 +FreeBSD-Command-manager/internal/network/bareos/route.go:82.16,84.3 1 0 +FreeBSD-Command-manager/internal/network/bareos/route.go:85.2,86.48 2 2 +FreeBSD-Command-manager/internal/network/bareos/route.go:86.48,88.48 2 5 +FreeBSD-Command-manager/internal/network/bareos/route.go:88.48,90.4 1 3 +FreeBSD-Command-manager/internal/network/bareos/route.go:92.2,92.19 1 2 FreeBSD-Command-manager/internal/network/bareos/testing.go:11.52,17.2 1 41 FreeBSD-Command-manager/internal/network/bareos/testing.go:20.84,23.27 2 44 FreeBSD-Command-manager/internal/network/bareos/testing.go:23.27,25.3 1 122 @@ -389,6 +355,87 @@ FreeBSD-Command-manager/pkg/jail/parser.go:42.20,43.26 1 5 FreeBSD-Command-manager/pkg/jail/parser.go:44.16,45.26 1 5 FreeBSD-Command-manager/pkg/jail/parser.go:48.3,48.30 1 5 FreeBSD-Command-manager/pkg/jail/parser.go:50.2,50.19 1 3 +FreeBSD-Command-manager/internal/jail/manager.go:54.102,59.2 1 15 +FreeBSD-Command-manager/internal/jail/manager.go:62.55,64.54 1 5 +FreeBSD-Command-manager/internal/jail/manager.go:64.54,66.3 1 1 +FreeBSD-Command-manager/internal/jail/manager.go:69.2,69.57 1 4 +FreeBSD-Command-manager/internal/jail/manager.go:69.57,71.3 1 1 +FreeBSD-Command-manager/internal/jail/manager.go:74.2,74.21 1 3 +FreeBSD-Command-manager/internal/jail/manager.go:74.21,75.64 1 0 +FreeBSD-Command-manager/internal/jail/manager.go:75.64,77.4 1 0 +FreeBSD-Command-manager/internal/jail/manager.go:81.2,87.16 2 3 +FreeBSD-Command-manager/internal/jail/manager.go:87.16,89.3 1 1 +FreeBSD-Command-manager/internal/jail/manager.go:91.2,91.12 1 2 +FreeBSD-Command-manager/internal/jail/manager.go:95.55,96.16 1 4 +FreeBSD-Command-manager/internal/jail/manager.go:96.16,98.3 1 1 +FreeBSD-Command-manager/internal/jail/manager.go:100.2,101.16 2 3 +FreeBSD-Command-manager/internal/jail/manager.go:101.16,103.3 1 1 +FreeBSD-Command-manager/internal/jail/manager.go:105.2,105.12 1 2 +FreeBSD-Command-manager/internal/jail/manager.go:109.54,110.16 1 8 +FreeBSD-Command-manager/internal/jail/manager.go:110.16,112.3 1 1 +FreeBSD-Command-manager/internal/jail/manager.go:114.2,115.16 2 7 +FreeBSD-Command-manager/internal/jail/manager.go:115.16,117.3 1 2 +FreeBSD-Command-manager/internal/jail/manager.go:119.2,119.12 1 5 +FreeBSD-Command-manager/internal/jail/manager.go:123.57,124.16 1 5 +FreeBSD-Command-manager/internal/jail/manager.go:124.16,126.3 1 1 +FreeBSD-Command-manager/internal/jail/manager.go:129.2,129.37 1 4 +FreeBSD-Command-manager/internal/jail/manager.go:129.37,132.3 1 1 +FreeBSD-Command-manager/internal/jail/manager.go:135.2,136.16 2 4 +FreeBSD-Command-manager/internal/jail/manager.go:136.16,138.3 1 1 +FreeBSD-Command-manager/internal/jail/manager.go:140.2,140.12 1 3 +FreeBSD-Command-manager/internal/jail/manager.go:144.61,146.16 2 0 +FreeBSD-Command-manager/internal/jail/manager.go:146.16,148.3 1 0 +FreeBSD-Command-manager/internal/jail/manager.go:149.2,150.16 2 0 +FreeBSD-Command-manager/internal/jail/manager.go:150.16,152.3 1 0 +FreeBSD-Command-manager/internal/jail/manager.go:153.2,153.19 1 0 +FreeBSD-Command-manager/internal/jail/manager.go:157.74,158.16 1 0 +FreeBSD-Command-manager/internal/jail/manager.go:158.16,160.3 1 0 +FreeBSD-Command-manager/internal/jail/manager.go:161.2,162.16 2 0 +FreeBSD-Command-manager/internal/jail/manager.go:162.16,164.3 1 0 +FreeBSD-Command-manager/internal/jail/manager.go:165.2,166.16 2 0 +FreeBSD-Command-manager/internal/jail/manager.go:166.16,168.3 1 0 +FreeBSD-Command-manager/internal/jail/manager.go:169.2,169.29 1 0 +FreeBSD-Command-manager/internal/jail/manager.go:169.29,170.24 1 0 +FreeBSD-Command-manager/internal/jail/manager.go:170.24,172.4 1 0 +FreeBSD-Command-manager/internal/jail/manager.go:174.2,174.51 1 0 +FreeBSD-Command-manager/internal/jail/manager.go:181.56,183.2 1 0 +FreeBSD-Command-manager/internal/jail/manager.go:186.63,187.49 1 0 +FreeBSD-Command-manager/internal/jail/manager.go:187.49,188.72 1 0 +FreeBSD-Command-manager/internal/jail/manager.go:188.72,190.4 1 0 +FreeBSD-Command-manager/internal/jail/manager.go:192.2,192.12 1 0 +FreeBSD-Command-manager/internal/jail/manager.go:196.68,198.50 2 0 +FreeBSD-Command-manager/internal/jail/manager.go:198.50,200.3 1 0 +FreeBSD-Command-manager/internal/jail/manager.go:201.2,201.12 1 0 +FreeBSD-Command-manager/internal/jail/manager.go:205.62,207.50 2 0 +FreeBSD-Command-manager/internal/jail/manager.go:207.50,209.3 1 0 +FreeBSD-Command-manager/internal/jail/manager.go:210.2,210.12 1 0 +FreeBSD-Command-manager/internal/jail/manager.go:217.52,219.2 1 0 +FreeBSD-Command-manager/internal/jail/manager.go:222.84,225.16 3 0 +FreeBSD-Command-manager/internal/jail/manager.go:225.16,227.3 1 0 +FreeBSD-Command-manager/internal/jail/manager.go:228.2,228.28 1 0 +FreeBSD-Command-manager/internal/jail/manager.go:232.31,236.2 3 0 +FreeBSD-Command-manager/internal/jail/testing.go:30.63,34.2 3 4 +FreeBSD-Command-manager/internal/jail/testing.go:37.68,42.2 4 0 +FreeBSD-Command-manager/internal/jail/testing.go:45.62,49.2 3 0 +FreeBSD-Command-manager/internal/jail/testing.go:61.84,66.2 4 11 +FreeBSD-Command-manager/internal/jail/testing.go:78.86,83.19 3 6 +FreeBSD-Command-manager/internal/jail/testing.go:83.19,85.3 1 6 +FreeBSD-Command-manager/internal/jail/testing.go:86.2,89.42 2 6 +FreeBSD-Command-manager/internal/jail/testing.go:89.42,90.18 1 6 +FreeBSD-Command-manager/internal/jail/testing.go:91.15,92.38 1 0 +FreeBSD-Command-manager/internal/jail/testing.go:93.18,94.47 1 0 +FreeBSD-Command-manager/internal/jail/testing.go:95.16,96.30 1 0 +FreeBSD-Command-manager/internal/jail/testing.go:97.17,98.30 1 0 +FreeBSD-Command-manager/internal/jail/testing.go:99.15,100.37 1 0 +FreeBSD-Command-manager/internal/jail/testing.go:101.15,102.34 1 0 +FreeBSD-Command-manager/internal/jail/testing.go:103.13,104.23 1 6 +FreeBSD-Command-manager/internal/jail/testing.go:104.23,106.5 1 6 +FreeBSD-Command-manager/internal/jail/testing.go:108.4,108.24 1 0 +FreeBSD-Command-manager/internal/jail/testing.go:108.24,110.5 1 0 +FreeBSD-Command-manager/internal/jail/testing.go:111.4,111.47 1 0 +FreeBSD-Command-manager/internal/jail/testing.go:116.2,116.50 1 0 +FreeBSD-Command-manager/internal/jail/testing.go:120.83,122.2 1 0 +FreeBSD-Command-manager/internal/jail/testing.go:125.31,129.2 3 1 FreeBSD-Command-manager/pkg/ifconfig/parser.go:56.42,65.29 7 1 FreeBSD-Command-manager/pkg/ifconfig/parser.go:65.29,68.31 2 29 FreeBSD-Command-manager/pkg/ifconfig/parser.go:68.31,69.26 1 4 diff --git a/internal/network/bareos/manager_test.go b/internal/network/bareos/manager_test.go index 4e32f13..f1aa910 100644 --- a/internal/network/bareos/manager_test.go +++ b/internal/network/bareos/manager_test.go @@ -2,9 +2,13 @@ package bareos import ( "errors" + "os/exec" + "strings" "testing" ) +const inet6Family = "inet6" + func TestBareOSManager_CreateInterface(t *testing.T) { tests := []struct { name string @@ -23,14 +27,14 @@ func TestBareOSManager_CreateInterface(t *testing.T) { }, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { mockCmd := NewMockCommandExecutor() manager := NewManager(mockCmd) - err := manager.CreateInterface(tt.interfaceName) + err := manager.CreateInterface(tc.interfaceName) - if tt.shouldError { + if tc.shouldError { if err == nil { t.Error("expected error but got none") } @@ -44,8 +48,8 @@ func TestBareOSManager_CreateInterface(t *testing.T) { if len(commands) < 2 { t.Errorf("expected at least 2 commands, got %d", len(commands)) } - expectedCreateCmd := "ifconfig " + tt.interfaceName + " create" - expectedUpCmd := "ifconfig " + tt.interfaceName + " up" + expectedCreateCmd := "ifconfig " + tc.interfaceName + " create" + expectedUpCmd := "ifconfig " + tc.interfaceName + " up" if commands[0] != expectedCreateCmd { t.Errorf("expected first command %s, got %s", expectedCreateCmd, commands[0]) } @@ -74,21 +78,21 @@ func TestBareOSManager_CreateBridge(t *testing.T) { }, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { mockCmd := NewMockCommandExecutor() // Set up mock responses for bridge creation - if !tt.shouldError { + if !tc.shouldError { mockCmd.SetOutput("ifconfig bridge create", "bridge0") mockCmd.SetOutput("ifconfig bridge0 up", "") } manager := NewManager(mockCmd) - err := manager.CreateBridge(tt.bridgeName) + err := manager.CreateBridge(tc.bridgeName) - if tt.shouldError { + if tc.shouldError { if err == nil { t.Error("expected error but got none") } @@ -159,25 +163,25 @@ func TestBareOSManager_CreateVLAN(t *testing.T) { }, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { mockCmd := NewMockCommandExecutor() // Set up mock responses for VLAN creation - if !tt.shouldError { + if !tc.shouldError { mockCmd.SetOutput("ifconfig vlan create", "vlan0") mockCmd.SetOutput("ifconfig vlan0 vlan 100 vlandev em0", "") mockCmd.SetOutput("ifconfig vlan0 up", "") - if tt.vlanName != "" { - mockCmd.SetOutput("ifconfig vlan0 name "+tt.vlanName, "") + if tc.vlanName != "" { + mockCmd.SetOutput("ifconfig vlan0 name "+tc.vlanName, "") } } manager := NewManager(mockCmd) - err := manager.CreateVLAN(tt.vlanName, tt.parent, tt.vlanID) + err := manager.CreateVLAN(tc.vlanName, tc.parent, tc.vlanID) - if tt.shouldError { + if tc.shouldError { if err == nil { t.Error("expected error but got none") } @@ -233,25 +237,25 @@ func TestBareOSManager_CreateGRE(t *testing.T) { }, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { mockCmd := NewMockCommandExecutor() // Set up mock responses for GRE creation - if !tt.shouldError { + if !tc.shouldError { mockCmd.SetOutput("ifconfig gre create", "gre0") mockCmd.SetOutput("ifconfig gre0 tunnel 192.168.1.2 192.168.1.1", "") mockCmd.SetOutput("ifconfig gre0 up", "") - if tt.greName != "" { - mockCmd.SetOutput("ifconfig gre0 name "+tt.greName, "") + if tc.greName != "" { + mockCmd.SetOutput("ifconfig gre0 name "+tc.greName, "") } } manager := NewManager(mockCmd) - err := manager.CreateGRE(tt.greName, tt.remote, tt.local) + err := manager.CreateGRE(tc.greName, tc.remote, tc.local) - if tt.shouldError { + if tc.shouldError { if err == nil { t.Error("expected error but got none") } @@ -326,11 +330,11 @@ func TestBareOSManager_GetInfo(t *testing.T) { }, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { mockCmd := NewMockCommandExecutor() - if !tt.shouldError { + if !tc.shouldError { // Set up mock ifconfig output for specific interface mockIfconfigOutput := `em0: flags=1008843 metric 0 mtu 1500 options=48505bb @@ -346,9 +350,9 @@ nd6 options=23` manager := NewManager(mockCmd) - info, err := manager.GetInfo(tt.interfaceName) + info, err := manager.GetInfo(tc.interfaceName) - if tt.shouldError { + if tc.shouldError { if err == nil { t.Error("expected error but got none") } @@ -362,10 +366,10 @@ nd6 options=23` t.Error("expected info but got nil") return } - if info.Name != tt.interfaceName { - t.Errorf("expected name %s, got %s", tt.interfaceName, info.Name) + if info.Name != tc.interfaceName { + t.Errorf("expected name %s, got %s", tc.interfaceName, info.Name) } - expectedCmd := "ifconfig " + tt.interfaceName + expectedCmd := "ifconfig " + tc.interfaceName if len(mockCmd.GetCommands()) == 0 || mockCmd.GetCommands()[0] != expectedCmd { t.Errorf("expected command %s, got %v", expectedCmd, mockCmd.GetCommands()) } @@ -527,33 +531,33 @@ func TestBareOSManager_CreateVXLAN(t *testing.T) { }, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { mockCmd := NewMockCommandExecutor() // Set up mock responses for VXLAN creation - if !tt.shouldError { + if !tc.shouldError { mockCmd.SetOutput("ifconfig vxlan create", "vxlan0") // Build expected command based on parameters expectedCmd := "ifconfig vxlan0 vxlan vni 1000 remote 192.168.1.2 local 192.168.1.1" - if tt.group != "" { - expectedCmd += " group " + tt.group + if tc.group != "" { + expectedCmd += " group " + tc.group } - if tt.dev != "" { - expectedCmd += " dev " + tt.dev + if tc.dev != "" { + expectedCmd += " dev " + tc.dev } mockCmd.SetOutput(expectedCmd, "") mockCmd.SetOutput("ifconfig vxlan0 up", "") - if tt.vxlanName != "" { - mockCmd.SetOutput("ifconfig vxlan0 name "+tt.vxlanName, "") + if tc.vxlanName != "" { + mockCmd.SetOutput("ifconfig vxlan0 name "+tc.vxlanName, "") } } manager := NewManager(mockCmd) - err := manager.CreateVXLAN(tt.vxlanName, tt.local, tt.remote, tt.group, tt.dev, tt.vxlanID) + err := manager.CreateVXLAN(tc.vxlanName, tc.local, tc.remote, tc.group, tc.dev, tc.vxlanID) - if tt.shouldError { + if tc.shouldError { if err == nil { t.Error("expected error but got none") } @@ -616,11 +620,11 @@ func TestBareOSManager_DeleteInterface(t *testing.T) { {"successful interface deletion", "test0", false}, {"empty interface name", "", true}, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { mockCmd := NewMockCommandExecutor() manager := NewManager(mockCmd) - runDeleteTest(t, mockCmd, manager.DeleteInterface, tt.interfaceName, "ifconfig "+tt.interfaceName+" destroy", tt.shouldError) + runDeleteTest(t, mockCmd, manager.DeleteInterface, tc.interfaceName, "ifconfig "+tc.interfaceName+" destroy", tc.shouldError) }) } } @@ -634,11 +638,11 @@ func TestBareOSManager_DeleteVLAN(t *testing.T) { {"successful VLAN deletion", "vlan100", false}, {"empty VLAN name", "", true}, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { mockCmd := NewMockCommandExecutor() manager := NewManager(mockCmd) - runDeleteTest(t, mockCmd, manager.DeleteVLAN, tt.vlanName, "ifconfig "+tt.vlanName+" destroy", tt.shouldError) + runDeleteTest(t, mockCmd, manager.DeleteVLAN, tc.vlanName, "ifconfig "+tc.vlanName+" destroy", tc.shouldError) }) } } @@ -652,11 +656,11 @@ func TestBareOSManager_DeleteGRE(t *testing.T) { {"successful GRE deletion", "gre0", false}, {"empty GRE name", "", true}, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { mockCmd := NewMockCommandExecutor() manager := NewManager(mockCmd) - runDeleteTest(t, mockCmd, manager.DeleteGRE, tt.greName, "ifconfig "+tt.greName+" destroy", tt.shouldError) + runDeleteTest(t, mockCmd, manager.DeleteGRE, tc.greName, "ifconfig "+tc.greName+" destroy", tc.shouldError) }) } } @@ -670,11 +674,11 @@ func TestBareOSManager_DeleteVXLAN(t *testing.T) { {"successful VXLAN deletion", "vxlan0", false}, {"empty VXLAN name", "", true}, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { mockCmd := NewMockCommandExecutor() manager := NewManager(mockCmd) - runDeleteTest(t, mockCmd, manager.DeleteVXLAN, tt.vxlanName, "ifconfig "+tt.vxlanName+" destroy", tt.shouldError) + runDeleteTest(t, mockCmd, manager.DeleteVXLAN, tc.vxlanName, "ifconfig "+tc.vxlanName+" destroy", tc.shouldError) }) } } @@ -691,11 +695,11 @@ func TestBareOSManager_AddInterfaceToBridge(t *testing.T) { {"empty bridge name", "", "em0", true}, {"empty interface name", "br0", "", true}, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { mockCmd := NewMockCommandExecutor() manager := NewManager(mockCmd) - runBridgeMemberTest(t, mockCmd, manager.AddInterfaceToBridge, tt.bridgeName, tt.interfaceName, "ifconfig "+tt.bridgeName+" addm "+tt.interfaceName, tt.shouldError) + runBridgeMemberTest(t, mockCmd, manager.AddInterfaceToBridge, tc.bridgeName, tc.interfaceName, "ifconfig "+tc.bridgeName+" addm "+tc.interfaceName, tc.shouldError) }) } } @@ -711,30 +715,30 @@ func TestBareOSManager_RemoveInterfaceFromBridge(t *testing.T) { {"empty bridge name", "", "em0", true}, {"empty interface name", "br0", "", true}, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { mockCmd := NewMockCommandExecutor() manager := NewManager(mockCmd) - runBridgeMemberTest(t, mockCmd, manager.RemoveInterfaceFromBridge, tt.bridgeName, tt.interfaceName, "ifconfig "+tt.bridgeName+" deletem "+tt.interfaceName, tt.shouldError) + runBridgeMemberTest(t, mockCmd, manager.RemoveInterfaceFromBridge, tc.bridgeName, tc.interfaceName, "ifconfig "+tc.bridgeName+" deletem "+tc.interfaceName, tc.shouldError) }) } } func TestAddIP_InvalidInput(t *testing.T) { t.Run("empty iface", func(t *testing.T) { - err := AddIP("", "192.168.1.10", 24, "inet") + err := AddIP("", "192.168.1.10", 24, defaultFamily) if err == nil { t.Error("expected error for empty iface") } }) t.Run("empty ip", func(t *testing.T) { - err := AddIP("em0", "", 24, "inet") + err := AddIP("em0", "", 24, defaultFamily) if err == nil { t.Error("expected error for empty ip") } }) t.Run("zero mask", func(t *testing.T) { - err := AddIP("em0", "192.168.1.10", 0, "inet") + err := AddIP("em0", "192.168.1.10", 0, defaultFamily) if err == nil { t.Error("expected error for zero mask") } @@ -743,19 +747,19 @@ func TestAddIP_InvalidInput(t *testing.T) { func TestAliasIP_InvalidInput(t *testing.T) { t.Run("empty iface", func(t *testing.T) { - err := AliasIP("", "192.168.1.20", 24, "inet") + err := AliasIP("", "192.168.1.20", 24, defaultFamily) if err == nil { t.Error("expected error for empty iface") } }) t.Run("empty ip", func(t *testing.T) { - err := AliasIP("em0", "", 24, "inet") + err := AliasIP("em0", "", 24, defaultFamily) if err == nil { t.Error("expected error for empty ip") } }) t.Run("zero mask", func(t *testing.T) { - err := AliasIP("em0", "192.168.1.20", 0, "inet") + err := AliasIP("em0", "192.168.1.20", 0, defaultFamily) if err == nil { t.Error("expected error for zero mask") } @@ -764,21 +768,136 @@ func TestAliasIP_InvalidInput(t *testing.T) { func TestDeleteIP_InvalidInput(t *testing.T) { t.Run("empty iface", func(t *testing.T) { - err := DeleteIP("", "192.168.1.10", 24, "inet") + err := DeleteIP("", "192.168.1.10", 24, defaultFamily) if err == nil { t.Error("expected error for empty iface") } }) t.Run("empty ip", func(t *testing.T) { - err := DeleteIP("em0", "", 24, "inet") + err := DeleteIP("em0", "", 24, defaultFamily) if err == nil { t.Error("expected error for empty ip") } }) t.Run("zero mask", func(t *testing.T) { - err := DeleteIP("em0", "192.168.1.10", 0, "inet") + err := DeleteIP("em0", "192.168.1.10", 0, defaultFamily) if err == nil { t.Error("expected error for zero mask") } }) } + +func TestAddRoute(t *testing.T) { + oldExecCommand := execCommand + defer func() { execCommand = oldExecCommand }() + + var calledArgs []string + execCommand = func(name string, args ...string) *exec.Cmd { + calledArgs = append([]string{name}, args...) + return exec.Command("echo") // always succeeds + } + + err := AddRoute(defaultFamily, "10.0.0.0/24", "10.0.0.1") + if err != nil { + t.Errorf("expected no error, got %v", err) + } + if len(calledArgs) == 0 || calledArgs[0] != "route" { + t.Errorf("expected route command, got %v", calledArgs) + } +} + +func TestAddRoute_Error(t *testing.T) { + oldExecCommand := execCommand + defer func() { execCommand = oldExecCommand }() + + execCommand = func(_ string, _ ...string) *exec.Cmd { + return exec.Command("false") // always fails + } + + err := AddRoute(defaultFamily, "10.0.0.0/24", "10.0.0.1") + if err == nil || !strings.Contains(err.Error(), "route add error") { + t.Errorf("expected route add error, got %v", err) + } +} + +func TestDelRoute_LastDefault(t *testing.T) { + oldListRoutes := listRoutes + oldExecCommand := execCommand + defer func() { listRoutes = oldListRoutes; execCommand = oldExecCommand }() + + listRoutes = func(family string) (string, error) { + if family == defaultFamily { + return "default 10.0.0.1\n", nil // only one default + } + if family == inet6Family { + return "default fe80::1\n2001:db8::/64 fe80::1\n", nil + } + return "", nil + } + + err := DelRoute(defaultFamily, "default") + if err == nil || !strings.Contains(err.Error(), "cannot delete the last default route") { + t.Errorf("expected last default route error, got %v", err) + } +} + +func TestDelRoute_Success(t *testing.T) { + oldListRoutes := listRoutes + oldExecCommand := execCommand + defer func() { listRoutes = oldListRoutes; execCommand = oldExecCommand }() + + listRoutes = func(family string) (string, error) { + if family == defaultFamily { + return "default 10.0.0.1\ndefault 10.0.0.2\n", nil // two defaults + } + if family == inet6Family { + return "default fe80::1\ndefault fe80::2\n", nil // two defaults + } + return "", nil + } + execCommand = func(_ string, _ ...string) *exec.Cmd { + return exec.Command("echo", "") // always succeeds + } + + err := DelRoute(defaultFamily, "default") + if err != nil { + t.Errorf("expected no error, got %v", err) + } +} + +func TestListAllRoutes(t *testing.T) { + oldListRoutes := listRoutes + defer func() { listRoutes = oldListRoutes }() + + listRoutes = func(family string) (string, error) { + if family == defaultFamily { + return "default 10.0.0.1\n10.0.0.0/24 10.0.0.1\n", nil + } + if family == inet6Family { + return "default fe80::1\n2001:db8::/64 fe80::1\n", nil + } + return "", nil + } + + out, err := ListAllRoutes("") + if err != nil { + t.Errorf("expected no error, got %v", err) + } + if !strings.Contains(out, "IPv4 routes:") || !strings.Contains(out, "IPv6 routes:") { + t.Errorf("expected both IPv4 and IPv6 routes, got %s", out) + } +} + +func TestListAllRoutes_Error(t *testing.T) { + oldListRoutes := listRoutes + defer func() { listRoutes = oldListRoutes }() + + listRoutes = func(_ string) (string, error) { + return "", errors.New("fail") + } + + _, err := ListAllRoutes(defaultFamily) + if err == nil || !strings.Contains(err.Error(), "fail") { + t.Errorf("expected error, got %v", err) + } +} diff --git a/internal/network/bareos/route.go b/internal/network/bareos/route.go index 5fdcf70..76027f1 100644 --- a/internal/network/bareos/route.go +++ b/internal/network/bareos/route.go @@ -6,13 +6,47 @@ import ( "strings" ) +var execCommand = exec.Command + +func realListRoutes(family string) (string, error) { + if family == "" { + family = defaultFamily + } + cmd := execCommand("netstat", "-rn", "-f", family) + output, err := cmd.CombinedOutput() + if err != nil { + return "", fmt.Errorf("route list error: %v, output: %s", err, string(output)) + } + return string(output), nil +} + +var listRoutes = realListRoutes + // AddRoute adds a route for the given network and gateway (IPv4 or IPv6). func AddRoute(family, network, gw string) error { fam := family if fam == "" { - fam = "inet" + fam = defaultFamily + } + cmd := execCommand("route", "-n", "add", "-"+fam, network, gw) + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("route add error: %v, output: %s", err, string(output)) + } + return nil +} + +// AddRouteWithIface adds a route for the given network, gateway, and optional interface (IPv4 or IPv6). +func AddRouteWithIface(family, network, gw, iface string) error { + fam := family + if fam == "" { + fam = defaultFamily + } + args := []string{"-n", "add", "-" + fam, network, gw} + if iface != "" { + args = append(args, "-ifp", iface) } - cmd := exec.Command("route", "-n", "add", "-"+fam, network, gw) + cmd := execCommand("route", args...) output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("route add error: %v, output: %s", err, string(output)) @@ -25,7 +59,7 @@ func AddRoute(family, network, gw string) error { func DelRoute(family, network string) error { fam := family if fam == "" { - fam = "inet" + fam = defaultFamily } if network == "default" { count, err := countDefaultRoutes(fam) @@ -36,7 +70,7 @@ func DelRoute(family, network string) error { return fmt.Errorf("cannot delete the last default route") } } - cmd := exec.Command("route", "-n", "delete", "-"+fam, network) + cmd := execCommand("route", "-n", "delete", "-"+fam, network) output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("route delete error: %v, output: %s", err, string(output)) @@ -44,23 +78,25 @@ func DelRoute(family, network string) error { return nil } -// ListRoutes lists all routes for the given family (IPv4 or IPv6). -func ListRoutes(family string) (string, error) { - fam := family - if fam == "" { - fam = "inet" +// ListAllRoutes lists both IPv4 and IPv6 routes if family is empty, otherwise lists the specified family. +func ListAllRoutes(family string) (string, error) { + if family != "" { + return listRoutes(family) } - cmd := exec.Command("netstat", "-rn", "-f", fam) - output, err := cmd.CombinedOutput() + ipv4, err := listRoutes(defaultFamily) if err != nil { - return "", fmt.Errorf("route list error: %v, output: %s", err, string(output)) + return "", fmt.Errorf("IPv4: %v", err) } - return string(output), nil + ipv6, err := listRoutes("inet6") + if err != nil { + return "", fmt.Errorf("IPv6: %v", err) + } + return fmt.Sprintf("IPv4 routes:\n%s\nIPv6 routes:\n%s", ipv4, ipv6), nil } // countDefaultRoutes returns the number of default routes for the given family. func countDefaultRoutes(family string) (int, error) { - out, err := ListRoutes(family) + out, err := listRoutes(family) if err != nil { return 0, err }