diff --git a/cmd/cloudflared/generic_service.go b/cmd/cloudflared/generic_service.go index 7fc8c5e6..17b8a5c0 100644 --- a/cmd/cloudflared/generic_service.go +++ b/cmd/cloudflared/generic_service.go @@ -3,11 +3,38 @@ package main import ( + "fmt" "os" cli "github.com/urfave/cli/v2" + + "github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil" ) func runApp(app *cli.App, graceShutdownC chan struct{}) { + app.Commands = append(app.Commands, &cli.Command{ + Name: "service", + Usage: "Manages the cloudflared system service (not supported on this operating system)", + Subcommands: []*cli.Command{ + { + Name: "install", + Usage: "Install cloudflared as a system service (not supported on this operating system)", + Action: cliutil.ConfiguredAction(installGenericService), + }, + { + Name: "uninstall", + Usage: "Uninstall the cloudflared service (not supported on this operating system)", + Action: cliutil.ConfiguredAction(uninstallGenericService), + }, + }, + }) app.Run(os.Args) } + +func installGenericService(c *cli.Context) error { + return fmt.Errorf("service installation is not supported on this operating system") +} + +func uninstallGenericService(c *cli.Context) error { + return fmt.Errorf("service uninstallation is not supported on this operating system") +} \ No newline at end of file diff --git a/diagnostic/network/collector_unix.go b/diagnostic/network/collector_unix.go index 2db2d262..ce792d6f 100644 --- a/diagnostic/network/collector_unix.go +++ b/diagnostic/network/collector_unix.go @@ -1,4 +1,4 @@ -//go:build darwin || linux +//go:build darwin || linux || freebsd || openbsd || netbsd package diagnostic diff --git a/diagnostic/network/collector_unix_test.go b/diagnostic/network/collector_unix_test.go index 5ec231a3..5743b2d9 100644 --- a/diagnostic/network/collector_unix_test.go +++ b/diagnostic/network/collector_unix_test.go @@ -1,4 +1,4 @@ -//go:build darwin || linux +//go:build darwin || linux || freebsd || openbsd || netbsd package diagnostic_test diff --git a/diagnostic/system_collector_unix.go b/diagnostic/system_collector_unix.go new file mode 100644 index 00000000..9468ffd7 --- /dev/null +++ b/diagnostic/system_collector_unix.go @@ -0,0 +1,168 @@ +//go:build !windows && !darwin && !linux + +package diagnostic + +import ( + "context" + "fmt" + "os/exec" + "runtime" + "strconv" + "strings" +) + +type SystemCollectorImpl struct { + version string +} + +func NewSystemCollectorImpl( + version string, +) *SystemCollectorImpl { + return &SystemCollectorImpl{ + version, + } +} + +func (collector *SystemCollectorImpl) Collect(ctx context.Context) (*SystemInformation, error) { + memoryInfo, memoryInfoRaw, memoryInfoErr := collectMemoryInformation(ctx) + fdInfo, fdInfoRaw, fdInfoErr := collectFileDescriptorInformation(ctx) + disks, disksRaw, diskErr := collectDiskVolumeInformationUnix(ctx) + osInfo, osInfoRaw, osInfoErr := collectOSInformationUnix(ctx) + + var memoryMaximum, memoryCurrent, fileDescriptorMaximum, fileDescriptorCurrent uint64 + var osSystem, name, osVersion, osRelease, architecture string + gerror := SystemInformationGeneralError{} + + if memoryInfoErr != nil { + gerror.MemoryInformationError = SystemInformationError{ + Err: memoryInfoErr, + RawInfo: memoryInfoRaw, + } + } else { + memoryMaximum = memoryInfo.MemoryMaximum + memoryCurrent = memoryInfo.MemoryCurrent + } + + if fdInfoErr != nil { + gerror.FileDescriptorsInformationError = SystemInformationError{ + Err: fdInfoErr, + RawInfo: fdInfoRaw, + } + } else { + fileDescriptorMaximum = fdInfo.FileDescriptorMaximum + fileDescriptorCurrent = fdInfo.FileDescriptorCurrent + } + + if diskErr != nil { + gerror.DiskVolumeInformationError = SystemInformationError{ + Err: diskErr, + RawInfo: disksRaw, + } + } + + if osInfoErr != nil { + gerror.OperatingSystemInformationError = SystemInformationError{ + Err: osInfoErr, + RawInfo: osInfoRaw, + } + } else { + osSystem = osInfo.OsSystem + name = osInfo.Name + osVersion = osInfo.OsVersion + osRelease = osInfo.OsRelease + architecture = osInfo.Architecture + } + + cloudflaredVersion := collector.version + info := NewSystemInformation( + memoryMaximum, + memoryCurrent, + fileDescriptorMaximum, + fileDescriptorCurrent, + osSystem, + name, + osVersion, + osRelease, + architecture, + cloudflaredVersion, + runtime.Version(), + runtime.GOARCH, + disks, + ) + + return info, gerror +} + +func collectMemoryInformation(ctx context.Context) (*MemoryInformation, string, error) { + // FreeBSD uses `sysctl` to retrieve memory information. + const ( + memTotalKey = "hw.physmem" + memAvailableKey = "vm.stats.vm.v_free_count" + ) + + command := exec.CommandContext(ctx, "sysctl", "-n", memTotalKey) + totalOutput, err := command.Output() + if err != nil { + return nil, "", fmt.Errorf("error retrieving output from command '%s': %w", command.String(), err) + } + + command = exec.CommandContext(ctx, "sysctl", "-n", memAvailableKey) + availableOutput, err := command.Output() + if err != nil { + return nil, "", fmt.Errorf("error retrieving output from command '%s': %w", command.String(), err) + } + + total, err := strconv.ParseUint(strings.TrimSpace(string(totalOutput)), 10, 64) + if err != nil { + return nil, string(totalOutput), fmt.Errorf("error parsing memory total: %w", err) + } + + available, err := strconv.ParseUint(strings.TrimSpace(string(availableOutput)), 10, 64) + if err != nil { + return nil, string(availableOutput), fmt.Errorf("error parsing memory available: %w", err) + } + + memoryInfo := &MemoryInformation{ + MemoryMaximum: total, + MemoryCurrent: available * 4096, // FreeBSD reports pages; multiply by page size (4K). + } + + return memoryInfo, fmt.Sprintf("Total: %s, Available: %s", totalOutput, availableOutput), nil +} + +func collectFileDescriptorInformation(ctx context.Context) (*FileDescriptorInformation, string, error) { + // FreeBSD uses `sysctl` for file descriptor limits. + const ( + fdMaxKey = "kern.maxfiles" + fdCurrentKey = "kern.openfiles" + ) + + command := exec.CommandContext(ctx, "sysctl", "-n", fdMaxKey) + maxOutput, err := command.Output() + if err != nil { + return nil, "", fmt.Errorf("error retrieving output from command '%s': %w", command.String(), err) + } + + command = exec.CommandContext(ctx, "sysctl", "-n", fdCurrentKey) + currentOutput, err := command.Output() + if err != nil { + return nil, "", fmt.Errorf("error retrieving output from command '%s': %w", command.String(), err) + } + + max, err := strconv.ParseUint(strings.TrimSpace(string(maxOutput)), 10, 64) + if err != nil { + return nil, string(maxOutput), fmt.Errorf("error parsing max file descriptors: %w", err) + } + + current, err := strconv.ParseUint(strings.TrimSpace(string(currentOutput)), 10, 64) + if err != nil { + return nil, string(currentOutput), fmt.Errorf("error parsing current file descriptors: %w", err) + } + + fdInfo := &FileDescriptorInformation{ + FileDescriptorMaximum: max, + FileDescriptorCurrent: current, + } + + return fdInfo, fmt.Sprintf("Max: %s, Current: %s", maxOutput, currentOutput), nil +} diff --git a/ingress/icmp_posix.go b/ingress/icmp_posix.go index a5353917..2c491a8e 100644 --- a/ingress/icmp_posix.go +++ b/ingress/icmp_posix.go @@ -1,5 +1,4 @@ -//go:build darwin || linux - +//go:build darwin || linux || freebsd || openbsd || netbsd package ingress // This file extracts logic shared by Linux and Darwin implementation if ICMPProxy. diff --git a/ingress/icmp_posix_test.go b/ingress/icmp_posix_test.go index 6231e1b9..ebbc4964 100644 --- a/ingress/icmp_posix_test.go +++ b/ingress/icmp_posix_test.go @@ -1,4 +1,4 @@ -//go:build darwin || linux +//go:build darwin || linux || freebsd || openbsd || netbsd package ingress