diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 95c12aaf..c40848a5 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -140,11 +140,8 @@ jobs: - name: Run cunīcu build run: make cunicu - - name: Run vet - run: make vet - - - name: Run staticcheck - run: make staticcheck + - name: Lint + run: make lint LINT_OPTS=--timeout=10m - name: Run tests if: ${{ matrix.target_arch == matrix.host_arch && matrix.target_os == matrix.host_os && matrix.host_os != 'windows' }} diff --git a/.gitignore b/.gitignore index db0d882b..da4f36db 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ completions/ dist/ docs/usage/man/*.1 docs/usage/md/*.md -cunicu +/cunicu *.log *.out *.test diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 00000000..7faa1b09 --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,124 @@ +run: + skip-files: + - ".*\\.pb\\.go$" + - ".*_grpc\\.pb\\.go$" + + skip-dirs: + - pkg/signaling/k8s/apis/cunicu/v1 + +issues: + exclude-rules: + - path: '(.+)_test\.go' + linters: + - gochecknoglobals + - path: 'test/(.+)' + text: do not define dynamic errors, use wrapped static errors instead + +linters-settings: + misspell: + locale: US + + exhaustive: + default-signifies-exhaustive: true + + gomodguard: + blocked: + modules: + - github.com/pkg/errors: + recommendations: + - errors + + tagliatelle: + case: + use-field-name: true + rules: + json: snake + yaml: snake + xml: snake + +linters: + enable: + - asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers + - bidichk # Checks for dangerous unicode character sequences + - bodyclose # checks whether HTTP response body is closed successfully + - contextcheck # check the function whether use a non-inherited context + - decorder # check declaration order and count of types, constants, variables and functions + - depguard # Go linter that checks if package imports are in a list of acceptable packages + - dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) + - dupl # Tool for code clone detection + - durationcheck # check for two durations multiplied together + - errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases + - errchkjson # Checks types passed to the json encoding functions. Reports unsupported types and optionally reports occasions, where the check for the returned error can be omitted. + - errname # Checks that sentinel errors are prefixed with the `Err` and error types are suffixed with the `Error`. + - errorlint # errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13. + - exhaustive # check exhaustiveness of enum switch statements + - exportloopref # checks for pointers to enclosing loop variables + - forcetypeassert # finds forced type assertions + - gci # Gci control golang package import order and make it always deterministic. + - gochecknoglobals # Checks that no globals are present in Go code + - gochecknoinits # Checks that no init functions are present in Go code + - gocognit # Computes and checks the cognitive complexity of functions + - goconst # Finds repeated strings that could be replaced by a constant + - gocritic # The most opinionated Go source code linter + - goerr113 # Golang linter to check the errors handling expressions + - gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification + - gofumpt # Gofumpt checks whether code was gofumpt-ed. + - goheader # Checks is file header matches to pattern + - goimports # Goimports does everything that gofmt does. Additionally it checks unused imports + - gomoddirectives # Manage the use of 'replace', 'retract', and 'excludes' directives in go.mod. + - gomodguard # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations. + - goprintffuncname # Checks that printf-like functions are named with `f` at the end + - gosec # Inspects source code for security problems + - gosimple # Linter for Go source code that specializes in simplifying a code + - govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string + - grouper # An analyzer to analyze expression groups. + - importas # Enforces consistent import aliases + - ineffassign # Detects when assignments to existing variables are not used + - misspell # Finds commonly misspelled English words in comments + - nakedret # Finds naked returns in functions greater than a specified function length + - nilerr # Finds the code that returns nil even if it checks that the error is not nil. + - nilnil # Checks that there is no simultaneous return of `nil` error and an invalid value. + - noctx # noctx finds sending http request without context.Context + - predeclared # find code that shadows one of Go's predeclared identifiers + - revive # golint replacement, finds style mistakes + - staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks + - stylecheck # Stylecheck is a replacement for golint + - tagliatelle # Checks the struct tags. + - tenv # tenv is analyzer that detects using os.Setenv instead of t.Setenv since Go1.17 + - tparallel # tparallel detects inappropriate usage of t.Parallel() method in your Go test codes + - typecheck # Like the front-end of a Go compiler, parses and type-checks Go code + - unconvert # Remove unnecessary type conversions + - unparam # Reports unused function parameters + - unused # Checks Go code for unused constants, variables, functions and types + - wastedassign # wastedassign finds wasted assignment statements + - whitespace # Tool for detection of leading and trailing whitespace + + disable: + - containedctx # containedctx is a linter that detects struct contained context.Context field + - cyclop # checks function and package cyclomatic complexity + - exhaustivestruct # Checks if all struct's fields are initialized + - forbidigo # Forbids identifiers + - funlen # Tool for detection of long functions + - gocyclo # Computes and checks the cyclomatic complexity of functions + - godot # Check if comments end in a period + - godox # Tool for detection of FIXME, TODO and other comment keywords + - gomnd # An analyzer to detect magic numbers. + - ifshort # Checks that your code uses short syntax for if-statements whenever possible + - ireturn # Accept Interfaces, Return Concrete Types + - lll # Reports long lines + - maintidx # maintidx measures the maintainability index of each function. + - makezero # Finds slice declarations with non-zero initial length + - maligned # Tool to detect Go structs that would take less memory if their fields were sorted + - nestif # Reports deeply nested if statements + - nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity + - nolintlint # Reports ill-formed or insufficient nolint directives + - paralleltest # paralleltest detects missing usage of t.Parallel() method in your Go test + - prealloc # Finds slice declarations that could potentially be preallocated + - promlinter # Check Prometheus metrics naming via promlint + - rowserrcheck # checks whether Err of rows is checked successfully + - sqlclosecheck # Checks that sql.Rows and sql.Stmt are closed. + - testpackage # linter that makes you use a separate _test package + - thelper # thelper detects golang test helpers without t.Helper() call and checks the consistency of test helpers + - varnamelen # checks that the length of a variable's name matches its scope + - wrapcheck # Checks that errors returned from external packages are wrapped + - wsl # Whitespace Linter - Forces you to use empty lines! diff --git a/Makefile b/Makefile index 483d6a79..3cfd0b2c 100644 --- a/Makefile +++ b/Makefile @@ -55,21 +55,18 @@ tidy: generate: go generate ./... -vet: - go vet --copylocks=false $(PKGS) - -staticcheck: - staticcheck $(PKGS) +lint: + golangci-lint run $(LINT_OPTS) $(PKGS) install-deps: go install google.golang.org/protobuf/cmd/protoc-gen-go@latest go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest go install github.com/onsi/ginkgo/v2/ginkgo@latest go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest - go install honnef.co/go/tools/cmd/staticcheck@latest go install github.com/amobe/gocov-merger@latest go install github.com/jandelgado/gcov2lcov@latest go install github.com/goreleaser/goreleaser@latest + go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest website: docs cd website && \ @@ -86,12 +83,12 @@ completions-dir: completions/cunicu.%: completions-dir go run ./cmd/cunicu/ completion $* > $@ -prepare: clean tidy generate vet staticcheck docs completions +prepare: clean tidy generate lint docs completions -ci: install-deps vet staticcheck tests +ci: install-deps lint tests clean: find . -name "*.out" -exec rm {} \; rm -rf cunicu lcov.info test/logs/ completions/ -.PHONY: all cunicu tests tests-watch coverage clean vet staticcheck install-deps ci completions docs prepare generate website +.PHONY: all cunicu tests tests-watch coverage clean lint install-deps ci completions docs prepare generate website diff --git a/cmd/cunicu/addresses.go b/cmd/cunicu/addresses.go index dcc3191a..3b28c200 100644 --- a/cmd/cunicu/addresses.go +++ b/cmd/cunicu/addresses.go @@ -12,32 +12,36 @@ import ( "go.uber.org/zap" ) -var ( +type addressesOptions struct { mask bool +} - addressesCmd = &cobra.Command{ +//nolint:gochecknoinits +func init() { + opts := &addressesOptions{} + cmd := &cobra.Command{ Use: "addresses", Short: "Derive IPv4 and IPv6 addresses from a WireGuard X25519 public key", Long: `cunīcu auto-configuration feature derives and assigns IPv4 and IPv6 addresses based on the public key of the WireGuard interface. This sub-command accepts a WireGuard public key on the standard input and prints out the calculated IP addresses on the standard output. `, - Run: addresses, + Run: func(cmd *cobra.Command, args []string) { + addresses(cmd, args, opts) + }, Example: `$ wg genkey | wg pubkey | cunicu addresses fc2f:9a4d:777f:7a97:8197:4a5d:1d1b:ed79 10.237.119.127`, Args: cobra.ArbitraryArgs, ValidArgsFunction: cobra.NoFileCompletions, } -) -func init() { - pf := addressesCmd.PersistentFlags() - pf.BoolVarP(&mask, "mask", "m", false, "Print CIDR mask") + pf := cmd.PersistentFlags() + pf.BoolVarP(&opts.mask, "mask", "m", false, "Print CIDR mask") - rootCmd.AddCommand(addressesCmd) + rootCmd.AddCommand(cmd) } -func addresses(cmd *cobra.Command, args []string) { +func addresses(_ *cobra.Command, args []string, opts *addressesOptions) { logger := zap.L() keyB64, err := io.ReadAll(os.Stdin) @@ -64,7 +68,7 @@ func addresses(cmd *cobra.Command, args []string) { q := key.IPAddress(*p) - if mask { + if opts.mask { fmt.Println(q.String()) } else { fmt.Println(q.IP.String()) diff --git a/cmd/cunicu/client.go b/cmd/cunicu/client.go index 04c68f3d..694214e9 100644 --- a/cmd/cunicu/client.go +++ b/cmd/cunicu/client.go @@ -9,8 +9,8 @@ import ( ) var ( - rpcClient *rpc.Client - rpcSockPath string + rpcClient *rpc.Client //nolint:gochecknoglobals + rpcSockPath string //nolint:gochecknoglobals ) func addClientCommand(rcmd, cmd *cobra.Command) { diff --git a/cmd/cunicu/cmd.go b/cmd/cunicu/cmd.go index 4ce277e1..7b4c8dd2 100644 --- a/cmd/cunicu/cmd.go +++ b/cmd/cunicu/cmd.go @@ -1,11 +1,7 @@ +//nolint:gci package main import ( - // Signaling backends - _ "github.com/stv0g/cunicu/pkg/signaling/grpc" - _ "github.com/stv0g/cunicu/pkg/signaling/inprocess" - _ "github.com/stv0g/cunicu/pkg/signaling/k8s" - // Daemon features _ "github.com/stv0g/cunicu/pkg/daemon/feature/autocfg" _ "github.com/stv0g/cunicu/pkg/daemon/feature/cfgsync" @@ -14,4 +10,9 @@ import ( _ "github.com/stv0g/cunicu/pkg/daemon/feature/hsync" _ "github.com/stv0g/cunicu/pkg/daemon/feature/pdisc" _ "github.com/stv0g/cunicu/pkg/daemon/feature/rtsync" + + // Signaling backends + _ "github.com/stv0g/cunicu/pkg/signaling/grpc" + _ "github.com/stv0g/cunicu/pkg/signaling/inprocess" + _ "github.com/stv0g/cunicu/pkg/signaling/k8s" ) diff --git a/cmd/cunicu/completions.go b/cmd/cunicu/completions.go index b467f15c..a4e13c13 100644 --- a/cmd/cunicu/completions.go +++ b/cmd/cunicu/completions.go @@ -8,6 +8,7 @@ import ( rpcproto "github.com/stv0g/cunicu/pkg/proto/rpc" ) +//nolint:gochecknoglobals var BooleanCompletions = cobra.FixedCompletions([]string{"true", "false"}, cobra.ShellCompDirectiveNoFileComp) func interfaceValidArgs(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { @@ -15,7 +16,7 @@ func interfaceValidArgs(cmd *cobra.Command, args []string, toComplete string) ([ if err := rpcConnect(cmd, args); err != nil { return nil, cobra.ShellCompDirectiveError } - defer rpcDisconnect(cmd, args) + defer rpcDisconnect(cmd, args) //nolint:errcheck p := &rpcproto.GetStatusParams{} diff --git a/cmd/cunicu/config.go b/cmd/cunicu/config.go index 8bdba82e..7c3e2284 100644 --- a/cmd/cunicu/config.go +++ b/cmd/cunicu/config.go @@ -7,24 +7,23 @@ import ( "strings" "github.com/spf13/cobra" - "go.uber.org/zap" - "golang.org/x/exp/maps" - "golang.org/x/exp/slices" - "github.com/stv0g/cunicu/pkg/config" "github.com/stv0g/cunicu/pkg/proto" rpcproto "github.com/stv0g/cunicu/pkg/proto/rpc" + "go.uber.org/zap" + "golang.org/x/exp/maps" + "golang.org/x/exp/slices" ) -var ( - configCmd = &cobra.Command{ +func init() { //nolint:gochecknoinits + cmd := &cobra.Command{ Use: "config", Short: "Manage configuration of a running cunīcu daemon.", Long: ` `, } - setCmd = &cobra.Command{ + setCmd := &cobra.Command{ Use: "set key value", Short: "Update the value of a configuration setting", Run: set, @@ -32,7 +31,7 @@ var ( ValidArgsFunction: validConfigSettings, } - getCmd = &cobra.Command{ + getCmd := &cobra.Command{ Use: "get [key]", Short: "Get current value of a configuration setting", Run: get, @@ -40,19 +39,18 @@ var ( ValidArgsFunction: validConfigSettings, } - reloadCmd = &cobra.Command{ + reloadCmd := &cobra.Command{ Use: "reload", Short: "Reload the configuration of the cunīcu daemon", RunE: reload, Args: cobra.NoArgs, } -) -func init() { - addClientCommand(rootCmd, configCmd) - configCmd.AddCommand(setCmd) - configCmd.AddCommand(getCmd) - configCmd.AddCommand(reloadCmd) + cmd.AddCommand(setCmd) + cmd.AddCommand(getCmd) + cmd.AddCommand(reloadCmd) + + addClientCommand(rootCmd, cmd) } func getCompletions(typ reflect.Type, haveCompleted, toComplete string) ([]string, cobra.ShellCompDirective) { diff --git a/cmd/cunicu/daemon.go b/cmd/cunicu/daemon.go index 4ae4837e..62225acf 100644 --- a/cmd/cunicu/daemon.go +++ b/cmd/cunicu/daemon.go @@ -6,59 +6,57 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - "go.uber.org/zap" - "go.uber.org/zap/zapio" - "github.com/stv0g/cunicu/pkg/config" "github.com/stv0g/cunicu/pkg/daemon" "github.com/stv0g/cunicu/pkg/rpc" "github.com/stv0g/cunicu/pkg/util/terminal" + "go.uber.org/zap" + "go.uber.org/zap/zapio" ) -var ( - daemonCmd = &cobra.Command{ +func init() { //nolint:gochecknoinits + cmd := &cobra.Command{ Use: "daemon [interface-names...]", Short: "Start the daemon", Example: `$ cunicu daemon -U -x mysecretpass wg0`, - Run: daemonRun, ValidArgsFunction: interfaceValidArgs, } - cfg *config.Config -) - -func init() { - f := daemonCmd.Flags() + f := cmd.Flags() f.SortFlags = false - pf := daemonCmd.PersistentFlags() + pf := cmd.PersistentFlags() - cfg = config.New(pf) + cfg := config.New(pf) - if err := daemonCmd.RegisterFlagCompletionFunc("ice-candidate-type", cobra.FixedCompletions([]string{"host", "srflx", "prflx", "relay"}, cobra.ShellCompDirectiveNoFileComp)); err != nil { + if err := cmd.RegisterFlagCompletionFunc("ice-candidate-type", cobra.FixedCompletions([]string{"host", "srflx", "prflx", "relay"}, cobra.ShellCompDirectiveNoFileComp)); err != nil { panic(err) } - if err := daemonCmd.RegisterFlagCompletionFunc("ice-network-type", cobra.FixedCompletions([]string{"udp4", "udp6", "tcp4", "tcp6"}, cobra.ShellCompDirectiveNoFileComp)); err != nil { + if err := cmd.RegisterFlagCompletionFunc("ice-network-type", cobra.FixedCompletions([]string{"udp4", "udp6", "tcp4", "tcp6"}, cobra.ShellCompDirectiveNoFileComp)); err != nil { panic(err) } - if err := daemonCmd.MarkPersistentFlagFilename("config", "yaml", "json"); err != nil { + if err := cmd.MarkPersistentFlagFilename("config", "yaml", "json"); err != nil { panic(err) } pf.VisitAll(func(f *pflag.Flag) { if f.Value.Type() == "bool" { - if err := daemonCmd.RegisterFlagCompletionFunc(f.Name, BooleanCompletions); err != nil { + if err := cmd.RegisterFlagCompletionFunc(f.Name, BooleanCompletions); err != nil { panic(err) } } }) - rootCmd.AddCommand(daemonCmd) + cmd.Run = func(cmd *cobra.Command, args []string) { + daemonRun(cmd, args, cfg) + } + + rootCmd.AddCommand(cmd) } -func daemonRun(cmd *cobra.Command, args []string) { +func daemonRun(_ *cobra.Command, args []string, cfg *config.Config) { if _, err := io.WriteString(os.Stdout, Banner(color)); err != nil { logger.Fatal("Failed to write banner", zap.Error(err)) } diff --git a/cmd/cunicu/docs.go b/cmd/cunicu/docs.go index 03148985..412072ae 100644 --- a/cmd/cunicu/docs.go +++ b/cmd/cunicu/docs.go @@ -13,63 +13,68 @@ import ( "gopkg.in/yaml.v3" ) -var ( +type docsOptions struct { outputDir string withFrontMatter bool +} - docsCmd = &cobra.Command{ +func init() { //nolint:gochecknoinits + opts := &docsOptions{} + cmd := &cobra.Command{ Use: "docs", Short: "Generate documentation for the cunīcu commands", Long: `When used without a sub-command, both the Markdown documentation and Man-pages will be generated.`, Hidden: true, Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { - if err := docsMarkdown(cmd, args); err != nil { + if err := docsMarkdown(cmd, args, opts); err != nil { logger.Fatal("Failed to generate markdown docs", zap.Error(err)) } - if err := docsManpage(cmd, args); err != nil { + if err := docsManpage(cmd, args, opts); err != nil { logger.Fatal("Failed to generate Manpage docs", zap.Error(err)) } }, } - docsMarkdownCmd = &cobra.Command{ + docsMarkdownCmd := &cobra.Command{ Use: "markdown", Short: "Generate markdown docs", - RunE: docsMarkdown, - Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + return docsMarkdown(cmd, args, opts) + }, + Args: cobra.NoArgs, } - docsManpageCmd = &cobra.Command{ + docsManpageCmd := &cobra.Command{ Use: "man", Short: "Generate manpages", - RunE: docsManpage, - Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + return docsManpage(cmd, args, opts) + }, + Args: cobra.NoArgs, } -) -func init() { - rootCmd.AddCommand(docsCmd) + rootCmd.AddCommand(cmd) - docsCmd.AddCommand(docsManpageCmd) - docsCmd.AddCommand(docsMarkdownCmd) + cmd.AddCommand(docsManpageCmd) + cmd.AddCommand(docsMarkdownCmd) - pf := docsCmd.PersistentFlags() - pf.StringVar(&outputDir, "output-dir", "./docs/usage", "Output directory of generated documentation") - pf.BoolVar(&withFrontMatter, "with-frontmatter", false, "Prepend a frontmatter to the generated Markdown files as used by our static website generator") + pf := cmd.PersistentFlags() + pf.StringVar(&opts.outputDir, "output-dir", "./docs/usage", "Output directory of generated documentation") + pf.BoolVar(&opts.withFrontMatter, "with-frontmatter", false, "Prepend a frontmatter to the generated Markdown files as used by our static website generator") } -func docsMarkdown(cmd *cobra.Command, args []string) error { - dir := filepath.Join(outputDir, "md") +func docsMarkdown(_ *cobra.Command, _ []string, opts *docsOptions) error { + dir := filepath.Join(opts.outputDir, "md") //#nosec G301 -- Doc directories must be world readable - if err := os.MkdirAll(dir, 0755); err != nil { + if err := os.MkdirAll(dir, 0o755); err != nil { return fmt.Errorf("failed to create directory: %w", err) } filePrepender := func(path string) string { - if !withFrontMatter { + if !opts.withFrontMatter { return "" } @@ -112,11 +117,11 @@ func docsMarkdown(cmd *cobra.Command, args []string) error { return doc.GenMarkdownTreeCustom(rootCmd, dir, filePrepender, linkHandler) } -func docsManpage(cmd *cobra.Command, args []string) error { - dir := filepath.Join(outputDir, "man") +func docsManpage(_ *cobra.Command, _ []string, opts *docsOptions) error { + dir := filepath.Join(opts.outputDir, "man") //#nosec G301 -- Doc directories must be world readable - if err := os.MkdirAll(dir, 0755); err != nil { + if err := os.MkdirAll(dir, 0o755); err != nil { return fmt.Errorf("failed to create directory: %w", err) } diff --git a/cmd/cunicu/invite.go b/cmd/cunicu/invite.go index c5bfa89b..6c6f8147 100644 --- a/cmd/cunicu/invite.go +++ b/cmd/cunicu/invite.go @@ -7,37 +7,40 @@ import ( "os" "github.com/spf13/cobra" - "go.uber.org/zap" - "golang.zx2c4.com/wireguard/wgctrl/wgtypes" - "github.com/stv0g/cunicu/pkg/crypto" + rpcproto "github.com/stv0g/cunicu/pkg/proto/rpc" "github.com/stv0g/cunicu/pkg/util/terminal" "github.com/stv0g/cunicu/pkg/wg" - - rpcproto "github.com/stv0g/cunicu/pkg/proto/rpc" + "go.uber.org/zap" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) -var inviteCmd = &cobra.Command{ - Use: "invite [interface]", - Short: "Add a new peer to the local daemon configuration and return the required configuration for this new peer", - Run: invite, - Args: cobra.ExactArgs(1), - ValidArgsFunction: interfaceValidArgs, +type inviteOptions struct { + listenPort int + qrCode bool } -var listenPort int -var qrCode bool +func init() { //nolint:gochecknoinits + opts := &inviteOptions{} + cmd := &cobra.Command{ + Use: "invite [interface]", + Short: "Add a new peer to the local daemon configuration and return the required configuration for this new peer", + Run: func(cmd *cobra.Command, args []string) { + invite(cmd, args, opts) + }, + Args: cobra.ExactArgs(1), + ValidArgsFunction: interfaceValidArgs, + } -func init() { - addClientCommand(rootCmd, inviteCmd) + addClientCommand(rootCmd, cmd) - pf := inviteCmd.PersistentFlags() + pf := cmd.PersistentFlags() - pf.IntVarP(&listenPort, "listen-port", "L", wg.DefaultPort, "Listen port for generated config") - pf.BoolVarP(&qrCode, "qr-code", "Q", false, "Show config as QR code in terminal") + pf.IntVarP(&opts.listenPort, "listen-port", "L", wg.DefaultPort, "Listen port for generated config") + pf.BoolVarP(&opts.qrCode, "qr-code", "Q", false, "Show config as QR code in terminal") } -func invite(cmd *cobra.Command, args []string) { +func invite(_ *cobra.Command, args []string, opts *inviteOptions) { sk, err := crypto.GeneratePrivateKey() if err != nil { logger.Fatal("Failed to generate private key", zap.Error(err)) @@ -66,7 +69,7 @@ func invite(cmd *cobra.Command, args []string) { cfg := wg.Config{ Config: wgtypes.Config{ PrivateKey: (*wgtypes.Key)(&sk), - ListenPort: &listenPort, + ListenPort: &opts.listenPort, Peers: []wgtypes.PeerConfig{cfgPeer}, }, Address: []net.IPNet{}, @@ -77,7 +80,7 @@ func invite(cmd *cobra.Command, args []string) { cfg.PeerEndpoints = []string{addPeerResp.Invitation.Endpoint} } - if qrCode { + if opts.qrCode { buf := &bytes.Buffer{} if err := cfg.Dump(buf); err != nil { logger.Fatal("Failed to dump config", zap.Error(err)) diff --git a/cmd/cunicu/monitor.go b/cmd/cunicu/monitor.go index b069a012..3cb859de 100644 --- a/cmd/cunicu/monitor.go +++ b/cmd/cunicu/monitor.go @@ -8,23 +8,31 @@ import ( "google.golang.org/protobuf/encoding/protojson" ) -var monitorCmd = &cobra.Command{ - Use: "monitor", - Short: "Monitor the cunīcu daemon for events", - Run: monitor, - Args: cobra.NoArgs, +type monitorOptions struct { + format config.OutputFormat } -var format config.OutputFormat = config.OutputFormatHuman +func init() { //nolint:gochecknoinits + opts := &monitorOptions{ + format: config.OutputFormatHuman, + } -func init() { - addClientCommand(rootCmd, monitorCmd) + cmd := &cobra.Command{ + Use: "monitor", + Short: "Monitor the cunīcu daemon for events", + Run: func(cmd *cobra.Command, args []string) { + monitor(cmd, args, opts) + }, + Args: cobra.NoArgs, + } - f := monitorCmd.PersistentFlags() - f.VarP(&format, "format", "f", "Output `format` (one of: json, logger, human)") + addClientCommand(rootCmd, cmd) + + f := cmd.PersistentFlags() + f.VarP(&opts.format, "format", "f", "Output `format` (one of: json, logger, human)") } -func monitor(cmd *cobra.Command, args []string) { +func monitor(_ *cobra.Command, _ []string, opts *monitorOptions) { signals := util.SetupSignals() logger := logger.Named("events") @@ -41,7 +49,7 @@ out: break out case evt := <-rpcClient.Events: - switch format { + switch opts.format { case config.OutputFormatJSON: buf, err := mo.Marshal(evt) if err != nil { diff --git a/cmd/cunicu/relay.go b/cmd/cunicu/relay.go index 67969000..6538a654 100644 --- a/cmd/cunicu/relay.go +++ b/cmd/cunicu/relay.go @@ -4,17 +4,21 @@ import ( "net" "github.com/spf13/cobra" + grpcx "github.com/stv0g/cunicu/pkg/signaling/grpc" + "github.com/stv0g/cunicu/pkg/util" "go.uber.org/zap" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" - - "github.com/stv0g/cunicu/pkg/util" - - grpcx "github.com/stv0g/cunicu/pkg/signaling/grpc" ) -var ( - relayCmd = &cobra.Command{ +type relayOptions struct { + listenAddress string + secure bool +} + +func init() { //nolint:gochecknoinits + opts := &relayOptions{} + cmd := &cobra.Command{ Use: "relay URL...", Short: "Start relay API server", Long: `This command starts a gRPC server providing cunicu agents with a list of available STUN and TURN servers. @@ -36,29 +40,29 @@ The command expects a list of STUN or TURN URLs according to RFC7065/RFC7064 wit - Example: turn:user1:pass1@server.com `, Example: `relay turn:server.com?secret=rest-api-secret&ttl=1h`, - Run: relay, - Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + relay(cmd, args, opts) + }, + Args: cobra.NoArgs, } -) -func init() { - pf := relayCmd.PersistentFlags() - pf.StringVarP(&listenAddress, "listen", "L", ":8080", "listen address") - pf.BoolVarP(&secure, "secure", "S", false, "listen with TLS") + pf := cmd.PersistentFlags() + pf.StringVarP(&opts.listenAddress, "listen", "L", ":8080", "listen address") + pf.BoolVarP(&opts.secure, "secure", "S", false, "listen with TLS") - rootCmd.AddCommand(relayCmd) + rootCmd.AddCommand(cmd) } -func relay(cmd *cobra.Command, args []string) { - l, err := net.Listen("tcp", listenAddress) +func relay(_ *cobra.Command, args []string, opts *relayOptions) { + l, err := net.Listen("tcp", opts.listenAddress) if err != nil { logger.Fatal("Failed to listen", zap.Error(err)) } // Disable TLS - opts := []grpc.ServerOption{} - if !secure { - opts = append(opts, grpc.Creds(insecure.NewCredentials())) + svrOpts := []grpc.ServerOption{} + if !opts.secure { + svrOpts = append(svrOpts, grpc.Creds(insecure.NewCredentials())) } relays, err := grpcx.NewRelayInfos(args) @@ -66,7 +70,7 @@ func relay(cmd *cobra.Command, args []string) { logger.Fatal("Failed to parse relays", zap.Error(err)) } - svr, err := grpcx.NewRelayAPIServer(relays, opts...) + svr, err := grpcx.NewRelayAPIServer(relays, svrOpts...) if err != nil { logger.Fatal("Failed to start gRPC server", zap.Error(err)) } @@ -81,7 +85,7 @@ func relay(cmd *cobra.Command, args []string) { } }() - logger.Info("Starting gRPC relay API server", zap.String("address", listenAddress)) + logger.Info("Starting gRPC relay API server", zap.String("address", opts.listenAddress)) if err := svr.Serve(l); err != nil { logger.Fatal("Failed to start gRPC server", zap.Error(err)) diff --git a/cmd/cunicu/restart.go b/cmd/cunicu/restart.go index 93914d36..3bd4948c 100644 --- a/cmd/cunicu/restart.go +++ b/cmd/cunicu/restart.go @@ -8,15 +8,15 @@ import ( "github.com/stv0g/cunicu/pkg/proto" ) -var restartCmd = &cobra.Command{ - Use: "restart", - Short: "Restart the cunīcu daemon", - RunE: restart, - Args: cobra.NoArgs, -} +func init() { //nolint:gochecknoinits + cmd := &cobra.Command{ + Use: "restart", + Short: "Restart the cunīcu daemon", + RunE: restart, + Args: cobra.NoArgs, + } -func init() { - addClientCommand(rootCmd, restartCmd) + addClientCommand(rootCmd, cmd) } func restart(cmd *cobra.Command, args []string) error { diff --git a/cmd/cunicu/root.go b/cmd/cunicu/root.go index 627812bc..31915298 100644 --- a/cmd/cunicu/root.go +++ b/cmd/cunicu/root.go @@ -50,10 +50,19 @@ Code & Issues: ` ) -var ( - logger *zap.Logger +type options struct { + logLevel config.Level + verbosityLevel int + logFile string + colorMode string +} - rootCmd = &cobra.Command{ +var ( + logger *zap.Logger //nolint:gochecknoglobals + color bool //nolint:gochecknoglobals + stdout io.Writer //nolint:gochecknoglobals + + rootCmd = &cobra.Command{ //nolint:gochecknoglobals Use: "cunicu", Short: "cunīcu is a user-space daemon managing WireGuard® interfaces to establish peer-to-peer connections in harsh network environments.", Long: Banner(terminal.IsATTY(os.Stdout)) + `cunīcu is a user-space daemon managing WireGuard® interfaces to @@ -69,50 +78,49 @@ in which WireGuard kernel support has not landed yet.`, Args: cobra.NoArgs, ValidArgsFunction: cobra.NoFileCompletions, } - - logLevel = config.Level{Level: zapcore.InfoLevel} - verbosityLevel int - logFile string - colorMode string - color bool - stdout io.Writer ) -func init() { +func init() { //nolint:gochecknoinits + opts := &options{ + logLevel: config.Level{ + Level: zapcore.InfoLevel, + }, + } + rootCmd.SetUsageTemplate(usageTemplate) - cobra.OnInitialize(onInitialize) + cobra.OnInitialize(func() { + onInitialize(opts) + }) f := rootCmd.Flags() f.SortFlags = false pf := rootCmd.PersistentFlags() - pf.IntVarP(&verbosityLevel, "verbose", "v", 0, "verbosity level") - pf.VarP(&logLevel, "log-level", "d", "log level (one of: debug, info, warn, error, dpanic, panic, and fatal)") - pf.StringVarP(&logFile, "log-file", "l", "", "path of a file to write logs to") - pf.StringVarP(&colorMode, "color", "q", "auto", "Enable colorization of output (one of: auto, always, never)") + pf.IntVarP(&opts.verbosityLevel, "verbose", "v", 0, "verbosity level") + pf.VarP(&opts.logLevel, "log-level", "d", "log level (one of: debug, info, warn, error, dpanic, panic, and fatal)") + pf.StringVarP(&opts.logFile, "log-file", "l", "", "path of a file to write logs to") + pf.StringVarP(&opts.colorMode, "color", "q", "auto", "Enable colorization of output (one of: auto, always, never)") - if err := daemonCmd.RegisterFlagCompletionFunc("log-level", cobra.FixedCompletions([]string{"debug", "info", "warn", "error", "dpanic", "panic", "fatal"}, cobra.ShellCompDirectiveNoFileComp)); err != nil { + if err := rootCmd.RegisterFlagCompletionFunc("log-level", cobra.FixedCompletions([]string{"debug", "info", "warn", "error", "dpanic", "panic", "fatal"}, cobra.ShellCompDirectiveNoFileComp)); err != nil { panic(err) } - if err := daemonCmd.RegisterFlagCompletionFunc("color", cobra.FixedCompletions([]string{"auto", "always", "never"}, cobra.ShellCompDirectiveNoFileComp)); err != nil { + if err := rootCmd.RegisterFlagCompletionFunc("color", cobra.FixedCompletions([]string{"auto", "always", "never"}, cobra.ShellCompDirectiveNoFileComp)); err != nil { panic(err) } - flagName := "output" - - if err := daemonCmd.MarkFlagFilename(flagName, "yaml", "json"); err != nil { + if err := rootCmd.MarkFlagFilename("output", "yaml", "json"); err != nil { panic(err) } } -func onInitialize() { +func onInitialize(opts *options) { // Initialize PRNG util.SetupRand() // Handle color output - switch colorMode { + switch opts.colorMode { case "auto": color = terminal.IsATTY(os.Stdout) case "always": @@ -130,12 +138,12 @@ func onInitialize() { outputPaths := []string{"stdout"} errOutputPaths := []string{"stderr"} - if logFile != "" { - outputPaths = append(outputPaths, logFile) - errOutputPaths = append(errOutputPaths, logFile) + if opts.logFile != "" { + outputPaths = append(outputPaths, opts.logFile) + errOutputPaths = append(errOutputPaths, opts.logFile) } - logger = log.SetupLogging(logLevel.Level, verbosityLevel, outputPaths, errOutputPaths, color) + logger = log.SetupLogging(opts.logLevel.Level, opts.verbosityLevel, outputPaths, errOutputPaths, color) } func main() { diff --git a/cmd/cunicu/selfupdate.go b/cmd/cunicu/selfupdate.go index f7acbcd6..210ced26 100644 --- a/cmd/cunicu/selfupdate.go +++ b/cmd/cunicu/selfupdate.go @@ -12,20 +12,23 @@ import ( "go.uber.org/zap" ) -var ( +type selfUpdateOptions struct { output string +} - selfUpdateCmd = &cobra.Command{ +func init() { //nolint:gochecknoinits + opts := &selfUpdateOptions{} + cmd := &cobra.Command{ Use: "selfupdate", Short: "Update the cunīcu binary", Long: `Downloads the latest stable release of cunīcu from GitHub and replaces the currently running binary. After download, the authenticity of the binary is verified using the GPG signature on the release files.`, - Run: selfUpdate, + Run: func(cmd *cobra.Command, args []string) { + selfUpdate(cmd, args, opts) + }, } -) -func init() { - rootCmd.AddCommand(selfUpdateCmd) + rootCmd.AddCommand(cmd) selfPath, err := os.Executable() if err != nil { @@ -37,20 +40,20 @@ func init() { self = "cunicu" } - flags := selfUpdateCmd.Flags() - flags.StringVarP(&output, "output", "o", self, "Save the downloaded file as `filename`") + flags := cmd.Flags() + flags.StringVarP(&opts.output, "output", "o", self, "Save the downloaded file as `filename`") } -func selfUpdate(cmd *cobra.Command, args []string) { +func selfUpdate(_ *cobra.Command, _ []string, opts *selfUpdateOptions) { logger := logger.Named("self-update") - rel, err := selfupdate.SelfUpdate(output, logger) + rel, err := selfupdate.SelfUpdate(opts.output, logger) if err != nil { logger.Fatal("Self-update failed", zap.Error(err)) } logger.Info("Successfully updated cunicu", zap.String("version", rel.Version), - zap.String("filename", output), + zap.String("filename", opts.output), ) } diff --git a/cmd/cunicu/signal.go b/cmd/cunicu/signal.go index 965a879d..57dc1719 100644 --- a/cmd/cunicu/signal.go +++ b/cmd/cunicu/signal.go @@ -4,47 +4,51 @@ import ( "net" "github.com/spf13/cobra" + grpcx "github.com/stv0g/cunicu/pkg/signaling/grpc" + "github.com/stv0g/cunicu/pkg/util" "go.uber.org/zap" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" - - grpcx "github.com/stv0g/cunicu/pkg/signaling/grpc" - "github.com/stv0g/cunicu/pkg/util" ) -var ( - signalCmd = &cobra.Command{ - Use: "signal", - Short: "Start gRPC signaling server", - Run: signal, - Args: cobra.NoArgs, - } - +type signalOptions struct { listenAddress string - secure = false -) - -func init() { - pf := signalCmd.PersistentFlags() - pf.StringVarP(&listenAddress, "listen", "L", ":8080", "listen address") - pf.BoolVarP(&secure, "secure", "S", false, "listen with TLS") - - rootCmd.AddCommand(signalCmd) + secure bool } -func signal(cmd *cobra.Command, args []string) { - l, err := net.Listen("tcp", listenAddress) +func init() { //nolint:gochecknoinits + opts := &signalOptions{ + secure: false, + } + cmd := &cobra.Command{ + Use: "signal", + Short: "Start gRPC signaling server", + Run: func(cmd *cobra.Command, args []string) { + signal(cmd, args, opts) + }, + Args: cobra.NoArgs, + } + + pf := cmd.PersistentFlags() + pf.StringVarP(&opts.listenAddress, "listen", "L", ":8080", "listen address") + pf.BoolVarP(&opts.secure, "secure", "S", false, "listen with TLS") + + rootCmd.AddCommand(cmd) +} + +func signal(_ *cobra.Command, _ []string, opts *signalOptions) { + l, err := net.Listen("tcp", opts.listenAddress) if err != nil { logger.Fatal("Failed to listen", zap.Error(err)) } // Disable TLS - opts := []grpc.ServerOption{} - if !secure { - opts = append(opts, grpc.Creds(insecure.NewCredentials())) + svrOpts := []grpc.ServerOption{} + if !opts.secure { + svrOpts = append(svrOpts, grpc.Creds(insecure.NewCredentials())) } - svr := grpcx.NewSignalingServer(opts...) + svr := grpcx.NewSignalingServer(svrOpts...) go func() { for sig := range util.SetupSignals() { @@ -56,7 +60,7 @@ func signal(cmd *cobra.Command, args []string) { } }() - logger.Info("Starting gRPC signaling server", zap.String("address", listenAddress)) + logger.Info("Starting gRPC signaling server", zap.String("address", opts.listenAddress)) if err := svr.Serve(l); err != nil { logger.Fatal("Failed to start gRPC server", zap.Error(err)) diff --git a/cmd/cunicu/status.go b/cmd/cunicu/status.go index f37eb023..2ed60994 100644 --- a/cmd/cunicu/status.go +++ b/cmd/cunicu/status.go @@ -6,38 +6,45 @@ import ( "github.com/spf13/cobra" "github.com/stv0g/cunicu/pkg/config" "github.com/stv0g/cunicu/pkg/crypto" + "github.com/stv0g/cunicu/pkg/log" + rpcproto "github.com/stv0g/cunicu/pkg/proto/rpc" "go.uber.org/zap" "google.golang.org/protobuf/encoding/protojson" - - rpcproto "github.com/stv0g/cunicu/pkg/proto/rpc" ) -var ( +type statusOptions struct { indent bool + format config.OutputFormat +} - statusCmd = &cobra.Command{ - Use: "status [interface-name [peer-public-key]]", - Short: "Show current status of the cunīcu daemon, its interfaces and peers", - Aliases: []string{"show"}, - Run: status, +func init() { //nolint:gochecknoinits + opts := &statusOptions{ + format: config.OutputFormatHuman, + } + + cmd := &cobra.Command{ + Use: "status [interface-name [peer-public-key]]", + Short: "Show current status of the cunīcu daemon, its interfaces and peers", + Aliases: []string{"show"}, + Run: func(cmd *cobra.Command, args []string) { + status(cmd, args, opts) + }, Args: cobra.RangeArgs(0, 2), ValidArgsFunction: interfaceValidArgs, } -) -func init() { - pf := statusCmd.PersistentFlags() - pf.VarP(&format, "format", "f", "Output `format` (one of: human, json)") - pf.BoolVarP(&indent, "indent", "i", true, "Format and indent JSON ouput") + pf := cmd.PersistentFlags() + pf.VarP(&opts.format, "format", "f", "Output `format` (one of: human, json)") + pf.BoolVarP(&opts.indent, "indent", "i", true, "Format and indent JSON output") - if err := statusCmd.RegisterFlagCompletionFunc("format", cobra.FixedCompletions([]string{"human", "json"}, cobra.ShellCompDirectiveNoFileComp)); err != nil { + if err := cmd.RegisterFlagCompletionFunc("format", cobra.FixedCompletions([]string{"human", "json"}, cobra.ShellCompDirectiveNoFileComp)); err != nil { panic(err) } - addClientCommand(rootCmd, statusCmd) + addClientCommand(rootCmd, cmd) } -func status(cmd *cobra.Command, args []string) { +func status(_ *cobra.Command, args []string, opts *statusOptions) { p := &rpcproto.GetStatusParams{} if len(args) > 0 { @@ -57,7 +64,7 @@ func status(cmd *cobra.Command, args []string) { logger.Fatal("Failed to retrieve status from daemon", zap.Error(err)) } - switch format { + switch opts.format { case config.OutputFormatJSON: mo := protojson.MarshalOptions{ AllowPartial: true, @@ -65,7 +72,7 @@ func status(cmd *cobra.Command, args []string) { EmitUnpopulated: false, } - if indent { + if opts.indent { mo.Multiline = true mo.Indent = " " } @@ -80,8 +87,10 @@ func status(cmd *cobra.Command, args []string) { } case config.OutputFormatHuman: - if err := sts.Dump(stdout, verbosityLevel); err != nil { + if err := sts.Dump(stdout, log.Verbosity.Level()); err != nil { logger.Fatal("Failed to write to stdout", zap.Error(err)) } + + case config.OutputFormatLogger: } } diff --git a/cmd/cunicu/stop.go b/cmd/cunicu/stop.go index d0998148..e6a8f755 100644 --- a/cmd/cunicu/stop.go +++ b/cmd/cunicu/stop.go @@ -8,15 +8,15 @@ import ( "github.com/stv0g/cunicu/pkg/proto" ) -var stopCmd = &cobra.Command{ - Use: "stop", - Short: "Shutdown the cunīcu daemon", - RunE: stop, - Args: cobra.NoArgs, -} +func init() { //nolint:gochecknoinits + cmd := &cobra.Command{ + Use: "stop", + Short: "Shutdown the cunīcu daemon", + RunE: stop, + Args: cobra.NoArgs, + } -func init() { - addClientCommand(rootCmd, stopCmd) + addClientCommand(rootCmd, cmd) } func stop(cmd *cobra.Command, args []string) error { diff --git a/cmd/cunicu/sync.go b/cmd/cunicu/sync.go index ded0b59b..46cec9ae 100644 --- a/cmd/cunicu/sync.go +++ b/cmd/cunicu/sync.go @@ -8,18 +8,16 @@ import ( "github.com/stv0g/cunicu/pkg/proto" ) -var ( - syncCmd = &cobra.Command{ +func init() { //nolint:gochecknoinits + cmd := &cobra.Command{ Use: "sync", Short: "Synchronize cunīcu daemon state", Long: "Synchronizes the internal daemon state with kernel routes, interfaces and addresses", RunE: sync, Args: cobra.NoArgs, } -) -func init() { - addClientCommand(rootCmd, syncCmd) + addClientCommand(rootCmd, cmd) } func sync(cmd *cobra.Command, args []string) error { diff --git a/cmd/cunicu/version.go b/cmd/cunicu/version.go index ae858712..5b36dfba 100644 --- a/cmd/cunicu/version.go +++ b/cmd/cunicu/version.go @@ -13,29 +13,36 @@ import ( "google.golang.org/protobuf/encoding/protojson" ) -var ( - versionCmd = &cobra.Command{ +type versionOptions struct { + short bool + format config.OutputFormat +} + +func init() { //nolint:gochecknoinits + opts := &versionOptions{ + format: config.OutputFormatHuman, + } + + cmd := &cobra.Command{ Use: "version", Short: "Show version of the cunīcu binary and optionally also a running daemon", Example: `$ sudo cunicu version client: v0.1.2 (os=linux, arch=arm64, commit=b22ee3e7, branch=master, built-at=2022-09-09T13:44:22+02:00, built-by=goreleaser) daemon: v0.1.2 (os=linux, arch=arm64, commit=b22ee3e7, branch=master, built-at=2022-09-09T13:44:22+02:00, built-by=goreleaser)`, - RunE: version, + RunE: func(cmd *cobra.Command, args []string) error { + return version(cmd, args, opts) + }, Args: cobra.NoArgs, } - short bool -) + pf := cmd.PersistentFlags() + pf.VarP(&opts.format, "format", "f", "Output `format` (one of: human, json)") + pf.BoolVarP(&opts.short, "short", "s", false, "Only show version and nothing else") -func init() { - pf := versionCmd.PersistentFlags() - pf.VarP(&format, "format", "f", "Output `format` (one of: human, json)") - pf.BoolVarP(&short, "short", "s", false, "Only show version and nothing else") - - rootCmd.AddCommand(versionCmd) + rootCmd.AddCommand(cmd) } -func version(cmd *cobra.Command, args []string) error { +func version(_ *cobra.Command, _ []string, opts *versionOptions) error { var err error buildInfos := &proto.BuildInfos{ @@ -52,7 +59,7 @@ func version(cmd *cobra.Command, args []string) error { } } - switch format { + switch opts.format { case config.OutputFormatJSON: mo := protojson.MarshalOptions{ AllowPartial: true, @@ -70,11 +77,13 @@ func version(cmd *cobra.Command, args []string) error { fmt.Print(string(buf)) fmt.Println() case config.OutputFormatHuman: - if short { + if opts.short { fmt.Println(buildInfos.Client.Version) } else { fmt.Print(buildInfos.ToString()) } + + case config.OutputFormatLogger: } return nil diff --git a/cmd/cunicu/wg.go b/cmd/cunicu/wg.go index 762c0ed8..5b454940 100644 --- a/cmd/cunicu/wg.go +++ b/cmd/cunicu/wg.go @@ -4,18 +4,17 @@ import ( "github.com/spf13/cobra" ) -var ( - wgCmd = &cobra.Command{ - Use: "wg", - Short: "WireGuard commands", - Long: `The wg sub-command mimics the wg(8) commands of the wireguard-tools package. +//nolint:gochecknoglobals +var wgCmd = &cobra.Command{ + Use: "wg", + Short: "WireGuard commands", + Long: `The wg sub-command mimics the wg(8) commands of the wireguard-tools package. In contrast to the wg(8) command, the cunico sub-command delegates it tasks to a running cunucu daemon. Currently, only a subset of the wg(8) are supported.`, - Args: cobra.NoArgs, - } -) + Args: cobra.NoArgs, +} -func init() { +func init() { //nolint:gochecknoinits rootCmd.AddCommand(wgCmd) } diff --git a/cmd/cunicu/wg_conf.go b/cmd/cunicu/wg_conf.go index d540cd87..0cee2ec0 100644 --- a/cmd/cunicu/wg_conf.go +++ b/cmd/cunicu/wg_conf.go @@ -2,15 +2,17 @@ package main import ( "context" + "errors" "fmt" "github.com/spf13/cobra" - rpcproto "github.com/stv0g/cunicu/pkg/proto/rpc" ) -var ( - wgShowConfCmd = &cobra.Command{ +var errNoSuchInterface = errors.New("unknown interface") + +func init() { //nolint:gochecknoinits + cmd := &cobra.Command{ Use: "showconf interface-name", Short: "Shows the current configuration and information of the provided WireGuard interface", Long: "Shows the current configuration of `interface-name` in the wg(8) format.", @@ -19,10 +21,8 @@ var ( Args: cobra.ExactArgs(1), ValidArgsFunction: interfaceValidArgs, } -) -func init() { - addClientCommand(wgCmd, wgShowConfCmd) + addClientCommand(wgCmd, cmd) } func wgShowConf(cmd *cobra.Command, args []string) error { @@ -36,7 +36,7 @@ func wgShowConf(cmd *cobra.Command, args []string) error { } if len(sts.Interfaces) != 1 { - return fmt.Errorf("failed to find interface '%s'", intfName) + return fmt.Errorf("%w: %s", errNoSuchInterface, intfName) } intf := sts.Interfaces[0] diff --git a/cmd/cunicu/wg_extract_handshakes.go b/cmd/cunicu/wg_extract_handshakes.go index f014538f..3680b8ee 100644 --- a/cmd/cunicu/wg_extract_handshakes.go +++ b/cmd/cunicu/wg_extract_handshakes.go @@ -13,8 +13,8 @@ import ( "go.uber.org/zap" ) -var ( - wgExtractHandshakesCmd = &cobra.Command{ +func init() { + cmd := &cobra.Command{ Use: "extract-handshakes", Short: "Extract WireGuard handshakes from Linux kernel", Long: `This command extracts ephemeral session secrets from handshakes of local WireGuard interfaces via Linux eBPF and kProbes. @@ -24,10 +24,8 @@ See: https://wiki.wireshark.org/WireGuard#key-log-format `, RunE: wgExtractHandshakes, } -) -func init() { - wgCmd.AddCommand(wgExtractHandshakesCmd) + wgCmd.AddCommand(cmd) } func wgExtractHandshakes(cmd *cobra.Command, args []string) error { diff --git a/cmd/cunicu/wg_key.go b/cmd/cunicu/wg_key.go index 61e833ea..e1f4139d 100644 --- a/cmd/cunicu/wg_key.go +++ b/cmd/cunicu/wg_key.go @@ -9,22 +9,22 @@ import ( "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) -var ( - wgGenKeyCmd = &cobra.Command{ +func init() { //nolint:gochecknoinits + genKeyCmd := &cobra.Command{ Use: "genkey", Short: "Generates a random private key in base64 and prints it to standard output.", RunE: wgGenKey, Args: cobra.NoArgs, } - wgGenPSKCmd = &cobra.Command{ + genPSKCmd := &cobra.Command{ Use: "genpsk", Short: "Generates a random preshared key in base64 and prints it to standard output.", RunE: wgGenKey, // a preshared key is generated in the same way as a private key Args: cobra.NoArgs, } - wgPubKeyCmd = &cobra.Command{ + pubKeyCmd := &cobra.Command{ Use: "pubkey", Short: "Calculates a public key and prints it in base64 to standard output.", Long: `Calculates a public key and prints it in base64 to standard output from a corresponding private key (generated with genkey) given in base64 on standard input.`, @@ -34,12 +34,10 @@ $ wg genkey | tee private.key | wg pubkey > public.key`, RunE: wgPubKey, Args: cobra.NoArgs, } -) -func init() { - wgCmd.AddCommand(wgGenKeyCmd) - wgCmd.AddCommand(wgGenPSKCmd) - wgCmd.AddCommand(wgPubKeyCmd) + wgCmd.AddCommand(genKeyCmd) + wgCmd.AddCommand(genPSKCmd) + wgCmd.AddCommand(pubKeyCmd) } func wgGenKey(cmd *cobra.Command, args []string) error { diff --git a/cmd/cunicu/wg_show.go b/cmd/cunicu/wg_show.go index 78879255..a2bd4044 100644 --- a/cmd/cunicu/wg_show.go +++ b/cmd/cunicu/wg_show.go @@ -1,22 +1,24 @@ +//nolint:goconst package main import ( "context" + "errors" "fmt" "os" "strings" "github.com/spf13/cobra" - "go.uber.org/zap" - "golang.zx2c4.com/wireguard/wgctrl/wgtypes" - - "github.com/stv0g/cunicu/pkg/wg" - + "github.com/stv0g/cunicu/pkg/proto/core" rpcproto "github.com/stv0g/cunicu/pkg/proto/rpc" + "github.com/stv0g/cunicu/pkg/wg" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) -var ( - wgShowCmd = &cobra.Command{ +var errUnknownField = errors.New("unknown field") + +func init() { //nolint:gochecknoinits + cmd := &cobra.Command{ Use: "show { interface-name | all | interfaces } [{ public-key | private-key | listen-port | fwmark | peers | preshared-keys | endpoints | allowed-ips | latest-handshakes | transfer | persistent-keepalive | dump }]", Short: "Shows current WireGuard configuration and runtime information of specified [interface].", Long: `Shows current WireGuard configuration and runtime information of specified [interface]. @@ -36,10 +38,8 @@ Subsequent lines are printed for each peer and contain in order separated by tab Args: cobra.MaximumNArgs(2), ValidArgsFunction: wgShowValidArgs, } -) -func init() { - addClientCommand(wgCmd, wgShowCmd) + addClientCommand(wgCmd, cmd) } func wgShowValidArgs(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { @@ -51,7 +51,7 @@ func wgShowValidArgs(cmd *cobra.Command, args []string, toComplete string) ([]st if err := rpcConnect(cmd, args); err != nil { return nil, cobra.ShellCompDirectiveError } - defer rpcDisconnect(cmd, args) + defer rpcDisconnect(cmd, args) //nolint:errcheck sts, err := rpcClient.GetStatus(context.Background(), &rpcproto.GetStatusParams{}) if err != nil { @@ -69,6 +69,33 @@ func wgShowValidArgs(cmd *cobra.Command, args []string, toComplete string) ([]st } func wgShow(cmd *cobra.Command, args []string) error { + intf, mode, field, err := parseWgShowArgs(args) + if err != nil { + return fmt.Errorf("failed to parse arguments: %w", err) + } + + sts, err := rpcClient.GetStatus(context.Background(), &rpcproto.GetStatusParams{ + Interface: intf, + }) + if err != nil { + return fmt.Errorf("failed RPC request: %w", err) + } + + switch mode { + case "interfaces": + showInterfaceList(sts.Interfaces) + default: + for i, intf := range sts.Interfaces { + if err := showInterfaceDetails(intf.Device(), i, mode, field); err != nil { + return err + } + } + } + + return nil +} + +func parseWgShowArgs(args []string) (string, string, string, error) { var intf, mode, field string if len(args) > 0 { @@ -97,11 +124,7 @@ func wgShow(cmd *cobra.Command, args []string) error { case "persistent-keepalive": case "dump": default: - if err := cmd.Usage(); err != nil { - logger.Fatal("Failed to show usage information", zap.Error(err)) - } - - os.Exit(1) + return "", "", "", fmt.Errorf("%w: %s", errUnknownField, args[1]) } field = args[1] } else { @@ -112,147 +135,147 @@ func wgShow(cmd *cobra.Command, args []string) error { field = "all" } - sts, err := rpcClient.GetStatus(context.Background(), &rpcproto.GetStatusParams{ - Interface: intf, - }) - if err != nil { - return fmt.Errorf("failed RPC request: %w", err) - } + return intf, mode, field, nil +} +func showInterfaceList(intfs []*core.Interface) { intfNames := []string{} - for i, intf := range sts.Interfaces { - if mode == "interfaces" { - intfNames = append(intfNames, intf.Name) + for _, intf := range intfs { + intfNames = append(intfNames, intf.Name) + } + + fmt.Println(strings.Join(intfNames, " ")) +} + +func showInterfaceDetails(dev *wg.Device, i int, mode, field string) error { + var prefix string + + if mode == "all" { + prefix = dev.Name + "\t" + } + + if field == "all" { + if i > 0 { + if _, err := fmt.Println(); err != nil { + return err + } + } + + if err := dev.DumpEnv(os.Stdout); err != nil { + return err + } + } else { + var value any + switch field { + case "public-key": + value = dev.PublicKey + case "private-key": + value = dev.PrivateKey + case "listen-port": + value = dev.ListenPort + case "fwmark": + value = "off" + if dev.FirewallMark != 0 { + value = fmt.Sprint(dev.FirewallMark) + } + } + + if value != nil { + fmt.Printf("%s%v\n", prefix, value) } else { - var prefix string + if field == "dump" { + fwmark := "off" + if dev.FirewallMark != 0 { + fwmark = fmt.Sprint(dev.FirewallMark) + } - mdev := intf.Device() - wdev := wg.Device(*mdev) - - if mode == "all" { - prefix = wdev.Name + "\t" + fmt.Printf("%s%s\t%s\t%d\t%s\n", + prefix, + dev.PrivateKey, + dev.PublicKey, + dev.ListenPort, + fwmark, + ) } - if field == "all" { - if i > 0 { - if _, err := fmt.Println(); err != nil { - return err - } - } + for _, peer := range dev.Peers { + peer := peer - if err := wdev.DumpEnv(os.Stdout); err != nil { - return err - } - } else { - - var value any - switch field { - case "public-key": - value = wdev.PublicKey - case "private-key": - value = wdev.PrivateKey - case "listen-port": - value = wdev.ListenPort - case "fwmark": - value = "off" - if wdev.FirewallMark != 0 { - value = fmt.Sprint(wdev.FirewallMark) - } - } - - if value != nil { - fmt.Printf("%s%v\n", prefix, value) + if field == "peers" { + fmt.Printf("%s%s\n", prefix, peer.PublicKey) } else { - if field == "dump" { - fwmark := "off" - if wdev.FirewallMark != 0 { - fwmark = fmt.Sprint(wdev.FirewallMark) - } - - fmt.Printf("%s%s\t%s\t%d\t%s\n", - prefix, - wdev.PrivateKey, - wdev.PublicKey, - wdev.ListenPort, - fwmark, - ) - } - - for _, peer := range wdev.Peers { - switch field { - case "peers": - fmt.Printf("%s%s\n", prefix, peer.PublicKey) - continue - case "preshared-keys": - value = peer.PresharedKey - case "endpoints": - value = peer.Endpoint - case "allowed-ips": - aips := []string{} - for _, aip := range peer.AllowedIPs { - aips = append(aips, aip.String()) - } - value = strings.Join(aips, " ") - case "latest-handshakes": - value = peer.LastHandshakeTime.Unix() - if peer.LastHandshakeTime.IsZero() { - value = 0 - } - case "transfer": - value = fmt.Sprintf("%d\t%d", peer.ReceiveBytes, peer.TransmitBytes) - case "persistent-keepalive": - value = peer.PersistentKeepaliveInterval.Seconds() - if peer.PersistentKeepaliveInterval == 0 { - value = "off" - } - case "dump": - as := []string{} - for _, aip := range peer.AllowedIPs { - as = append(as, aip.String()) - } - aIPs := strings.Join(as, ",") - - zero := wgtypes.Key{} - psk := "(none)" - if peer.PresharedKey != zero { - psk = peer.PresharedKey.String() - } - - ep := "" - if peer.Endpoint != nil { - ep = peer.Endpoint.String() - } - - pka := "off" - if peer.PersistentKeepaliveInterval.Seconds() > 0 { - pka = fmt.Sprintf("%d", int(peer.PersistentKeepaliveInterval.Seconds())) - } - - lhs := int64(0) - if !peer.LastHandshakeTime.IsZero() { - lhs = peer.LastHandshakeTime.Unix() - } - - value = fmt.Sprintf("%s\t%s\t%s\t%s\t%d\t%d\t%d\t%s", - peer.PublicKey, - psk, ep, aIPs, lhs, - peer.ReceiveBytes, - peer.TransmitBytes, - pka, - ) - } - - fmt.Printf("%s%s\t%v\n", prefix, peer.PublicKey, value) - } + showPeerDetails(&peer, field, prefix) } } } } - if mode == "interfaces" { - fmt.Println(strings.Join(intfNames, " ")) - } - return nil } + +func showPeerDetails(peer *wgtypes.Peer, field, prefix string) { + var value any + + switch field { + case "preshared-keys": + value = peer.PresharedKey + case "endpoints": + value = peer.Endpoint + case "allowed-ips": + aips := []string{} + for _, aip := range peer.AllowedIPs { + aips = append(aips, aip.String()) + } + value = strings.Join(aips, " ") + case "latest-handshakes": + value = peer.LastHandshakeTime.Unix() + if peer.LastHandshakeTime.IsZero() { + value = 0 + } + case "transfer": + value = fmt.Sprintf("%d\t%d", peer.ReceiveBytes, peer.TransmitBytes) + case "persistent-keepalive": + value = peer.PersistentKeepaliveInterval.Seconds() + if peer.PersistentKeepaliveInterval == 0 { + value = "off" + } + case "dump": + as := []string{} + for _, aip := range peer.AllowedIPs { + as = append(as, aip.String()) + } + aIPs := strings.Join(as, ",") + + zero := wgtypes.Key{} + psk := "(none)" + if peer.PresharedKey != zero { + psk = peer.PresharedKey.String() + } + + ep := "" + if peer.Endpoint != nil { + ep = peer.Endpoint.String() + } + + pka := "off" + if peer.PersistentKeepaliveInterval.Seconds() > 0 { + pka = fmt.Sprintf("%d", int(peer.PersistentKeepaliveInterval.Seconds())) + } + + lhs := int64(0) + if !peer.LastHandshakeTime.IsZero() { + lhs = peer.LastHandshakeTime.Unix() + } + + value = fmt.Sprintf("%s\t%s\t%s\t%s\t%d\t%d\t%d\t%s", + peer.PublicKey, + psk, ep, aIPs, lhs, + peer.ReceiveBytes, + peer.TransmitBytes, + pka, + ) + } + + fmt.Printf("%s%s\t%v\n", prefix, peer.PublicKey, value) +} diff --git a/go.mod b/go.mod index 11b40a58..9a8096bc 100644 --- a/go.mod +++ b/go.mod @@ -23,10 +23,10 @@ require ( github.com/spf13/pflag v1.0.5 github.com/vishvananda/netlink v1.2.1-beta.2 go.uber.org/zap v1.23.0 - golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2 + golang.org/x/crypto v0.1.0 golang.org/x/exp v0.0.0-20221010202428-3a778c567f61 golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 - golang.org/x/sys v0.0.0-20221010170243-090e33056c14 + golang.org/x/sys v0.2.0 golang.zx2c4.com/wireguard v0.0.0-20220920152132-bb719d3a6e2c golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220916014741-473347a5e6e3 google.golang.org/grpc v1.50.0 @@ -42,8 +42,8 @@ require ( require ( github.com/foxcpp/go-mockdns v1.0.0 // test-only github.com/gopacket/gopacket v0.0.0-20221006103438-9e6d99b9b443 // test-only - github.com/onsi/ginkgo/v2 v2.2.0 // test-only - github.com/onsi/gomega v1.21.1 // test-only + github.com/onsi/ginkgo/v2 v2.5.1 // test-only + github.com/onsi/gomega v1.24.0 // test-only github.com/stv0g/gont v1.6.3 // test-only sigs.k8s.io/controller-runtime v0.13.0 // test-only ) @@ -90,13 +90,13 @@ require ( github.com/vishvananda/netns v0.0.0-20220913150850-18c4f4234207 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.8.0 // indirect - golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect - golang.org/x/net v0.0.0-20221004154528-8021a29435af // indirect + golang.org/x/mod v0.6.0 // indirect + golang.org/x/net v0.1.0 // indirect golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1 // indirect - golang.org/x/term v0.0.0-20220919170432-7a66f970e087 // indirect - golang.org/x/text v0.3.7 // indirect + golang.org/x/term v0.1.0 // indirect + golang.org/x/text v0.4.0 // indirect golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect - golang.org/x/tools v0.1.12 // indirect + golang.org/x/tools v0.2.0 // indirect golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e // indirect diff --git a/go.sum b/go.sum index 7c1b3d1a..2f5d75d7 100644 --- a/go.sum +++ b/go.sum @@ -291,10 +291,10 @@ github.com/npillmayer/nestext v0.1.3/go.mod h1:h2lrijH8jpicr25dFY+oAJLyzlya6jhnu github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI= -github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= -github.com/onsi/gomega v1.21.1 h1:OB/euWYIExnPBohllTicTHmGTrMaqJ67nIu80j0/uEM= -github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc= +github.com/onsi/ginkgo/v2 v2.5.1 h1:auzK7OI497k6x4OvWq+TKAcpcSAlod0doAH72oIN0Jw= +github.com/onsi/ginkgo/v2 v2.5.1/go.mod h1:63DOGlLAH8+REH8jUGdL3YpCpu7JODesutUjdENfUAc= +github.com/onsi/gomega v1.24.0 h1:+0glovB9Jd6z3VR+ScSwQqXVTIfJcGA9UBM8yzQxhqg= +github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= @@ -413,8 +413,8 @@ golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2 h1:x8vtB3zMecnlqZIwJNUUpwYKYSqCz5jXbiyv0ZJJZeI= -golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20221010202428-3a778c567f61 h1:9echpU8vWVULSj2oFTtlY8mpPya+ED1L5xlaCaEEc+M= golang.org/x/exp v0.0.0-20221010202428-3a778c567f61/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= @@ -427,8 +427,8 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I= +golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -461,8 +461,8 @@ golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220923203811-8be639271d50/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.0.0-20221004154528-8021a29435af h1:wv66FM3rLZGPdxpYL+ApnDe2HzHcTFta3z5nsc13wI4= -golang.org/x/net v0.0.0-20221004154528-8021a29435af/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -524,20 +524,21 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20221010170243-090e33056c14 h1:k5II8e6QD8mITdi+okbbmR/cIyEbeXLBhy5Ha4nevyc= -golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.0.0-20220919170432-7a66f970e087 h1:tPwmk4vmvVCMdr98VgL4JH+qZxPL8fqlUOHnyOM8N3w= -golang.org/x/term v0.0.0-20220919170432-7a66f970e087/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y= golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -554,8 +555,8 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= +golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/config/agent.go b/pkg/config/agent.go index fb1832f8..c06a6c52 100644 --- a/pkg/config/agent.go +++ b/pkg/config/agent.go @@ -2,21 +2,23 @@ package config import ( "context" + "errors" "fmt" "path/filepath" "github.com/pion/ice/v2" - "golang.org/x/sync/errgroup" - "google.golang.org/grpc" - "github.com/stv0g/cunicu/pkg/crypto" icex "github.com/stv0g/cunicu/pkg/ice" signalingproto "github.com/stv0g/cunicu/pkg/proto/signaling" grpcx "github.com/stv0g/cunicu/pkg/signaling/grpc" "github.com/stv0g/cunicu/pkg/util" + "golang.org/x/sync/errgroup" + "google.golang.org/grpc" ) -func (c *InterfaceSettings) AgentURLs(ctx context.Context, pk *crypto.Key) ([]*ice.URL, error) { +var errInvalidURLScheme = errors.New("invalid ICE URL scheme") + +func (c *InterfaceSettings) AgentURLs(ctx context.Context, pk *crypto.Key) ([]*ice.URL, error) { //nolint:gocognit iceURLs := []*ice.URL{} g := errgroup.Group{} @@ -80,14 +82,14 @@ func (c *InterfaceSettings) AgentURLs(ctx context.Context, pk *crypto.Key) ([]*i }) default: - return nil, fmt.Errorf("invalid ICE URL scheme: %s", u.Scheme) + return nil, fmt.Errorf("%w: %s", errInvalidURLScheme, u.Scheme) } } return iceURLs, g.Wait() } -func (c *InterfaceSettings) AgentConfig(ctx context.Context, peer *crypto.Key) (*ice.AgentConfig, error) { +func (c *InterfaceSettings) AgentConfig(ctx context.Context, peer *crypto.Key) (*ice.AgentConfig, error) { //nolint:gocognit var err error cfg := &ice.AgentConfig{ diff --git a/pkg/config/agent_test.go b/pkg/config/agent_test.go index 420fff2b..3c51da05 100644 --- a/pkg/config/agent_test.go +++ b/pkg/config/agent_test.go @@ -7,16 +7,14 @@ import ( "strings" "time" - "github.com/pion/ice/v2" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" - . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - + "github.com/pion/ice/v2" "github.com/stv0g/cunicu/pkg/config" "github.com/stv0g/cunicu/pkg/crypto" grpcx "github.com/stv0g/cunicu/pkg/signaling/grpc" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" ) var _ = Describe("Agent config", func() { @@ -104,7 +102,10 @@ var _ = Describe("Agent config", func() { l, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) Expect(err).To(Succeed()) - go svr.Serve(l) + go func() { + err := svr.Serve(l) + Expect(err).To(Succeed()) + }() }) AfterEach(func() { @@ -146,6 +147,8 @@ var _ = Describe("Agent config", func() { pk.String(), })) Expect(u.Password).To(Equal(pass)) + + case ice.SchemeTypeSTUNS, ice.SchemeTypeTURNS: } } }) diff --git a/pkg/config/config.go b/pkg/config/config.go index e2c14045..0b6d4a48 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -254,7 +254,7 @@ func (c *Config) Update(sets map[string]any) (map[string]Change, error) { // SaveRuntime saves the current runtime configuration to disk func (c *Config) SaveRuntime() error { - f, err := os.OpenFile(RuntimeConfigFile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) + f, err := os.OpenFile(RuntimeConfigFile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o600) if err != nil { return err } @@ -328,8 +328,8 @@ func (c *Config) Marshal(wr io.Writer) error { func (c *Config) InterfaceSettings(name string) (cfg *InterfaceSettings) { for _, set := range c.InterfaceOrderByName(name) { if cfg == nil { - copy := c.DefaultInterfaceSettings - cfg = © + cfgCopy := c.DefaultInterfaceSettings + cfg = &cfgCopy } if icfg, ok := c.Interfaces[set]; ok { diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 4cee2cab..70a72386 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -14,12 +14,10 @@ import ( . "github.com/onsi/gomega" "github.com/onsi/gomega/ghttp" "github.com/pion/ice/v2" - "github.com/stv0g/cunicu/pkg/config" "github.com/stv0g/cunicu/pkg/crypto" - "github.com/stv0g/cunicu/test" - icex "github.com/stv0g/cunicu/pkg/ice" + "github.com/stv0g/cunicu/test" ) func TestSuite(t *testing.T) { @@ -34,7 +32,7 @@ var _ = Context("config", func() { dir := GinkgoT().TempDir() fn := filepath.Join(dir, "cunicu.yaml") - file, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY, 0600) + file, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY, 0o600) Expect(err).To(Succeed()) defer file.Close() @@ -161,7 +159,7 @@ var _ = Context("config", func() { }) AfterEach(func() { - //shut down the server between tests + // shut down the server between tests server.Close() }) @@ -377,7 +375,6 @@ var _ = Context("config", func() { }) Describe("reload", func() { - }) Describe("interface overwrites", func() { diff --git a/pkg/config/defaults.go b/pkg/config/defaults.go index 0d421a7b..9174b5b6 100644 --- a/pkg/config/defaults.go +++ b/pkg/config/defaults.go @@ -22,6 +22,7 @@ const ( EphemeralPortMax = (1 << 16) - 1 ) +//nolint:gochecknoglobals var ( DefaultPrefixes = []string{"fc2f:9a4d::/32", "10.237.0.0/16"} diff --git a/pkg/config/defaults_unix.go b/pkg/config/defaults_unix.go index f8d6bd86..7f2f2959 100644 --- a/pkg/config/defaults_unix.go +++ b/pkg/config/defaults_unix.go @@ -1,5 +1,4 @@ package config -var ( - RuntimeConfigFile = "/var/lib/cunicu.runtime.yaml" -) +//nolint:gochecknoglobals +var RuntimeConfigFile = "/var/lib/cunicu.runtime.yaml" diff --git a/pkg/config/diff.go b/pkg/config/diff.go index dd6b60c5..5d585fdf 100644 --- a/pkg/config/diff.go +++ b/pkg/config/diff.go @@ -12,23 +12,23 @@ type Change struct { New any } -func DiffSettings(old, new *Settings) map[string]Change { - oldMap := Map(old, "koanf") - newMap := Map(new, "koanf") +func DiffSettings(oldSettings, newSettings *Settings) map[string]Change { + oldMap := Map(oldSettings, "koanf") + newMap := Map(newSettings, "koanf") return diff(oldMap, newMap) } -func diff(old, new map[string]any) map[string]Change { +func diff(oldSettings, newSettings map[string]any) map[string]Change { added, removed, kept := util.SliceDiff( - maps.Keys(old), - maps.Keys(new), + maps.Keys(oldSettings), + maps.Keys(newSettings), ) changes := map[string]Change{} for _, key := range added { - newValue := new[key] + newValue := newSettings[key] changes[key] = Change{ New: newValue, @@ -36,7 +36,7 @@ func diff(old, new map[string]any) map[string]Change { } for _, key := range removed { - oldValue := old[key] + oldValue := oldSettings[key] changes[key] = Change{ Old: oldValue, @@ -44,19 +44,17 @@ func diff(old, new map[string]any) map[string]Change { } for _, key := range kept { - oldStruct, oldIsStruct := old[key].(map[string]any) - newStruct, newIsStruct := new[key].(map[string]any) + oldStruct, oldIsStruct := oldSettings[key].(map[string]any) + newStruct, newIsStruct := newSettings[key].(map[string]any) if oldIsStruct && newIsStruct { for skey, chg := range diff(oldStruct, newStruct) { changes[key+"."+skey] = chg } - } else { - if !reflect.DeepEqual(old[key], new[key]) { - changes[key] = Change{ - Old: old[key], - New: new[key], - } + } else if !reflect.DeepEqual(oldSettings[key], newSettings[key]) { + changes[key] = Change{ + Old: oldSettings[key], + New: newSettings[key], } } } diff --git a/pkg/config/handlers.go b/pkg/config/handlers.go index 7b008b18..bddedc78 100644 --- a/pkg/config/handlers.go +++ b/pkg/config/handlers.go @@ -7,11 +7,10 @@ import ( ) type ChangedHandler interface { - OnConfigChanged(key string, old, new any) + OnConfigChanged(key string, oldValue, newValue any) } func (c *Config) OnInterfaceChanged(name, key string, h ChangedHandler) { - } func (c *Config) InvokeHandlers(key string, change Change) { diff --git a/pkg/config/hooks.go b/pkg/config/hooks.go index 45324285..71dcb243 100644 --- a/pkg/config/hooks.go +++ b/pkg/config/hooks.go @@ -1,6 +1,7 @@ package config import ( + "errors" "fmt" "net" "reflect" @@ -8,6 +9,8 @@ import ( "github.com/mitchellh/mapstructure" ) +var errUnknownHookType = errors.New("unknown hook type") + func hookDecodeHook(f, t reflect.Type, data any) (any, error) { if f.Kind() != reflect.Map { return data, nil @@ -33,7 +36,7 @@ func hookDecodeHook(f, t reflect.Type, data any) (any, error) { Stdin: true, } default: - return nil, fmt.Errorf("unknown hook type: %s", base.Type) + return nil, fmt.Errorf("%w: %s", errUnknownHookType, base.Type) } decoder, err := mapstructure.NewDecoder(DecoderConfig(hook)) @@ -48,7 +51,8 @@ func hookDecodeHook(f, t reflect.Type, data any) (any, error) { func stringToIPAddrHook( f reflect.Type, t reflect.Type, - data interface{}) (interface{}, error) { + data interface{}, +) (interface{}, error) { if f.Kind() != reflect.String { return data, nil } @@ -66,7 +70,8 @@ func stringToIPAddrHook( func stringToIPNetAddrHookFunc( f reflect.Type, t reflect.Type, - data interface{}) (interface{}, error) { + data interface{}, +) (interface{}, error) { if f.Kind() != reflect.String { return data, nil } @@ -75,7 +80,12 @@ func stringToIPNetAddrHookFunc( } // Convert it by parsing - ip, net, err := net.ParseCIDR(data.(string)) + str, ok := data.(string) + if !ok { + panic("type assertion failed") + } + + ip, net, err := net.ParseCIDR(str) if err != nil { return nil, err } diff --git a/pkg/config/lookup_test.go b/pkg/config/lookup_test.go index d89b9920..4ec857e2 100644 --- a/pkg/config/lookup_test.go +++ b/pkg/config/lookup_test.go @@ -42,7 +42,7 @@ var _ = Describe("lookup", func() { var dnsSrv *mockdns.Server var webSrv *ghttp.Server - var cfgPath = "/cunicu" + cfgPath := "/cunicu" BeforeEach(func() { var err error @@ -176,7 +176,8 @@ var _ = Describe("lookup", func() { )) Expect(cfg.Interfaces).To(HaveKey("wg-test")) - cfg.Marshal(GinkgoWriter) + err = cfg.Marshal(GinkgoWriter) + Expect(err).To(Succeed()) }) AfterEach(func() { diff --git a/pkg/config/map.go b/pkg/config/map.go index c29c777b..e14cf69b 100644 --- a/pkg/config/map.go +++ b/pkg/config/map.go @@ -6,15 +6,18 @@ import ( "strings" ) -var configPkgPath = reflect.TypeOf(Settings{}).PkgPath() - func Map(v any, tagName string) map[string]any { rv := reflect.ValueOf(v) - return _map(rv, tagName).(map[string]any) + m, ok := _map(rv, tagName).(map[string]any) + if !ok { + panic("type assertion failed") + } + + return m } -func _map(v reflect.Value, tagName string) any { +func _map(v reflect.Value, tagName string) any { //nolint:gocognit t := v.Type() // Stringable types will be stringed @@ -29,7 +32,7 @@ func _map(v reflect.Value, tagName string) any { } // Types outside the config package will be taken as an interface - if t.PkgPath() != configPkgPath && t.PkgPath() != "" { + if t.PkgPath() != reflect.TypeOf(Settings{}).PkgPath() && t.PkgPath() != "" { return v.Interface() } diff --git a/pkg/config/meta.go b/pkg/config/meta.go index bf60bc50..7fe9ea21 100644 --- a/pkg/config/meta.go +++ b/pkg/config/meta.go @@ -77,9 +77,7 @@ func (m *Meta) Lookup(key string) *Meta { func (m *Meta) lookup(key []string) *Meta { if len(key) == 0 { return m - } - - if m.Fields != nil { + } else if m.Fields != nil { if n, ok := m.Fields[key[0]]; ok { return n.lookup(key[1:]) } diff --git a/pkg/config/order.go b/pkg/config/order.go index 41e3cf2c..d48d5aea 100644 --- a/pkg/config/order.go +++ b/pkg/config/order.go @@ -1,16 +1,19 @@ package config import ( + "errors" "fmt" "gopkg.in/yaml.v3" ) +var errNoMappingNode = errors.New("no mapping node") + type stringMapSlice []string func (keys *stringMapSlice) UnmarshalYAML(v *yaml.Node) error { if v.Kind != yaml.MappingNode { - return fmt.Errorf("pipeline must contain YAML mapping, has %v", v.Kind) + return fmt.Errorf("%w, has %v", errNoMappingNode, v.Kind) } *keys = make([]string, len(v.Content)/2) diff --git a/pkg/config/file.go b/pkg/config/provider_file.go similarity index 79% rename from pkg/config/file.go rename to pkg/config/provider_file.go index b57964de..5cfbce1e 100644 --- a/pkg/config/file.go +++ b/pkg/config/provider_file.go @@ -14,6 +14,12 @@ import ( "github.com/stv0g/cunicu/pkg/util/buildinfo" ) +var ( + errInsecureRemoteConfig = errors.New("remote configuration must be provided via HTTPS") + errFailedToFetch = errors.New("failed to fetch") + errInsecurePermissions = errors.New("insecure permissions on configuration file") +) + type RemoteFileProvider struct { url *url.URL etag string @@ -28,7 +34,7 @@ func NewRemoteFileProvider(u *url.URL) *RemoteFileProvider { } func (p *RemoteFileProvider) Read() (map[string]interface{}, error) { - return nil, errors.New("this provider does not support parsers") + return nil, errNotImplemented } func (p *RemoteFileProvider) ReadBytes() ([]byte, error) { @@ -37,7 +43,7 @@ func (p *RemoteFileProvider) ReadBytes() ([]byte, error) { if err != nil { return nil, fmt.Errorf("failed to split host:port: %w", err) } else if host != "localhost" && host != "127.0.0.1" && host != "::1" && host != "[::1]" { - return nil, errors.New("remote configuration must be provided via HTTPS") + return nil, errInsecureRemoteConfig } } @@ -55,10 +61,11 @@ func (p *RemoteFileProvider) ReadBytes() ([]byte, error) { resp, err := client.Do(req) if err != nil { - return nil, fmt.Errorf("failed to fetch %s: %w", p.url, err) + return nil, fmt.Errorf("failed to fetch: %s: %w", p.url, err) } else if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("failed to fetch: %s: %s", p.url, resp.Status) + return nil, fmt.Errorf("%w: %s: %s", errFailedToFetch, p.url, resp.Status) } + defer resp.Body.Close() buf, err := io.ReadAll(resp.Body) if err != nil { @@ -125,10 +132,11 @@ func (p *RemoteFileProvider) hasChanged() (bool, error) { resp, err := client.Do(req) if err != nil { - return false, fmt.Errorf("failed to fetch %s: %w", p.url, err) + return false, fmt.Errorf("%s %s: %w", errFailedToFetch, p.url, err) } else if resp.StatusCode != http.StatusOK { - return false, fmt.Errorf("failed to fetch: %s: %s", p.url, resp.Status) + return false, fmt.Errorf("%w: %s: %s", errFailedToFetch, p.url, resp.Status) } + defer resp.Body.Close() return resp.StatusCode == 200, nil } @@ -153,8 +161,8 @@ func (p *LocalFileProvider) ReadBytes() ([]byte, error) { return nil, err } - if p := fi.Mode().Perm(); p&07 != 0 { - return nil, fmt.Errorf("insecure permissions on configuration file: %#o. The configuration file must not be world-readable", p) + if p := fi.Mode().Perm(); p&0o7 != 0 { + return nil, fmt.Errorf("%w: %#o. The configuration file must not be world-readable", errInsecurePermissions, p) } buf, err := p.File.ReadBytes() diff --git a/pkg/config/lookup.go b/pkg/config/provider_lookup.go similarity index 92% rename from pkg/config/lookup.go rename to pkg/config/provider_lookup.go index 34c4f857..9fe5a4bc 100644 --- a/pkg/config/lookup.go +++ b/pkg/config/provider_lookup.go @@ -18,6 +18,11 @@ import ( "golang.org/x/sync/errgroup" ) +var ( + errNotImplemented = errors.New("not implemented") + errNoSOA = errors.New("failed to find SOA record") +) + type LookupProvider struct { domain string lastSerial int @@ -40,7 +45,7 @@ func NewLookupProvider(domain string) *LookupProvider { } func (p *LookupProvider) ReadBytes() ([]byte, error) { - return nil, errors.New("this provider requires no parser") + return nil, errNotImplemented } func (p *LookupProvider) Read() (map[string]any, error) { @@ -146,7 +151,7 @@ func (p *LookupProvider) lookupSerial(ctx context.Context) (int, error) { msg.SetQuestion(dns.Fqdn(p.domain), dns.TypeSOA) if err := conn.WriteMsg(msg); err != nil { - return -1, fmt.Errorf("failed to write question: %w", err) + return -1, fmt.Errorf("failed to send request: %w", err) } resp, err := conn.ReadMsg() @@ -164,16 +169,16 @@ func (p *LookupProvider) lookupSerial(ctx context.Context) (int, error) { } } - return -1, errors.New("failed to find SOA record") + return -1, errNoSOA } -func (p *LookupProvider) lookupTXT(ctx context.Context) error { +func (p *LookupProvider) lookupTXT(_ context.Context) error { rr, err := net.LookupTXT(p.domain) if err != nil { return err } - var re = regexp.MustCompile(`^(?m)cunicu-(.+?)=(.*)$`) + re := regexp.MustCompile(`^(?m)cunicu-(.+?)=(.*)$`) p.logger.Debug("TXT records found", zap.Any("records", rr)) @@ -218,7 +223,7 @@ func (p *LookupProvider) lookupTXT(ctx context.Context) error { return nil } -func (p *LookupProvider) lookupSRV(ctx context.Context) error { +func (p *LookupProvider) lookupSRV(_ context.Context) error { svcs := map[string][]string{ "stun": {"udp"}, "stuns": {"tcp"}, diff --git a/pkg/config/structs.go b/pkg/config/provider_structs.go similarity index 84% rename from pkg/config/structs.go rename to pkg/config/provider_structs.go index b836ee2e..9042c922 100644 --- a/pkg/config/structs.go +++ b/pkg/config/provider_structs.go @@ -1,9 +1,5 @@ package config -import ( - "errors" -) - type StructsProvider struct { value any tag string @@ -19,7 +15,7 @@ func NewStructsProvider(v any, t string) *StructsProvider { } func (p *StructsProvider) ReadBytes() ([]byte, error) { - return nil, errors.New("this provider requires no parser") + return nil, errNotImplemented } func (p *StructsProvider) Read() (map[string]any, error) { diff --git a/pkg/config/wg.go b/pkg/config/provider_wg.go similarity index 95% rename from pkg/config/wg.go rename to pkg/config/provider_wg.go index 5d70a689..687629ee 100644 --- a/pkg/config/wg.go +++ b/pkg/config/provider_wg.go @@ -8,14 +8,12 @@ import ( "path/filepath" "strings" - "go.uber.org/zap" - "golang.org/x/exp/slices" - "github.com/stv0g/cunicu/pkg/crypto" "github.com/stv0g/cunicu/pkg/device" - "github.com/stv0g/cunicu/pkg/wg" - errorsx "github.com/stv0g/cunicu/pkg/errors" + "github.com/stv0g/cunicu/pkg/wg" + "go.uber.org/zap" + "golang.org/x/exp/slices" ) type WireGuardProvider struct { @@ -83,7 +81,7 @@ func (p *WireGuardProvider) Read() (map[string]interface{}, error) { } func (p *WireGuardProvider) ReadBytes() ([]byte, error) { - return nil, errors.New("this provider does not support parsers") + return nil, errNotImplemented } func (p *WireGuardProvider) Order() []string { @@ -92,13 +90,12 @@ func (p *WireGuardProvider) Order() []string { return p.order } -type wgParser struct { -} +type wgParser struct{} func (p *wgParser) Unmarshal(data []byte) (map[string]interface{}, error) { c, err := wg.ParseConfig(data) if err != nil { - return nil, fmt.Errorf("failed to parse configuration: %s", err) + return nil, fmt.Errorf("failed to parse configuration: %w", err) } s, err := NewInterfaceSettingsFromConfig(c) diff --git a/pkg/config/providers.go b/pkg/config/providers.go index e3524f81..e3a3a839 100644 --- a/pkg/config/providers.go +++ b/pkg/config/providers.go @@ -1,6 +1,7 @@ package config import ( + "errors" "fmt" "net/url" "os" @@ -14,46 +15,12 @@ import ( "go.uber.org/zap" ) -var ( +const ( envPrefix = "CUNICU_" - - // Map flags from the flags to to Koanf settings - flagMap = map[string]string{ - // Feature flags - "discover-peers": "discover_peers", - "discover-endpoints": "discover_endpoints", - "sync-config": "sync_config", - "sync-hosts": "sync_hosts", - "sync-routes": "sync_routes", - - "backend": "backends", - "watch-interval": "watch_interval", - - // Socket - "rpc-socket": "rpc.socket", - "rpc-wait": "rpc.wait", - - // WireGuard - "wg-userspace": "userspace", - - // Endpoint discovery - "url": "ice.urls", - "username": "ice.username", - "password": "ice.password", - "ice-candidate-type": "ice.candidate_types", - "ice-network-type": "ice.network_types", - "ice-relay-tcp": "ice.relay_tcp", - "ice-relay-tls": "ice.relay_tls", - - // Peer discovery - "community": "community", - "hostname": "hostname", - - // Route synchronization - "routing-table": "routing_table", - } ) +var errUnsupportedScheme = errors.New("unsupported scheme") + type Watchable interface { Watch(cb func(event interface{}, err error)) error } @@ -132,7 +99,7 @@ func (c *Config) GetProviders() ([]koanf.Provider, error) { case "": p = NewLocalFileProvider(u) default: - return nil, fmt.Errorf("unsupported scheme '%s' for config file", u.Scheme) + return nil, fmt.Errorf("%w '%s' for config file", errUnsupportedScheme, u.Scheme) } ps = append(ps, p) @@ -172,11 +139,46 @@ func (c *Config) EnvironmentProvider() koanf.Provider { } return k, v - }) } func (c *Config) FlagProvider() koanf.Provider { + // Map flags from the flags to to Koanf settings + flagMap := map[string]string{ + // Feature flags + "discover-peers": "discover_peers", + "discover-endpoints": "discover_endpoints", + "sync-config": "sync_config", + "sync-hosts": "sync_hosts", + "sync-routes": "sync_routes", + + "backend": "backends", + "watch-interval": "watch_interval", + + // Socket + "rpc-socket": "rpc.socket", + "rpc-wait": "rpc.wait", + + // WireGuard + "wg-userspace": "userspace", + + // Endpoint discovery + "url": "ice.urls", + "username": "ice.username", + "password": "ice.password", + "ice-candidate-type": "ice.candidate_types", + "ice-network-type": "ice.network_types", + "ice-relay-tcp": "ice.relay_tcp", + "ice-relay-tls": "ice.relay_tls", + + // Peer discovery + "community": "community", + "hostname": "hostname", + + // Route synchronization + "routing-table": "routing_table", + } + return posflag.ProviderWithFlag(c.flags, ".", nil, func(f *pflag.Flag) (string, any) { setting, ok := flagMap[f.Name] if !ok { diff --git a/pkg/config/settings.go b/pkg/config/settings.go index 3278f43d..7d501528 100644 --- a/pkg/config/settings.go +++ b/pkg/config/settings.go @@ -1,6 +1,7 @@ package config import ( + "errors" "fmt" "net" "time" @@ -9,6 +10,9 @@ import ( icex "github.com/stv0g/cunicu/pkg/ice" ) +var errInvalidSettings = errors.New("invalid settings") + +//nolint:revive type ConfigSettings struct { Watch bool `koanf:"watch,omitempty"` } @@ -158,7 +162,8 @@ func (c *Settings) Check() error { func (c *InterfaceSettings) Check() error { if c.ListenPortRange != nil && c.ListenPortRange.Min > c.ListenPortRange.Max { - return fmt.Errorf("invalid settings: WireGuard minimal listen port (%d) must be smaller or equal than maximal port (%d)", + return fmt.Errorf("%w: WireGuard minimal listen port (%d) must be smaller or equal than maximal port (%d)", + errInvalidSettings, c.ListenPortRange.Min, c.ListenPortRange.Max, ) diff --git a/pkg/config/types.go b/pkg/config/types.go index 260a03bb..dbee9d23 100644 --- a/pkg/config/types.go +++ b/pkg/config/types.go @@ -1,6 +1,7 @@ package config import ( + "errors" "fmt" "net/url" "strings" @@ -65,13 +66,14 @@ const ( OutputFormatHuman OutputFormat = "human" ) -var ( - OutputFormats = []OutputFormat{ - OutputFormatJSON, - OutputFormatLogger, - OutputFormatHuman, - } -) +//nolint:gochecknoglobals +var OutputFormats = []OutputFormat{ + OutputFormatJSON, + OutputFormatLogger, + OutputFormatHuman, +} + +var errUnknownFormat = errors.New("unknown output format") func (f *OutputFormat) UnmarshalText(text []byte) error { *f = OutputFormat(text) @@ -81,7 +83,7 @@ func (f *OutputFormat) UnmarshalText(text []byte) error { return nil } - return fmt.Errorf("unknown output format: %s", string(text)) + return fmt.Errorf("%w: %s", errUnknownFormat, string(text)) } func (f OutputFormat) MarshalText() ([]byte, error) { diff --git a/pkg/config/wg_test.go b/pkg/config/wg_test.go index f3a024df..b5cdd2f9 100644 --- a/pkg/config/wg_test.go +++ b/pkg/config/wg_test.go @@ -13,8 +13,9 @@ import ( var _ = Context("wg provider", func() { var dir string + // TODO: Use Ginkgo facilities for creating temporary file createConfigFile := func(name, contents string) { - err := os.WriteFile(filepath.Join(dir, name+".conf"), []byte(contents), 0644) + err := os.WriteFile(filepath.Join(dir, name+".conf"), []byte(contents), 0o600) Expect(err).To(Succeed()) } diff --git a/pkg/core/core.go b/pkg/core/core.go index 83b6f1bf..e9c66405 100644 --- a/pkg/core/core.go +++ b/pkg/core/core.go @@ -1,2 +1,2 @@ -// Package core defines core datastructures of the project such as a WireGuard interface and peer +// Package core defines core data structures of the project such as a WireGuard interface and peer package core diff --git a/pkg/core/handlers.go b/pkg/core/handlers.go index 35776bcf..303b800a 100644 --- a/pkg/core/handlers.go +++ b/pkg/core/handlers.go @@ -68,9 +68,9 @@ type EventsHandler struct { Events chan Event } -func NewEventsHandler(len int) *EventsHandler { +func NewEventsHandler(length int) *EventsHandler { return &EventsHandler{ - Events: make(chan Event, len), + Events: make(chan Event, length), } } diff --git a/pkg/core/interface.go b/pkg/core/interface.go index e19cb08f..573a2bf8 100644 --- a/pkg/core/interface.go +++ b/pkg/core/interface.go @@ -5,17 +5,15 @@ import ( "io" "time" + "github.com/stv0g/cunicu/pkg/crypto" + "github.com/stv0g/cunicu/pkg/device" + proto "github.com/stv0g/cunicu/pkg/proto" + coreproto "github.com/stv0g/cunicu/pkg/proto/core" + "github.com/stv0g/cunicu/pkg/util" + "github.com/stv0g/cunicu/pkg/wg" "go.uber.org/zap" "golang.zx2c4.com/wireguard/wgctrl" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" - - "github.com/stv0g/cunicu/pkg/crypto" - "github.com/stv0g/cunicu/pkg/device" - "github.com/stv0g/cunicu/pkg/util" - "github.com/stv0g/cunicu/pkg/wg" - - proto "github.com/stv0g/cunicu/pkg/proto" - coreproto "github.com/stv0g/cunicu/pkg/proto/core" ) type Interface struct { @@ -85,72 +83,72 @@ func (i *Interface) DumpConfig(wr io.Writer) error { return cfg.Dump(wr) } -func (i *Interface) Sync(new *wgtypes.Device) (InterfaceModifier, []wgtypes.Peer, []wgtypes.Peer) { - old := i.Device +func (i *Interface) Sync(newDev *wgtypes.Device) (InterfaceModifier, []wgtypes.Peer, []wgtypes.Peer) { + oldDev := i.Device mod := InterfaceModifiedNone // Compare device properties - if new.Name != old.Name { + if newDev.Name != oldDev.Name { i.logger.Info("Name has changed", - zap.Any("old", old.Name), - zap.Any("new", new.Name), + zap.Any("old", oldDev.Name), + zap.Any("new", newDev.Name), ) mod |= InterfaceModifiedName } // Compare device properties - if new.Type != old.Type { + if newDev.Type != oldDev.Type { i.logger.Info("Type has changed", - zap.Any("old", old.Type), - zap.Any("new", new.Type), + zap.Any("old", oldDev.Type), + zap.Any("new", newDev.Type), ) mod |= InterfaceModifiedType } - if new.FirewallMark != old.FirewallMark { + if newDev.FirewallMark != oldDev.FirewallMark { i.logger.Info("Firewall mark has changed", - zap.Any("old", old.FirewallMark), - zap.Any("new", new.FirewallMark), + zap.Any("old", oldDev.FirewallMark), + zap.Any("new", newDev.FirewallMark), ) mod |= InterfaceModifiedFirewallMark } - if new.PrivateKey != old.PrivateKey { + if newDev.PrivateKey != oldDev.PrivateKey { i.logger.Info("PrivateKey has changed", - zap.Any("old", old.PrivateKey), - zap.Any("new", new.PrivateKey), + zap.Any("old", oldDev.PrivateKey), + zap.Any("new", newDev.PrivateKey), ) mod |= InterfaceModifiedPrivateKey } - if new.ListenPort != old.ListenPort { + if newDev.ListenPort != oldDev.ListenPort { i.logger.Info("ListenPort has changed", - zap.Any("old", old.ListenPort), - zap.Any("new", new.ListenPort), + zap.Any("old", oldDev.ListenPort), + zap.Any("new", newDev.ListenPort), ) mod |= InterfaceModifiedListenPort } - peersAdded, peersRemoved, peersKept := util.SliceDiffFunc(old.Peers, new.Peers, wg.CmpPeers) + peersAdded, peersRemoved, peersKept := util.SliceDiffFunc(oldDev.Peers, newDev.Peers, wg.CmpPeers) if len(peersAdded) > 0 || len(peersRemoved) > 0 { mod |= InterfaceModifiedPeers } // Call handlers - i.Device = (*wg.Device)(new) + i.Device = (*wg.Device)(newDev) i.LastSync = time.Now() if mod != InterfaceModifiedNone { i.logger.Debug("Interface has been modified", zap.Strings("changes", mod.Strings())) for _, h := range i.onModified { - h.OnInterfaceModified(i, old, mod) + h.OnInterfaceModified(i, oldDev, mod) } } diff --git a/pkg/core/interface_modifier.go b/pkg/core/interface_modifier.go index 45612e86..f74669e0 100644 --- a/pkg/core/interface_modifier.go +++ b/pkg/core/interface_modifier.go @@ -16,16 +16,15 @@ const ( InterfaceModifiedNone InterfaceModifier = 0 ) -var ( - InterfaceModifiersStrings = []string{ - "name", - "type", - "private-key", - "listen-port", - "firewall-mark", - "peers", - } -) +//nolint:gochecknoglobals +var InterfaceModifiersStrings = []string{ + "name", + "type", + "private-key", + "listen-port", + "firewall-mark", + "peers", +} func (i InterfaceModifier) Strings() []string { modifiers := []string{} diff --git a/pkg/core/peer.go b/pkg/core/peer.go index 60cf8208..13cd7e54 100644 --- a/pkg/core/peer.go +++ b/pkg/core/peer.go @@ -6,15 +6,13 @@ import ( "net" "time" - "go.uber.org/zap" - "github.com/stv0g/cunicu/pkg/crypto" - "github.com/stv0g/cunicu/pkg/util" - "golang.zx2c4.com/wireguard/wgctrl" - "golang.zx2c4.com/wireguard/wgctrl/wgtypes" - proto "github.com/stv0g/cunicu/pkg/proto" coreproto "github.com/stv0g/cunicu/pkg/proto/core" + "github.com/stv0g/cunicu/pkg/util" + "go.uber.org/zap" + "golang.zx2c4.com/wireguard/wgctrl" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) type SignalingState int @@ -109,7 +107,7 @@ func (p *Peer) IsControlling() bool { // WireGuardConfig return the WireGuard peer configuration func (p *Peer) WireGuardConfig() *wgtypes.PeerConfig { cfg := &wgtypes.PeerConfig{ - PublicKey: *(*wgtypes.Key)(&p.Peer.PublicKey), + PublicKey: p.Peer.PublicKey, Endpoint: p.Endpoint, AllowedIPs: p.Peer.AllowedIPs, } @@ -234,50 +232,50 @@ func (p *Peer) RemoveAllowedIP(a net.IPNet) error { return p.client.ConfigureDevice(p.Interface.Device.Name, cfg) } -func (p *Peer) Sync(new *wgtypes.Peer) (PeerModifier, []net.IPNet, []net.IPNet) { - old := p.Peer +func (p *Peer) Sync(newPeer *wgtypes.Peer) (PeerModifier, []net.IPNet, []net.IPNet) { + oldPeer := p.Peer mod := PeerModifiedNone now := time.Now() // Compare peer properties - if new.PresharedKey != old.PresharedKey { + if newPeer.PresharedKey != oldPeer.PresharedKey { mod |= PeerModifiedPresharedKey } - if util.CmpUDPAddr(new.Endpoint, old.Endpoint) != 0 { + if util.CmpUDPAddr(newPeer.Endpoint, oldPeer.Endpoint) != 0 { mod |= PeerModifiedEndpoint } - if new.PersistentKeepaliveInterval != old.PersistentKeepaliveInterval { + if newPeer.PersistentKeepaliveInterval != oldPeer.PersistentKeepaliveInterval { mod |= PeerModifiedKeepaliveInterval } - if new.LastHandshakeTime != old.LastHandshakeTime { + if newPeer.LastHandshakeTime != oldPeer.LastHandshakeTime { mod |= PeerModifiedHandshakeTime } - if new.ReceiveBytes != old.ReceiveBytes { + if newPeer.ReceiveBytes != oldPeer.ReceiveBytes { mod |= PeerModifiedReceiveBytes p.LastReceiveTime = now } - if new.TransmitBytes != old.TransmitBytes { + if newPeer.TransmitBytes != oldPeer.TransmitBytes { mod |= PeerModifiedTransmitBytes p.LastTransmitTime = now } - if new.ProtocolVersion != old.ProtocolVersion { + if newPeer.ProtocolVersion != oldPeer.ProtocolVersion { mod |= PeerModifiedProtocolVersion } // Find changes in AllowedIP list - ipsAdded, ipsRemoved, _ := util.SliceDiffFunc(old.AllowedIPs, new.AllowedIPs, util.CmpNet) + ipsAdded, ipsRemoved, _ := util.SliceDiffFunc(oldPeer.AllowedIPs, newPeer.AllowedIPs, util.CmpNet) if len(ipsAdded) > 0 || len(ipsRemoved) > 0 { mod |= PeerModifiedAllowedIPs } - p.Peer = new + p.Peer = newPeer if mod != PeerModifiedNone { p.logger.Debug("Peer has been modified", zap.Strings("changes", mod.Strings())) for _, h := range p.onModified { - h.OnPeerModified(p, old, mod, ipsAdded, ipsRemoved) + h.OnPeerModified(p, oldPeer, mod, ipsAdded, ipsRemoved) } } @@ -335,6 +333,7 @@ func (p *Peer) Reachability() coreproto.ReachabilityType { lastActivity = p.LastTransmitTime } + //nolint:gocritic if p.LastHandshakeTime.After(now.Add(-2 * time.Minute)) { return coreproto.ReachabilityType_REACHABILITY_TYPE_DIRECT } else if lastActivity.After(p.LastHandshakeTime) { diff --git a/pkg/core/peer_modifier.go b/pkg/core/peer_modifier.go index d8d15ba3..d27b6441 100644 --- a/pkg/core/peer_modifier.go +++ b/pkg/core/peer_modifier.go @@ -19,19 +19,18 @@ const ( PeerModifiedNone PeerModifier = 0 ) -var ( - PeerModifiersStrings = []string{ - "preshared-key", - "endpoint", - "keepalive-interval", - "handshake-time", - "receive-bytes", - "transmit-bytes", - "allowed-ips", - "protocol-version", - "name", - } -) +//nolint:gochecknoglobals +var PeerModifiersStrings = []string{ + "preshared-key", + "endpoint", + "keepalive-interval", + "handshake-time", + "receive-bytes", + "transmit-bytes", + "allowed-ips", + "protocol-version", + "name", +} func (i PeerModifier) Strings() []string { modifiers := []string{} diff --git a/pkg/crypto/crypto.go b/pkg/crypto/crypto.go index 3bf36a96..0d4d306c 100644 --- a/pkg/crypto/crypto.go +++ b/pkg/crypto/crypto.go @@ -3,8 +3,8 @@ package crypto import "crypto/rand" -func GetNonce(len int) (Nonce, error) { - var nonce = make(Nonce, len) +func GetNonce(length int) (Nonce, error) { + nonce := make(Nonce, length) _, err := rand.Read(nonce) if err != nil { diff --git a/pkg/crypto/types.go b/pkg/crypto/types.go index 7e0e778b..78bb7f2c 100644 --- a/pkg/crypto/types.go +++ b/pkg/crypto/types.go @@ -18,16 +18,21 @@ const ( KeyLength = 32 ) +//nolint:gochecknoglobals var ( // A cunīcu specific key for siphash to generate unique IPv6 addresses from the // interfaces public key addrHashKey = [...]byte{0x67, 0x67, 0x2c, 0x05, 0xd1, 0x3e, 0x11, 0x94, 0xbb, 0x38, 0x91, 0xff, 0x4f, 0x80, 0xb3, 0x97} argonSalt = [...]byte{0x77, 0x31, 0x63, 0x33, 0x63, 0x30, 0x6e, 0x6e, 0x33, 0x63, 0x74, 0x73, 0x33, 0x76, 0x65, 0x72, 0x79, 0x62, 0x30, 0x64, 0x79} + + errInvalidKeyLength = errors.New("invalid length") ) -type Nonce []byte -type Key [KeyLength]byte +type ( + Nonce []byte + Key [KeyLength]byte +) func GenerateKeyFromPassword(pw string) Key { key := argon2.IDKey([]byte(pw), argonSalt[:], 1, 64*1024, 4, KeyLength) @@ -70,7 +75,7 @@ func ParseKey(str string) (Key, error) { func ParseKeyBytes(buf []byte) (Key, error) { if len(buf) != KeyLength { - return Key{}, errors.New("invalid length") + return Key{}, errInvalidKeyLength } return *(*Key)(buf), nil diff --git a/pkg/crypto/types_test.go b/pkg/crypto/types_test.go index 5f1520ed..9c4f7c67 100644 --- a/pkg/crypto/types_test.go +++ b/pkg/crypto/types_test.go @@ -3,15 +3,13 @@ package crypto_test import ( "net" - "github.com/stv0g/cunicu/pkg/crypto" - "github.com/stv0g/cunicu/test" - . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/stv0g/cunicu/pkg/crypto" + "github.com/stv0g/cunicu/test" ) var _ = Describe("key", func() { - Describe("Argon2id key derivation", func() { var key1, key2 crypto.Key @@ -195,11 +193,9 @@ var _ = Describe("key", func() { }) It("marshal", func() { - keyCfg := crypto.Key(key) - - keyCfgStr, err := keyCfg.MarshalText() + keyStr2, err := key.MarshalText() Expect(err).To(Succeed()) - Expect(string(keyCfgStr)).To(Equal(keyStr)) + Expect(string(keyStr2)).To(Equal(keyStr)) }) It("fails on invalid key", func() { @@ -258,7 +254,7 @@ var _ = Describe("key", func() { q := k.IPAddress(*p) ones, bits := q.Mask.Size() - Expect(ones).To(Equal(10)) + Expect(ones).To(Equal(32)) Expect(bits).To(Equal(128)) Expect(p.Contains(q.IP)).To(BeTrue()) }) diff --git a/pkg/daemon/daemon.go b/pkg/daemon/daemon.go index 9125ecf1..80e880e3 100644 --- a/pkg/daemon/daemon.go +++ b/pkg/daemon/daemon.go @@ -10,15 +10,16 @@ import ( "github.com/stv0g/cunicu/pkg/core" "github.com/stv0g/cunicu/pkg/crypto" "github.com/stv0g/cunicu/pkg/device" + "github.com/stv0g/cunicu/pkg/signaling" "github.com/stv0g/cunicu/pkg/util" "github.com/stv0g/cunicu/pkg/watcher" "github.com/stv0g/cunicu/pkg/wg" "go.uber.org/zap" "golang.zx2c4.com/wireguard/wgctrl" - - "github.com/stv0g/cunicu/pkg/signaling" ) +var errInsufficientPrivileges = errors.New("insufficient privileges. Please run cunicu with administrator privileges") + type Daemon struct { // Shared Backend *signaling.MultiBackend @@ -42,7 +43,7 @@ func New(cfg *config.Config) (*Daemon, error) { // Check permissions if !util.HasAdminPrivileges() { - return nil, errors.New("insufficient privileges. Please run cunicu with administrator privileges") + return nil, errInsufficientPrivileges } d := &Daemon{ @@ -68,6 +69,7 @@ func New(cfg *config.Config) (*Daemon, error) { // Create signaling backend urls := []*url.URL{} for _, u := range cfg.Backends { + u := u urls = append(urls, &u.URL) } diff --git a/pkg/daemon/feature.go b/pkg/daemon/feature.go index 85f79e92..5938dde6 100644 --- a/pkg/daemon/feature.go +++ b/pkg/daemon/feature.go @@ -4,6 +4,7 @@ import ( "golang.org/x/exp/slices" ) +//nolint:gochecknoglobals var ( features = map[string]*FeaturePlugin{} featuresSorted []*FeaturePlugin @@ -26,11 +27,11 @@ type Feature interface { Close() error } -func RegisterFeature(name, desc string, New func(i *Interface) (Feature, error), order int) { +func RegisterFeature(name, desc string, ctor func(i *Interface) (Feature, error), order int) { features[name] = &FeaturePlugin{ Name: name, Description: desc, - New: New, + New: ctor, Order: order, } } diff --git a/pkg/daemon/feature/autocfg/autocfg.go b/pkg/daemon/feature/autocfg/autocfg.go index 04d2d630..804c0ec5 100644 --- a/pkg/daemon/feature/autocfg/autocfg.go +++ b/pkg/daemon/feature/autocfg/autocfg.go @@ -17,7 +17,9 @@ import ( "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) -func init() { +var errMTUTooSmall = errors.New("MTU too small") + +func init() { //nolint:gochecknoinits daemon.RegisterFeature("autocfg", "Auto configuration", New, 10) } @@ -155,7 +157,7 @@ func (i *Interface) DetectMTU() (mtu int, err error) { } if mtu-wg.TunnelOverhead < wg.MinimalMTU { - return -1, fmt.Errorf("MTU too small: %d", mtu) + return -1, fmt.Errorf("%w: %d", errMTUTooSmall, mtu) } return mtu - wg.TunnelOverhead, nil diff --git a/pkg/daemon/feature/autocfg/handlers.go b/pkg/daemon/feature/autocfg/handlers.go index 4573a708..c1e56cbe 100644 --- a/pkg/daemon/feature/autocfg/handlers.go +++ b/pkg/daemon/feature/autocfg/handlers.go @@ -33,11 +33,11 @@ func (i *Interface) OnPeerAdded(p *core.Peer) { logger := i.logger.With(zap.String("peer", p.String())) // Check if peer has been created by peer discovery - var hasDesc bool + hasDesc := false if f, ok := i.Interface.Features["pdisc"]; ok { - hasDesc = f.(*pdisc.Interface).Description(p) != nil - } else { - hasDesc = false + if i, ok := f.(*pdisc.Interface); ok { + hasDesc = i.Description(p) != nil + } } // Add AllowedIPs for peer if they are not added by the peer-discovery diff --git a/pkg/daemon/feature/cfgsync/cfgsync.go b/pkg/daemon/feature/cfgsync/cfgsync.go index 5e66cf81..7ea3d4f2 100644 --- a/pkg/daemon/feature/cfgsync/cfgsync.go +++ b/pkg/daemon/feature/cfgsync/cfgsync.go @@ -15,7 +15,7 @@ import ( "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) -func init() { +func init() { //nolint:gochecknoinits daemon.RegisterFeature("cfgsync", "Config synchronization", New, 20) } @@ -39,7 +39,7 @@ func (i *Interface) Start() error { // Assign static addresses for _, addr := range i.Settings.Addresses { - if err := i.KernelDevice.AddAddress(net.IPNet(addr)); err != nil && !errors.Is(err, syscall.EEXIST) { + if err := i.KernelDevice.AddAddress(addr); err != nil && !errors.Is(err, syscall.EEXIST) { return fmt.Errorf("failed to assign address '%s': %w", addr.String(), err) } } @@ -82,7 +82,7 @@ func (i *Interface) Close() error { return nil } -func (i *Interface) ConfigureWireGuard() error { +func (i *Interface) ConfigureWireGuard() error { //nolint:gocognit cfg := wgtypes.Config{} if i.Settings.FirewallMark != 0 && i.Settings.FirewallMark != i.FirewallMark { diff --git a/pkg/daemon/feature/epdisc/epdisc.go b/pkg/daemon/feature/epdisc/epdisc.go index 4a1f20a0..8b1adb77 100644 --- a/pkg/daemon/feature/epdisc/epdisc.go +++ b/pkg/daemon/feature/epdisc/epdisc.go @@ -8,21 +8,18 @@ import ( "github.com/pion/ice/v2" "github.com/pion/stun" - "go.uber.org/zap" - "github.com/stv0g/cunicu/pkg/core" "github.com/stv0g/cunicu/pkg/crypto" "github.com/stv0g/cunicu/pkg/daemon" "github.com/stv0g/cunicu/pkg/daemon/feature/epdisc/proxy" "github.com/stv0g/cunicu/pkg/device" - errorsx "github.com/stv0g/cunicu/pkg/errors" icex "github.com/stv0g/cunicu/pkg/ice" - epdiscproto "github.com/stv0g/cunicu/pkg/proto/feature/epdisc" + "go.uber.org/zap" ) -func init() { +func init() { //nolint:gochecknoinits daemon.RegisterFeature("epdisc", "Endpoint discovery", New, 50) } @@ -216,6 +213,8 @@ func (i *Interface) Endpoint() (*net.UDPAddr, error) { Port: c.Port(), } } + + case ice.CandidateTypePeerReflexive, ice.CandidateTypeRelay, ice.CandidateTypeUnspecified: } } } diff --git a/pkg/daemon/feature/epdisc/handlers.go b/pkg/daemon/feature/epdisc/handlers.go index 3816d3f1..aa45d52f 100644 --- a/pkg/daemon/feature/epdisc/handlers.go +++ b/pkg/daemon/feature/epdisc/handlers.go @@ -4,19 +4,17 @@ import ( "context" "net" - "go.uber.org/zap" - "golang.zx2c4.com/wireguard/wgctrl/wgtypes" - "github.com/stv0g/cunicu/pkg/core" "github.com/stv0g/cunicu/pkg/crypto" "github.com/stv0g/cunicu/pkg/daemon/feature/epdisc/proxy" - "github.com/stv0g/cunicu/pkg/wg" - icex "github.com/stv0g/cunicu/pkg/ice" + "github.com/stv0g/cunicu/pkg/wg" + "go.uber.org/zap" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) type OnConnectionStateHandler interface { - OnConnectionStateChange(p *Peer, new, prev icex.ConnectionState) + OnConnectionStateChange(p *Peer, newState, prevState icex.ConnectionState) } func (i *Interface) OnConnectionStateChange(h OnConnectionStateHandler) { diff --git a/pkg/daemon/feature/epdisc/peer.go b/pkg/daemon/feature/epdisc/peer.go index c260afbe..e4136ce4 100644 --- a/pkg/daemon/feature/epdisc/peer.go +++ b/pkg/daemon/feature/epdisc/peer.go @@ -8,23 +8,27 @@ import ( "strings" "time" - "github.com/pion/ice/v2" - "go.uber.org/zap" - "github.com/cenkalti/backoff/v4" - + "github.com/pion/ice/v2" "github.com/stv0g/cunicu/pkg/core" "github.com/stv0g/cunicu/pkg/crypto" "github.com/stv0g/cunicu/pkg/daemon/feature/epdisc/proxy" "github.com/stv0g/cunicu/pkg/device" - "github.com/stv0g/cunicu/pkg/log" - "github.com/stv0g/cunicu/pkg/signaling" - "github.com/stv0g/cunicu/pkg/util" - icex "github.com/stv0g/cunicu/pkg/ice" + "github.com/stv0g/cunicu/pkg/log" proto "github.com/stv0g/cunicu/pkg/proto" coreproto "github.com/stv0g/cunicu/pkg/proto/core" epdiscproto "github.com/stv0g/cunicu/pkg/proto/feature/epdisc" + "github.com/stv0g/cunicu/pkg/signaling" + "github.com/stv0g/cunicu/pkg/util" + "go.uber.org/zap" +) + +var ( + errNoNATorBind = errors.New("failed tp setup peer. Neither NAT or Bind is configured") + errCreateNonClosedAgent = errors.New("failed to create new agent if previous one is not closed") + errSwitchToIdle = errors.New("failed to switch to idle state") + errStillIdle = errors.New("not connected yet") ) type Peer struct { @@ -70,6 +74,7 @@ func NewPeer(cp *core.Peer, e *Interface) (*Peer, error) { p.logger.Info("Subscribed to messages from peer", zap.Any("kp", kp)) // Setup proxy + //nolint:gocritic if dev, ok := e.KernelDevice.(*device.UserDevice); ok { if p.proxy, err = proxy.NewUserBindProxy(dev.Bind); err != nil { return nil, fmt.Errorf("failed to setup proxy: %w", err) @@ -79,7 +84,7 @@ func NewPeer(cp *core.Peer, e *Interface) (*Peer, error) { return nil, fmt.Errorf("failed to setup proxy: %w", err) } } else { - return nil, fmt.Errorf("failed tp setup peer. Neither NAT or Bind is configured") + return nil, errNoNATorBind } if err = p.createAgentWithBackoff(); err != nil { @@ -216,7 +221,7 @@ func (p *Peer) createAgent() error { var err error if !p.setConnectionStateIf(ice.ConnectionStateClosed, icex.ConnectionStateCreating) { - return fmt.Errorf("failed to create new agent if previous one is not closed") + return errCreateNonClosedAgent } // Reset state to closed if we error-out of this function @@ -271,18 +276,20 @@ func (p *Peer) createAgent() error { } if !p.setConnectionStateIf(icex.ConnectionStateCreating, icex.ConnectionStateIdle) { - return fmt.Errorf("failed to switch to idle state") + return errSwitchToIdle } // Send peer credentials as long as we remain in ConnectionStateIdle - go p.sendCredentialsWithBackoff(true) + go func() { + if err := p.sendCredentialsWithBackoff(true); err != nil { + p.logger.Error("Failed to send credentials", zap.Error(err)) + } + }() return nil } func (p *Peer) sendCredentialsWithBackoff(need bool) error { - ErrStillIdle := errors.New("not connected yet") - bo := backoff.NewExponentialBackOff() bo.MaxInterval = 1 * time.Minute @@ -293,13 +300,13 @@ func (p *Peer) sendCredentialsWithBackoff(need bool) error { } if p.ConnectionState() == icex.ConnectionStateIdle { - return ErrStillIdle + return errStillIdle } return nil }, bo, func(err error, d time.Duration) { - if err != ErrStillIdle { + if errors.Is(err, errStillIdle) { p.logger.Error("Failed to send peer credentials", zap.Error(err), zap.Duration("after", d)) @@ -373,35 +380,35 @@ func (p *Peer) connect(ufrag, pwd string) error { // setConnectionState updates the connection state of the peer and invokes registered handlers. // It returns the previous connection state. -func (p *Peer) setConnectionState(new icex.ConnectionState) icex.ConnectionState { - prev := p.connectionState.Swap(new) +func (p *Peer) setConnectionState(newState icex.ConnectionState) icex.ConnectionState { //nolint:unparam + prevState := p.connectionState.Swap(newState) p.lastStateChange = time.Now() p.logger.Info("Connection state changed", - zap.String("new", strings.ToLower(new.String())), - zap.String("previous", strings.ToLower(prev.String()))) + zap.String("new", strings.ToLower(newState.String())), + zap.String("previous", strings.ToLower(prevState.String()))) for _, h := range p.Interface.onConnectionStateChange { - h.OnConnectionStateChange(p, new, prev) + h.OnConnectionStateChange(p, newState, prevState) } - return prev + return prevState } // setConnectionStateIf updates the connection state of the peer if the previous state matches the one supplied. // It returns true if the state has been changed. -func (p *Peer) setConnectionStateIf(prev, new icex.ConnectionState) bool { - swapped := p.connectionState.CompareAndSwap(prev, new) +func (p *Peer) setConnectionStateIf(prevState, newState icex.ConnectionState) bool { + swapped := p.connectionState.CompareAndSwap(prevState, newState) if swapped { p.lastStateChange = time.Now() p.logger.Info("Connection state changed", - zap.String("new", strings.ToLower(new.String())), - zap.String("previous", strings.ToLower(prev.String()))) + zap.String("new", strings.ToLower(newState.String())), + zap.String("previous", strings.ToLower(prevState.String()))) for _, h := range p.Interface.onConnectionStateChange { - h.OnConnectionStateChange(p, new, prev) + h.OnConnectionStateChange(p, newState, prevState) } } @@ -475,11 +482,13 @@ func (p *Peer) Reachability() coreproto.ReachabilityType { } lc, rc := cp.Local, cp.Remote - if lc.Type() == ice.CandidateTypeRelay && rc.Type() == ice.CandidateTypeRelay { + + switch { + case lc.Type() == ice.CandidateTypeRelay && rc.Type() == ice.CandidateTypeRelay: return coreproto.ReachabilityType_REACHABILITY_TYPE_RELAYED_BIDIR - } else if lc.Type() == ice.CandidateTypeRelay || rc.Type() == ice.CandidateTypeRelay { + case lc.Type() == ice.CandidateTypeRelay || rc.Type() == ice.CandidateTypeRelay: return coreproto.ReachabilityType_REACHABILITY_TYPE_RELAYED - } else { + default: return coreproto.ReachabilityType_REACHABILITY_TYPE_DIRECT } diff --git a/pkg/daemon/feature/epdisc/peer_handlers.go b/pkg/daemon/feature/epdisc/peer_handlers.go index cbb93b45..141e9898 100644 --- a/pkg/daemon/feature/epdisc/peer_handlers.go +++ b/pkg/daemon/feature/epdisc/peer_handlers.go @@ -5,29 +5,32 @@ import ( "github.com/pion/ice/v2" "github.com/stv0g/cunicu/pkg/crypto" - "github.com/stv0g/cunicu/pkg/signaling" - "go.uber.org/zap" - icex "github.com/stv0g/cunicu/pkg/ice" epdiscproto "github.com/stv0g/cunicu/pkg/proto/feature/epdisc" + "github.com/stv0g/cunicu/pkg/signaling" + "go.uber.org/zap" ) // onConnectionStateChange is a callback which gets called by the ICE agent // whenever the state of the ICE connection has changed -func (p *Peer) onConnectionStateChange(new icex.ConnectionState) { +func (p *Peer) onConnectionStateChange(newState icex.ConnectionState) { if p.ConnectionState() == icex.ConnectionStateClosing { p.logger.Debug("Ignoring state transition as we are closing the session") return } - p.setConnectionState(new) + p.setConnectionState(newState) - if new == ice.ConnectionStateFailed || new == ice.ConnectionStateDisconnected { + if newState == ice.ConnectionStateFailed || newState == ice.ConnectionStateDisconnected { if err := p.Restart(); err != nil { p.logger.Error("Failed to restart ICE session", zap.Error(err)) } - } else if new == ice.ConnectionStateClosed { - go p.createAgentWithBackoff() + } else if newState == ice.ConnectionStateClosed { + go func() { + if err := p.createAgentWithBackoff(); err != nil { + p.logger.Error("Failed to connect", zap.Error(err)) + } + }() } } diff --git a/pkg/daemon/feature/epdisc/proxy/mux.go b/pkg/daemon/feature/epdisc/proxy/mux.go index 2280b055..859aabd9 100644 --- a/pkg/daemon/feature/epdisc/proxy/mux.go +++ b/pkg/daemon/feature/epdisc/proxy/mux.go @@ -1,21 +1,26 @@ package proxy import ( + "errors" "net" "github.com/pion/ice/v2" - "go.uber.org/zap" - "github.com/stv0g/cunicu/pkg/log" + "go.uber.org/zap" ) +var errInvalidCast = errors.New("invalid cast") + func CreateUDPMux() (ice.UDPMux, int, error) { conn, err := net.ListenUDP("udp", nil) if err != nil { return nil, 0, err } - lAddr := conn.LocalAddr().(*net.UDPAddr) + lAddr, ok := conn.LocalAddr().(*net.UDPAddr) + if !ok { + return nil, -1, errInvalidCast + } mux := ice.NewUDPMuxDefault(ice.UDPMuxParams{ UDPConn: conn, @@ -34,7 +39,10 @@ func CreateUniversalUDPMux() (ice.UniversalUDPMux, int, error) { return nil, 0, err } - lAddr := conn.LocalAddr().(*net.UDPAddr) + lAddr, ok := conn.LocalAddr().(*net.UDPAddr) + if !ok { + return nil, -1, errInvalidCast + } mux := ice.NewUniversalUDPMuxDefault(ice.UniversalUDPMuxParams{ UDPConn: conn, diff --git a/pkg/daemon/feature/epdisc/proxy/nat_linux.go b/pkg/daemon/feature/epdisc/proxy/nat_linux.go index 6903aaa6..747961fd 100644 --- a/pkg/daemon/feature/epdisc/proxy/nat_linux.go +++ b/pkg/daemon/feature/epdisc/proxy/nat_linux.go @@ -12,6 +12,8 @@ import ( "golang.org/x/sys/unix" ) +var errRuleNotFound = errors.New("rule not found") + type NAT struct { NFConn *nftables.Conn @@ -54,7 +56,7 @@ func (n *NAT) AddRule(r *nftables.Rule, comment string) (*NATRule, error) { } if r.Handle == 0 { - return nil, errors.New("rule not found") + return nil, errRuleNotFound } return &NATRule{ @@ -206,7 +208,6 @@ func (n *NAT) MasqueradeSourcePort(fromPort, toPort int, dest *net.UDPAddr) (*NA Table: n.table, Chain: n.chainEgress, Exprs: []expr.Any{ - // meta l4proto udp &expr.Meta{ Key: expr.MetaKeyL4PROTO, diff --git a/pkg/daemon/feature/epdisc/proxy/nat_others.go b/pkg/daemon/feature/epdisc/proxy/nat_others.go index f15faa2c..306efaeb 100644 --- a/pkg/daemon/feature/epdisc/proxy/nat_others.go +++ b/pkg/daemon/feature/epdisc/proxy/nat_others.go @@ -8,8 +8,10 @@ import ( "github.com/stv0g/cunicu/pkg/errors" ) -type NATRule struct{} -type NAT struct{} +type ( + NATRule struct{} + NAT struct{} +) func NewNAT(ident string) (*NAT, error) { return nil, errors.ErrNotSupported diff --git a/pkg/daemon/feature/epdisc/proxy/proxy.go b/pkg/daemon/feature/epdisc/proxy/proxy.go index 0496dca9..63394d46 100644 --- a/pkg/daemon/feature/epdisc/proxy/proxy.go +++ b/pkg/daemon/feature/epdisc/proxy/proxy.go @@ -7,7 +7,6 @@ import ( "net" "github.com/pion/ice/v2" - epdiscproto "github.com/stv0g/cunicu/pkg/proto/feature/epdisc" ) diff --git a/pkg/daemon/feature/epdisc/proxy/proxy_kernel.go b/pkg/daemon/feature/epdisc/proxy/proxy_kernel.go index f8f09888..2f39f56f 100644 --- a/pkg/daemon/feature/epdisc/proxy/proxy_kernel.go +++ b/pkg/daemon/feature/epdisc/proxy/proxy_kernel.go @@ -8,9 +8,8 @@ import ( "time" "github.com/pion/ice/v2" - "go.uber.org/zap" - epdiscproto "github.com/stv0g/cunicu/pkg/proto/feature/epdisc" + "go.uber.org/zap" ) const ( @@ -45,8 +44,7 @@ func NewKernelProxy(nat *NAT, listenPort int) (*KernelProxy, error) { func (p *KernelProxy) Close() error { if p.connUser != nil { - // TODO: really required? - if err := p.connUser.SetWriteDeadline(time.Now().Add(1 * time.Second)); err != nil { + if err := p.connUser.SetWriteDeadline(time.Now().Add(1 * time.Second)); err != nil { // TODO: really required? return fmt.Errorf("failed to set write deadline: %w", err) } @@ -71,8 +69,6 @@ func (p *KernelProxy) UpdateCandidatePair(cp *ice.CandidatePair, conn *ice.Conn) } func (p *KernelProxy) Update(newCP *ice.CandidatePair, newConnICE *ice.Conn, newListenPort int) error { - var err error - if newListenPort > 0 { p.listenPort = newListenPort } @@ -84,70 +80,95 @@ func (p *KernelProxy) Update(newCP *ice.CandidatePair, newConnICE *ice.Conn, new if p.cp != nil { switch p.Type() { case epdiscproto.ProxyType_KERNEL_NAT: - p.ep = &net.UDPAddr{ - IP: net.ParseIP(p.cp.Remote.Address()), - Port: p.cp.Remote.Port(), - } - - // Delete any old SPAT rule - if p.natRule != nil { - if err := p.natRule.Delete(); err != nil { - return fmt.Errorf("failed to delete rule: %w", err) - } - } - - // Setup SNAT redirect (WireGuard listen-port -> STUN port) - if p.natRule, err = p.nat.MasqueradeSourcePort(p.listenPort, p.cp.Local.Port(), p.ep); err != nil { + if err := p.updateNAT(); err != nil { return err } case epdiscproto.ProxyType_KERNEL_CONN: - // We ca not do anything for prfx and relay candidates. - // Let them pass through the userspace connection - - var create = false - if p.connUser == nil { - // We lazily create the user connection on demand to avoid opening unused sockets - create = true - } else if ra, ok := p.connUser.RemoteAddr().(*net.UDPAddr); ok && ra.Port != p.listenPort { - // Also recreate the user connection in case the WireGuard listen port has changed - create = true + if err := p.updateConn(newConnICE); err != nil { + return err } - var newConnUser *net.UDPConn - if create { - if newConnUser, err = p.newUserConn(newConnICE); err != nil { - return fmt.Errorf("failed to setup user connection: %w", err) - } - - p.logger.Info("Created user-space proxy connection", - zap.Any("local", newConnUser.LocalAddr()), - zap.Any("remote", newConnUser.RemoteAddr())) - } - - // Start copying if the underlying ice.Conn has changed - if newConnICE != p.connICE || newConnUser != p.connUser { - if p.connUser != nil { - if err := p.connUser.Close(); err != nil { - return fmt.Errorf("failed to close old user connection: %w", err) - } - } - - p.connICE = newConnICE - p.connUser = newConnUser - - // Bi-directional copy between ICE and loopback UDP sockets - go p.copy(newConnICE, p.connUser) - go p.copy(p.connUser, newConnICE) - } - - p.ep = p.connUser.LocalAddr().(*net.UDPAddr) + case epdiscproto.ProxyType_NO_PROXY, epdiscproto.ProxyType_USER_BIND: } } return nil } +func (p *KernelProxy) updateNAT() error { + var err error + + p.ep = &net.UDPAddr{ + IP: net.ParseIP(p.cp.Remote.Address()), + Port: p.cp.Remote.Port(), + } + + // Delete any old SPAT rule + if p.natRule != nil { + if err := p.natRule.Delete(); err != nil { + return fmt.Errorf("failed to delete rule: %w", err) + } + } + + // Setup SNAT redirect (WireGuard listen-port -> STUN port) + if p.natRule, err = p.nat.MasqueradeSourcePort(p.listenPort, p.cp.Local.Port(), p.ep); err != nil { + return err + } + + return nil +} + +func (p *KernelProxy) updateConn(newConnICE *ice.Conn) error { + var err error + + // We cant to anything for prfx and relay candidates. + // Let them pass through the userspace connection + + create := false + if p.connUser == nil { + // We lazily create the user connection on demand to avoid opening unused sockets + create = true + } else if ra, ok := p.connUser.RemoteAddr().(*net.UDPAddr); ok && ra.Port != p.listenPort { + // Also recreate the user connection in case the WireGuard listen port has changed + create = true + } + + var newConnUser *net.UDPConn + if create { + if newConnUser, err = p.newUserConn(newConnICE); err != nil { + return fmt.Errorf("failed to setup user connection: %w", err) + } + p.logger.Info("Setup user-space proxy connection", + zap.Any("localAddress", newConnUser.LocalAddr()), + zap.Any("remoteAddress", newConnUser.RemoteAddr())) + } + + // Start copying if the underlying ice.Conn has changed + if newConnICE != p.connICE || newConnUser != p.connUser { + if p.connUser != nil { + if err := p.connUser.Close(); err != nil { + return fmt.Errorf("failed to close old user connection: %w", err) + } + } + + p.connICE = newConnICE + p.connUser = newConnUser + + // Bi-directional copy between ICE and loopback UDP sockets + go p.copy(newConnICE, p.connUser) + go p.copy(p.connUser, newConnICE) + } + + var ok bool + p.ep, ok = p.connUser.LocalAddr().(*net.UDPAddr) + if !ok { + return errInvalidCast + } + + return nil +} + func (p *KernelProxy) copy(dst io.Writer, src io.Reader) { buf := make([]byte, maxSegmentSize) for { @@ -167,11 +188,11 @@ func (p *KernelProxy) copy(dst io.Writer, src io.Reader) { } } -func (p *KernelProxy) newUserConn(iceConn *ice.Conn) (*net.UDPConn, error) { +func (p *KernelProxy) newUserConn(_ *ice.Conn) (*net.UDPConn, error) { // User-space proxying rAddr := net.UDPAddr{ IP: net.IPv6loopback, - Port: int(p.listenPort), + Port: p.listenPort, } lAddr := net.UDPAddr{ @@ -188,11 +209,12 @@ func (p *KernelProxy) newUserConn(iceConn *ice.Conn) (*net.UDPConn, error) { } func (p *KernelProxy) Type() epdiscproto.ProxyType { - if p.cp == nil { + switch { + case p.cp == nil: return epdiscproto.ProxyType_NO_PROXY - } else if p.cp.Local.Type() == ice.CandidateTypeHost || p.cp.Local.Type() == ice.CandidateTypeServerReflexive { + case p.cp.Local.Type() == ice.CandidateTypeHost || p.cp.Local.Type() == ice.CandidateTypeServerReflexive: return epdiscproto.ProxyType_KERNEL_NAT - } else { + default: return epdiscproto.ProxyType_KERNEL_CONN } } diff --git a/pkg/daemon/feature/epdisc/proxy/proxy_user.go b/pkg/daemon/feature/epdisc/proxy/proxy_user.go index 9c837b3c..8c4532a0 100644 --- a/pkg/daemon/feature/epdisc/proxy/proxy_user.go +++ b/pkg/daemon/feature/epdisc/proxy/proxy_user.go @@ -7,11 +7,9 @@ import ( "net" "github.com/pion/ice/v2" - "go.uber.org/zap" - - "github.com/stv0g/cunicu/pkg/wg" - epdiscproto "github.com/stv0g/cunicu/pkg/proto/feature/epdisc" + "github.com/stv0g/cunicu/pkg/wg" + "go.uber.org/zap" ) type UserBindProxy struct { diff --git a/pkg/daemon/feature/hooks/exec.go b/pkg/daemon/feature/hooks/exec.go index a1a16bb4..9aec63de 100644 --- a/pkg/daemon/feature/hooks/exec.go +++ b/pkg/daemon/feature/hooks/exec.go @@ -7,17 +7,15 @@ import ( "os/exec" "strings" + "github.com/stv0g/cunicu/pkg/config" + "github.com/stv0g/cunicu/pkg/core" + "github.com/stv0g/cunicu/pkg/daemon/feature/epdisc" + icex "github.com/stv0g/cunicu/pkg/ice" + "github.com/stv0g/cunicu/pkg/wg" "go.uber.org/zap" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" - - "github.com/stv0g/cunicu/pkg/config" - "github.com/stv0g/cunicu/pkg/core" - "github.com/stv0g/cunicu/pkg/daemon/feature/epdisc" - "github.com/stv0g/cunicu/pkg/wg" - - icex "github.com/stv0g/cunicu/pkg/ice" ) type ExecHook struct { @@ -47,8 +45,8 @@ func (h *ExecHook) run(msg proto.Message, args ...any) { allArgs = append(allArgs, fmt.Sprintf("%v", arg)) } - //#nosec G204 -- The filename should ne configurable by the user - cmd := exec.Command(h.Command, allArgs...) + // It the main purpose of an exec hook to run arbitrary external executables + cmd := exec.Command(h.Command, allArgs...) //nolint:gosec if msg != nil && h.Stdin { mo := protojson.MarshalOptions{ @@ -129,14 +127,14 @@ func (h *ExecHook) OnPeerModified(p *core.Peer, old *wgtypes.Peer, m core.PeerMo } if m.Is(core.PeerModifiedKeepaliveInterval) { - go h.run(pm, "modified", "peer", p.Interface.Name(), p.PublicKey(), "presistent-keepalive", p.PersistentKeepaliveInterval.Seconds(), old.PersistentKeepaliveInterval.Seconds()) + go h.run(pm, "modified", "peer", p.Interface.Name(), p.PublicKey(), "persistent-keepalive", p.PersistentKeepaliveInterval.Seconds(), old.PersistentKeepaliveInterval.Seconds()) } if m.Is(core.PeerModifiedHandshakeTime) { - new := fmt.Sprint(p.LastHandshakeTime.UnixMilli()) - old := fmt.Sprint(old.LastHandshakeTime.UnixMilli()) + newTime := fmt.Sprint(p.LastHandshakeTime.UnixMilli()) + oldTime := fmt.Sprint(old.LastHandshakeTime.UnixMilli()) - go h.run(pm, "modified", "peer", p.Interface.Name(), p.PublicKey(), "last-handshake", new, old) + go h.run(pm, "modified", "peer", p.Interface.Name(), p.PublicKey(), "last-handshake", newTime, oldTime) } if m.Is(core.PeerModifiedAllowedIPs) { @@ -162,9 +160,9 @@ func (h *ExecHook) OnPeerModified(p *core.Peer, old *wgtypes.Peer, m core.PeerMo } } -func (h *ExecHook) OnConnectionStateChange(p *epdisc.Peer, new, prev icex.ConnectionState) { +func (h *ExecHook) OnConnectionStateChange(p *epdisc.Peer, newState, prevState icex.ConnectionState) { m := p.Peer.Marshal() m.Ice = p.Marshal() - go h.run(m, "changed", "peer", "connection-state", p.Interface.Name(), p.PublicKey(), new, prev) + go h.run(m, "changed", "peer", "connection-state", p.Interface.Name(), p.PublicKey(), newState, prevState) } diff --git a/pkg/daemon/feature/hooks/hooks.go b/pkg/daemon/feature/hooks/hooks.go index 0385bebb..fb82a067 100644 --- a/pkg/daemon/feature/hooks/hooks.go +++ b/pkg/daemon/feature/hooks/hooks.go @@ -8,7 +8,7 @@ import ( "go.uber.org/zap" ) -func init() { +func init() { //nolint:gochecknoinits daemon.RegisterFeature("hooks", "Hooks", New, 70) } @@ -48,7 +48,9 @@ func New(i *daemon.Interface) (daemon.Feature, error) { h.OnPeer(hk) if f, ok := h.Features["epdisc"]; ok { - f.(*epdisc.Interface).OnConnectionStateChange(hk) + if ep, ok := f.(*epdisc.Interface); ok { + ep.OnConnectionStateChange(hk) + } } h.hooks = append(h.hooks, hk) diff --git a/pkg/daemon/feature/hooks/web.go b/pkg/daemon/feature/hooks/web.go index 055a8697..ca1f4b5c 100644 --- a/pkg/daemon/feature/hooks/web.go +++ b/pkg/daemon/feature/hooks/web.go @@ -6,20 +6,18 @@ import ( "net" "net/http" + "github.com/stv0g/cunicu/pkg/config" + "github.com/stv0g/cunicu/pkg/core" + "github.com/stv0g/cunicu/pkg/daemon/feature/epdisc" + icex "github.com/stv0g/cunicu/pkg/ice" + hooksproto "github.com/stv0g/cunicu/pkg/proto/feature/hooks" + rpcproto "github.com/stv0g/cunicu/pkg/proto/rpc" + "github.com/stv0g/cunicu/pkg/util/buildinfo" + "github.com/stv0g/cunicu/pkg/wg" "go.uber.org/zap" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" - - "github.com/stv0g/cunicu/pkg/config" - "github.com/stv0g/cunicu/pkg/core" - "github.com/stv0g/cunicu/pkg/daemon/feature/epdisc" - "github.com/stv0g/cunicu/pkg/util/buildinfo" - "github.com/stv0g/cunicu/pkg/wg" - - icex "github.com/stv0g/cunicu/pkg/ice" - hooksproto "github.com/stv0g/cunicu/pkg/proto/feature/hooks" - rpcproto "github.com/stv0g/cunicu/pkg/proto/rpc" ) type WebHook struct { @@ -66,13 +64,18 @@ func (h *WebHook) run(msg proto.Message) { req.Body = io.NopCloser(bytes.NewReader(buf)) } - if resp, err := http.DefaultClient.Do(req); err != nil { + resp, err := http.DefaultClient.Do(req) + if err != nil { h.logger.Error("Failed to invoke web-hook", zap.Error(err)) } else if resp.StatusCode != http.StatusOK { h.logger.Warn("Webhook endpoint responded with non-200 code", zap.String("status", resp.Status), zap.Int("status_code", resp.StatusCode)) } + + if err := resp.Body.Close(); err != nil { + h.logger.Error("Failed to close response body", zap.Error(err)) + } } func (h *WebHook) OnInterfaceAdded(i *core.Interface) { @@ -119,7 +122,7 @@ func (h *WebHook) OnPeerModified(p *core.Peer, old *wgtypes.Peer, m core.PeerMod }) } -func (h *WebHook) OnConnectionStateChange(p *epdisc.Peer, new, prev icex.ConnectionState) { +func (h *WebHook) OnConnectionStateChange(p *epdisc.Peer, newState, prevState icex.ConnectionState) { pm := marshalRedactedPeer(p.Peer) pm.Ice = p.Marshal() diff --git a/pkg/daemon/feature/hsync/host.go b/pkg/daemon/feature/hsync/host.go index 712dc0dc..16b77c75 100644 --- a/pkg/daemon/feature/hsync/host.go +++ b/pkg/daemon/feature/hsync/host.go @@ -1,11 +1,16 @@ package hsync import ( - "fmt" + "errors" "net" "strings" ) +var ( + errMissingNames = errors.New("missing names") + errInvalidIPAddress = errors.New("failed to parse IP address") +) + type Host struct { IP net.IP Names []string @@ -25,11 +30,11 @@ func ParseHost(line string) (Host, error) { if len(ipNameStrs) > 1 { h.Names = ipNameStrs[1:] } else { - return h, fmt.Errorf("missing names") + return h, errMissingNames } if h.IP = net.ParseIP(ipNameStrs[0]); h.IP == nil { - return h, fmt.Errorf("failed to parse IP address") + return h, errInvalidIPAddress } return h, nil diff --git a/pkg/daemon/feature/hsync/hsync.go b/pkg/daemon/feature/hsync/hsync.go index 1fe1f005..a06b0660 100644 --- a/pkg/daemon/feature/hsync/hsync.go +++ b/pkg/daemon/feature/hsync/hsync.go @@ -17,7 +17,7 @@ const ( hostsPath = "/etc/hosts" ) -func init() { +func init() { //nolint:gochecknoinits daemon.RegisterFeature("hsync", "Hosts synchronization", New, 200) } diff --git a/pkg/daemon/feature/hsync/util.go b/pkg/daemon/feature/hsync/util.go index 6480ae8f..a147bba4 100644 --- a/pkg/daemon/feature/hsync/util.go +++ b/pkg/daemon/feature/hsync/util.go @@ -8,7 +8,7 @@ import ( func readLines(fn string) ([]string, error) { //#nosec G304 -- Filename is hard coded. - f, err := os.OpenFile(fn, os.O_CREATE|os.O_RDONLY, 0600) + f, err := os.OpenFile(fn, os.O_CREATE|os.O_RDONLY, 0o600) if err != nil { return nil, fmt.Errorf("failed to open file: %w", err) } @@ -29,8 +29,8 @@ func readLines(fn string) ([]string, error) { func writeLines(fn string, lines []string) error { //#nosec G302 -- /etc/hosts must be world readable - //#nosec G304 -- Filename is hard coded - f, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755) + //#nosec G304 -- Filename is hard coded. + f, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o755) if err != nil { return fmt.Errorf("failed to open file: %w", err) } diff --git a/pkg/daemon/feature/pdisc/handlers.go b/pkg/daemon/feature/pdisc/handlers.go index 22b32202..6b4090e3 100644 --- a/pkg/daemon/feature/pdisc/handlers.go +++ b/pkg/daemon/feature/pdisc/handlers.go @@ -58,7 +58,7 @@ func (i *Interface) OnSignalingMessage(kp *crypto.PublicKeyPair, msg *signaling. } } -func (i *Interface) OnPeerDescription(d *pdiscproto.PeerDescription) error { +func (i *Interface) OnPeerDescription(d *pdiscproto.PeerDescription) error { //nolint:gocognit i.logger.Debug("Received peer description", zap.Any("description", d)) pk, err := crypto.ParseKeyBytes(d.PublicKey) @@ -123,9 +123,10 @@ func (i *Interface) OnPeerDescription(d *pdiscproto.PeerDescription) error { // Update hostname if it has been changed if f, ok := i.Features["hsync"]; ok { - hs := f.(*hsync.Interface) - if err := hs.Sync(); err != nil { - return fmt.Errorf("failed to sync hosts: %w", err) + if hs, ok := f.(*hsync.Interface); ok { + if err := hs.Sync(); err != nil { + return fmt.Errorf("failed to sync hosts: %w", err) + } } } } diff --git a/pkg/daemon/feature/pdisc/pdisc.go b/pkg/daemon/feature/pdisc/pdisc.go index 3d843dae..55ee776e 100644 --- a/pkg/daemon/feature/pdisc/pdisc.go +++ b/pkg/daemon/feature/pdisc/pdisc.go @@ -3,23 +3,24 @@ package pdisc import ( "context" + "errors" "fmt" "net" - "go.uber.org/zap" - "github.com/stv0g/cunicu/pkg/core" "github.com/stv0g/cunicu/pkg/crypto" "github.com/stv0g/cunicu/pkg/daemon" + proto "github.com/stv0g/cunicu/pkg/proto/core" + pdiscproto "github.com/stv0g/cunicu/pkg/proto/feature/pdisc" "github.com/stv0g/cunicu/pkg/signaling" "github.com/stv0g/cunicu/pkg/util" "github.com/stv0g/cunicu/pkg/util/buildinfo" - - proto "github.com/stv0g/cunicu/pkg/proto/core" - pdiscproto "github.com/stv0g/cunicu/pkg/proto/feature/pdisc" + "go.uber.org/zap" ) -func init() { +var errFailedUpdatePublicKey = errors.New("can not change public key in non-update message") + +func init() { //nolint:gochecknoinits daemon.RegisterFeature("pdisc", "Peer discovery", New, 60) } @@ -45,11 +46,11 @@ func New(i *daemon.Interface) (daemon.Feature, error) { } for _, k := range pd.Settings.Whitelist { - pd.filter[crypto.Key(k)] = true + pd.filter[k] = true } for _, k := range pd.Settings.Blacklist { - pd.filter[crypto.Key(k)] = false + pd.filter[k] = false } // Avoid sending a peer description if the interface does not have a private key yet @@ -71,7 +72,7 @@ func (i *Interface) Start() error { // Subscribe to peer updates kp := &crypto.KeyPair{ Ours: crypto.Key(i.Settings.Community), - Theirs: signaling.AnyKey, + Theirs: crypto.Key{}, } if _, err := i.Daemon.Backend.Subscribe(context.Background(), kp, i); err != nil { return fmt.Errorf("failed to subscribe on peer discovery channel: %w", err) @@ -127,7 +128,6 @@ func (i *Interface) sendPeerDescription(chg pdiscproto.PeerDescriptionChange, pk // Other networks for _, netw := range i.Settings.Networks { netw := netw - allowedIPs = append(allowedIPs, &netw) } @@ -166,7 +166,7 @@ func (i *Interface) sendPeerDescription(chg pdiscproto.PeerDescriptionChange, pk if pkOld != nil { if d.Change != pdiscproto.PeerDescriptionChange_PEER_UPDATE { - return fmt.Errorf("can not change public key in non-update message") + return errFailedUpdatePublicKey } d.PublicKeyNew = i.PublicKey().Bytes() diff --git a/pkg/daemon/feature/rtsync/handlers.go b/pkg/daemon/feature/rtsync/handlers.go index 2e4a4256..7aa8810c 100644 --- a/pkg/daemon/feature/rtsync/handlers.go +++ b/pkg/daemon/feature/rtsync/handlers.go @@ -2,7 +2,6 @@ package rtsync import ( "errors" - "fmt" "net" "net/netip" "syscall" @@ -19,7 +18,7 @@ func (i *Interface) OnPeerAdded(p *core.Peer) { gwn := pk.IPAddress(q) gw, ok := netip.AddrFromSlice(gwn.IP) if !ok { - panic(fmt.Errorf("failed to get address from slice: %s", gwn)) + panic("failed to get address from slice") } i.gwMap[gw] = p @@ -40,7 +39,7 @@ func (i *Interface) OnPeerRemoved(p *core.Peer) { gwn := pk.IPAddress(q) gw, ok := netip.AddrFromSlice(gwn.IP) if !ok { - panic(fmt.Errorf("failed to get address from slice: %s", gwn)) + panic("failed to get address from slice") } delete(i.gwMap, gw) diff --git a/pkg/daemon/feature/rtsync/rtsync.go b/pkg/daemon/feature/rtsync/rtsync.go index da75518e..932b2f67 100644 --- a/pkg/daemon/feature/rtsync/rtsync.go +++ b/pkg/daemon/feature/rtsync/rtsync.go @@ -5,15 +5,13 @@ import ( "errors" "net/netip" - "go.uber.org/zap" - "github.com/stv0g/cunicu/pkg/core" "github.com/stv0g/cunicu/pkg/daemon" - xerrors "github.com/stv0g/cunicu/pkg/errors" + "go.uber.org/zap" ) -func init() { +func init() { //nolint:gochecknoinits daemon.RegisterFeature("rtsync", "Route synchronization", New, 30) } diff --git a/pkg/daemon/feature/rtsync/sync_linux.go b/pkg/daemon/feature/rtsync/sync_linux.go index 5eac3c07..48aa8d94 100644 --- a/pkg/daemon/feature/rtsync/sync_linux.go +++ b/pkg/daemon/feature/rtsync/sync_linux.go @@ -153,7 +153,7 @@ func (i *Interface) handleRouteUpdate(ru *netlink.RouteUpdate) error { gw, ok := netip.AddrFromSlice(ru.Gw) if !ok { - return fmt.Errorf("failed to get address from slice") + panic("failed to get address from slice") } p, ok := i.gwMap[gw] diff --git a/pkg/device/device_bsd.go b/pkg/device/device_bsd.go index 8f37cbad..75b82f14 100644 --- a/pkg/device/device_bsd.go +++ b/pkg/device/device_bsd.go @@ -3,8 +3,10 @@ package device import ( + "errors" "fmt" "net" + "os/exec" "regexp" "strconv" "strings" @@ -12,6 +14,11 @@ import ( "go.uber.org/zap" ) +var ( + errInvalidCommandOutput = errors.New("invalid command output") + errFailedToExecute = errors.New("failed to run") +) + type BSDKernelDevice struct { created bool index int @@ -112,7 +119,7 @@ func (d *BSDKernelDevice) MTU() int { panic(err) } - mtuStrs := mtuRegex.FindStringSubmatch(string(out)) + mtuStrs := mtuRegex.FindStringSubmatch(out) if len(mtuStrs) < 2 { panic("no MTU found") } @@ -185,12 +192,12 @@ func getRouteMTU(ip net.IP) (int, error) { } out = strings.TrimSpace(out) - lines := strings.Split(string(out), "\n") + lines := strings.Split(out, "\n") lastLine := lines[len(lines)-1] fields := strings.Fields(lastLine) if len(fields) < 7 { - return -1, fmt.Errorf("invalid command output: %s", lastLine) + return -1, fmt.Errorf("%w: %s", errInvalidCommandOutput, lastLine) } return strconv.Atoi(fields[6]) @@ -203,7 +210,7 @@ func run(args ...string) (string, error) { out, err := cmd.CombinedOutput() outStr := string(out) if err != nil { - return "", fmt.Errorf("failed to run: %s\n%s", strings.Join(args, " "), outStr) + return "", fmt.Errorf("%w: %s\n%s", errFailedToExecute, strings.Join(args, " "), outStr) } return outStr, nil diff --git a/pkg/device/device_freebsd.go b/pkg/device/device_freebsd.go index f941b4c2..47a6994c 100644 --- a/pkg/device/device_freebsd.go +++ b/pkg/device/device_freebsd.go @@ -3,7 +3,6 @@ package device import ( "fmt" "net" - "os/exec" "go.uber.org/zap" ) diff --git a/pkg/device/device_linux.go b/pkg/device/device_linux.go index c8841730..723bd4c2 100644 --- a/pkg/device/device_linux.go +++ b/pkg/device/device_linux.go @@ -15,6 +15,11 @@ import ( "golang.org/x/sys/unix" ) +var ( + errNoRouteToDestination = errors.New("no route to destination") + errNoRoutesOrInterfaceFound = errors.New("no routes or interfaces found") +) + const ( ipr2TablesFile = "/etc/iproute2/rt_tables" ) @@ -198,14 +203,14 @@ func DetectDefaultMTU(fwmark int) (int, error) { // the default MTU of the link which is used by the route as next hop. func mtuFromRoutes(rts []netlink.Route) (int, error) { if len(rts) == 0 { - return -1, errors.New("no route to destination") + return -1, errNoRouteToDestination } var err error var mtu int - var links = map[int]netlink.Link{} - var linkMTU = math.MaxInt - var routeMTU = math.MaxInt + links := map[int]netlink.Link{} + linkMTU := math.MaxInt + routeMTU := math.MaxInt for _, rt := range rts { if rt.MTU != 0 && rt.MTU < routeMTU { @@ -231,7 +236,7 @@ func mtuFromRoutes(rts []netlink.Route) (int, error) { if mtu = routeMTU; mtu == math.MaxInt { if mtu = linkMTU; mtu == math.MaxInt { - return -1, fmt.Errorf("no routes or interfaces found") + return -1, errNoRoutesOrInterfaceFound } } @@ -239,7 +244,7 @@ func mtuFromRoutes(rts []netlink.Route) (int, error) { } func Table(str string) (int, error) { - if f, err := os.OpenFile(ipr2TablesFile, os.O_RDONLY, 0600); err == nil { + if f, err := os.OpenFile(ipr2TablesFile, os.O_RDONLY, 0o600); err == nil { sc := bufio.NewScanner(f) for sc.Scan() { line := sc.Text() diff --git a/pkg/device/device_linux_test.go b/pkg/device/device_linux_test.go index 01b72f0b..b5945f89 100644 --- a/pkg/device/device_linux_test.go +++ b/pkg/device/device_linux_test.go @@ -8,15 +8,14 @@ import ( "net" "syscall" - "github.com/stv0g/cunicu/pkg/config" - "github.com/stv0g/cunicu/pkg/device" - "golang.org/x/sys/unix" - wgdevice "golang.zx2c4.com/wireguard/device" - . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/stv0g/cunicu/pkg/config" + "github.com/stv0g/cunicu/pkg/device" g "github.com/stv0g/gont/pkg" nl "github.com/vishvananda/netlink" + "golang.org/x/sys/unix" + wgdevice "golang.zx2c4.com/wireguard/device" ) var _ = Describe("device", func() { @@ -41,7 +40,7 @@ var _ = Describe("device", func() { } JustBeforeEach(OncePerOrdered, func() { - name := fmt.Sprintf("wg-test-%d", rand.Intn(1000)) + name := fmt.Sprintf("wg-test-%d", rand.Intn(1000)) //nolint:gosec ns, err = g.NewNamespace(name) Expect(err).To(Succeed()) diff --git a/pkg/device/device_user.go b/pkg/device/device_user.go index 5715fa10..def7b47b 100644 --- a/pkg/device/device_user.go +++ b/pkg/device/device_user.go @@ -13,6 +13,7 @@ import ( "golang.zx2c4.com/wireguard/tun" ) +//nolint:gochecknoglobals var ( userDevices = map[string]*UserDevice{} userDevicesLock sync.Mutex diff --git a/pkg/device/device_windows.go b/pkg/device/device_windows.go index b45e02e9..1b526d3a 100644 --- a/pkg/device/device_windows.go +++ b/pkg/device/device_windows.go @@ -4,9 +4,8 @@ import ( "net" "strconv" - "go.uber.org/zap" - "github.com/stv0g/cunicu/pkg/errors" + "go.uber.org/zap" ) type WindowsKernelDevice struct { diff --git a/pkg/ice/types.go b/pkg/ice/types.go index 25cba449..9382750e 100644 --- a/pkg/ice/types.go +++ b/pkg/ice/types.go @@ -1,11 +1,17 @@ package ice import ( + "errors" "fmt" "github.com/pion/ice/v2" ) +var ( + errUnknownCandidateType = errors.New("unknown candidate type") + errUnknownNetworkType = errors.New("unknown network type") +) + type URL struct { ice.URL } @@ -41,7 +47,7 @@ func (t *CandidateType) UnmarshalText(text []byte) error { t.CandidateType = ice.CandidateTypeRelay default: t.CandidateType = ice.CandidateTypeUnspecified - return fmt.Errorf("unknown candidate type: %s", text) + return fmt.Errorf("%w: %s", errUnknownCandidateType, text) } return nil @@ -71,7 +77,7 @@ func (t *NetworkType) UnmarshalText(text []byte) error { t.NetworkType = ice.NetworkTypeTCP6 default: t.NetworkType = ice.NetworkTypeTCP4 - return fmt.Errorf("unknown network type: %s", text) + return fmt.Errorf("%w: %s", errUnknownNetworkType, text) } return nil diff --git a/pkg/ice/types_test.go b/pkg/ice/types_test.go index a8c5bde1..cebd6402 100644 --- a/pkg/ice/types_test.go +++ b/pkg/ice/types_test.go @@ -1,11 +1,9 @@ package ice_test import ( - "github.com/pion/ice/v2" - . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - + "github.com/pion/ice/v2" icex "github.com/stv0g/cunicu/pkg/ice" ) @@ -25,7 +23,7 @@ var _ = Describe("Marshaling of ICE types", func() { }, t) DescribeTable("Marshal", func(ct ice.CandidateType, st string) { - var ctp = icex.CandidateType{ct} + ctp := icex.CandidateType{ct} m, err := ctp.MarshalText() Expect(err).To(Succeed()) Expect(string(m)).To(Equal(st)) @@ -47,7 +45,7 @@ var _ = Describe("Marshaling of ICE types", func() { }, t) DescribeTable("Marshal", func(ct ice.NetworkType, st string) { - var ntp = icex.NetworkType{ct} + ntp := icex.NetworkType{ct} m, err := ntp.MarshalText() Expect(err).To(Succeed()) Expect(string(m)).To(Equal(st)) @@ -95,7 +93,7 @@ var _ = Describe("Marshaling of ICE types", func() { }, t) DescribeTable("Marshal", func(urlStr string, url ice.URL) { - var u = icex.URL{url} + u := icex.URL{url} m, err := u.MarshalText() Expect(err).To(Succeed()) Expect(string(m)).To(BeEquivalentTo(urlStr)) diff --git a/pkg/log/log.go b/pkg/log/log.go index 8e9ea505..2f2b12fd 100644 --- a/pkg/log/log.go +++ b/pkg/log/log.go @@ -8,6 +8,7 @@ import ( "k8s.io/klog/v2" ) +//nolint:gochecknoglobals var ( Verbosity VerbosityLevel Severity zap.AtomicLevel diff --git a/pkg/log/log_test.go b/pkg/log/log_test.go index 16404ee8..4e65b152 100644 --- a/pkg/log/log_test.go +++ b/pkg/log/log_test.go @@ -3,13 +3,12 @@ package log_test import ( "fmt" "io" + stdlog "log" "os" "path/filepath" "regexp" "testing" - stdlog "log" - . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/stv0g/cunicu/pkg/log" diff --git a/pkg/log/verbosity.go b/pkg/log/verbosity.go index 8224a311..664360b2 100644 --- a/pkg/log/verbosity.go +++ b/pkg/log/verbosity.go @@ -17,18 +17,18 @@ func NewVerbosityLevel() VerbosityLevel { func NewVerbosityLevelAt(l int) VerbosityLevel { a := NewVerbosityLevel() a.SetLevel(l) - return a + return a //nolint:govet } -func (lvl VerbosityLevel) Enabled(l int) bool { +func (lvl VerbosityLevel) Enabled(l int) bool { //nolint:govet return lvl.Level() >= l || l == 0 } -func (lvl VerbosityLevel) Level() int { +func (lvl VerbosityLevel) Level() int { //nolint:govet return int(lvl.l.Load()) } // SetLevel alters the logging level. -func (lvl VerbosityLevel) SetLevel(l int) { +func (lvl VerbosityLevel) SetLevel(l int) { //nolint:govet lvl.l.Store(int32(l)) } diff --git a/pkg/proto/core/peer.go b/pkg/proto/core/peer.go index 709aa672..61c2512e 100644 --- a/pkg/proto/core/peer.go +++ b/pkg/proto/core/peer.go @@ -50,7 +50,7 @@ func (p *Peer) Peer() wgtypes.Peer { return q } -func (p *Peer) Dump(wr io.Writer, verbosity int) error { +func (p *Peer) Dump(wr io.Writer, verbosity int) error { //nolint:gocognit wri := t.NewIndenter(wr, " ") if _, err := fmt.Fprintf(wr, t.Mods("peer", t.Bold, t.FgYellow)+": "+t.Mods("%s", t.FgYellow)+"\n", p.Name); err != nil { diff --git a/pkg/proto/feature/epdisc/epdisc.go b/pkg/proto/feature/epdisc/epdisc.go index 6784c45d..4245921c 100644 --- a/pkg/proto/feature/epdisc/epdisc.go +++ b/pkg/proto/feature/epdisc/epdisc.go @@ -7,12 +7,10 @@ import ( "github.com/pion/ice/v2" "github.com/pion/randutil" - "golang.org/x/exp/slices" - + icex "github.com/stv0g/cunicu/pkg/ice" "github.com/stv0g/cunicu/pkg/util" t "github.com/stv0g/cunicu/pkg/util/terminal" - - icex "github.com/stv0g/cunicu/pkg/ice" + "golang.org/x/exp/slices" ) const ( @@ -53,7 +51,7 @@ func (i *Interface) Dump(wr io.Writer, verbosity int) error { return nil } -func (p *Peer) Dump(wr io.Writer, verbosity int) error { +func (p *Peer) Dump(wr io.Writer, verbosity int) error { //nolint:gocognit var v string if _, err := t.FprintKV(wr, "state", t.Mods(p.State.String(), t.Bold, p.State.Color())); err != nil { @@ -82,7 +80,7 @@ func (p *Peer) Dump(wr io.Writer, verbosity int) error { } if verbosity > 5 && len(p.CandidatePairStats) > 0 { - var cmap = map[string]int{} + cmap := map[string]int{} var cpsNom *CandidatePairStats if len(p.CandidatePairStats) > 0 { diff --git a/pkg/proto/feature/epdisc/epdisc_candidate.go b/pkg/proto/feature/epdisc/epdisc_candidate.go index 3f38aaee..317dee5f 100644 --- a/pkg/proto/feature/epdisc/epdisc_candidate.go +++ b/pkg/proto/feature/epdisc/epdisc_candidate.go @@ -5,9 +5,7 @@ import ( "io" "github.com/pion/ice/v2" - "github.com/stv0g/cunicu/pkg/proto" - t "github.com/stv0g/cunicu/pkg/util/terminal" ) @@ -40,8 +38,8 @@ func NewCandidate(ic ice.Candidate) *Candidate { func (c *Candidate) ICECandidate() (ice.Candidate, error) { var err error - var relAddr = "" - var relPort = 0 + relAddr := "" + relPort := 0 if c.RelatedAddress != nil { relAddr = c.RelatedAddress.Address relPort = int(c.RelatedAddress.Port) @@ -98,7 +96,7 @@ func (c *Candidate) ICECandidate() (ice.Candidate, error) { }) default: - err = fmt.Errorf("unknown candidate type: %s", c.Type) + err = fmt.Errorf("%w: %s", ice.ErrUnknownCandidateTyp, c.Type) } return ic, err @@ -115,7 +113,7 @@ func NewCandidatePairStats(cps *ice.CandidatePairStats) *CandidatePairStats { BytesSent: cps.BytesSent, BytesReceived: cps.BytesReceived, TotalRoundtripTime: cps.TotalRoundTripTime, - CurrenTroundtripTime: cps.CurrentRoundTripTime, + CurrentRoundtripTime: cps.CurrentRoundTripTime, AvailableOutgoingBitrate: cps.AvailableOutgoingBitrate, AvailableIncomingBitrate: cps.AvailableIncomingBitrate, CircuitBreakerTriggerCount: cps.CircuitBreakerTriggerCount, @@ -184,6 +182,7 @@ func (c *Candidate) ToString() string { addr = fmt.Sprintf("[%s]", c.Address) case NetworkType_NETWORK_TYPE_UDP4, NetworkType_NETWORK_TYPE_TCP4: addr = c.Address + case NetworkType_NETWORK_TYPE_UNSPECIFIED: } var nt string @@ -206,6 +205,7 @@ func (cs *CandidateStats) ToString() string { addr = fmt.Sprintf("[%s]", cs.Ip) case NetworkType_NETWORK_TYPE_UDP4, NetworkType_NETWORK_TYPE_TCP4: addr = cs.Ip + case NetworkType_NETWORK_TYPE_UNSPECIFIED: } var nt string diff --git a/pkg/proto/feature/epdisc/epdisc_candidate.pb.go b/pkg/proto/feature/epdisc/epdisc_candidate.pb.go index bed383b5..da9ea777 100644 --- a/pkg/proto/feature/epdisc/epdisc_candidate.pb.go +++ b/pkg/proto/feature/epdisc/epdisc_candidate.pb.go @@ -602,7 +602,7 @@ type CandidatePairStats struct { // CurrentRoundTripTime represents the latest round trip time measured in seconds, // computed from both STUN connectivity checks, including those that are sent // for consent verification. - CurrenTroundtripTime float64 `protobuf:"fixed64,16,opt,name=curren_troundtrip_time,json=currenTroundtripTime,proto3" json:"curren_troundtrip_time,omitempty"` + CurrentRoundtripTime float64 `protobuf:"fixed64,16,opt,name=current_roundtrip_time,json=currentRoundtripTime,proto3" json:"current_roundtrip_time,omitempty"` // AvailableOutgoingBitrate is calculated by the underlying congestion control // by combining the available bitrate for all the outgoing RTP streams using // this candidate pair. The bitrate measurement does not count the size of the @@ -784,9 +784,9 @@ func (x *CandidatePairStats) GetTotalRoundtripTime() float64 { return 0 } -func (x *CandidatePairStats) GetCurrenTroundtripTime() float64 { +func (x *CandidatePairStats) GetCurrentRoundtripTime() float64 { if x != nil { - return x.CurrenTroundtripTime + return x.CurrentRoundtripTime } return 0 } @@ -1115,9 +1115,9 @@ var file_feature_epdisc_candidate_proto_rawDesc = []byte{ 0x30, 0x0a, 0x14, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x74, 0x72, 0x69, 0x70, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x01, 0x52, 0x12, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x74, 0x72, 0x69, 0x70, 0x54, 0x69, 0x6d, - 0x65, 0x12, 0x34, 0x0a, 0x16, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x5f, 0x74, 0x72, 0x6f, 0x75, + 0x65, 0x12, 0x34, 0x0a, 0x16, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x74, 0x72, 0x69, 0x70, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x10, 0x20, 0x01, 0x28, - 0x01, 0x52, 0x14, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x54, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x74, + 0x01, 0x52, 0x14, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x74, 0x72, 0x69, 0x70, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x3c, 0x0a, 0x1a, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x62, 0x69, 0x74, 0x72, 0x61, 0x74, 0x65, 0x18, 0x11, 0x20, 0x01, 0x28, 0x01, 0x52, 0x18, 0x61, 0x76, 0x61, diff --git a/pkg/proto/feature/epdisc/types.go b/pkg/proto/feature/epdisc/types.go index 3f7b109e..c529c544 100644 --- a/pkg/proto/feature/epdisc/types.go +++ b/pkg/proto/feature/epdisc/types.go @@ -25,6 +25,8 @@ func (p RelayProtocol) ToString() string { return "dtls" case RelayProtocol_RELAY_PROTOCOL_TLS: return "tls" + case RelayProtocol_RELAY_PROTOCOL_UNSPECIFIED: + return "unspecified" } return "unknown" diff --git a/pkg/proto/signaling/signaling.go b/pkg/proto/signaling/signaling.go index 61ff192b..040e2e24 100644 --- a/pkg/proto/signaling/signaling.go +++ b/pkg/proto/signaling/signaling.go @@ -9,6 +9,12 @@ import ( "google.golang.org/protobuf/proto" ) +var ( + errKeyPairMismatch = errors.New("key pair mismatch") + errInvalidNonceLength = errors.New("invalid nonce length") + errFailedToDecrypt = errors.New("failed to open") +) + func (e *Envelope) PublicKeyPair() (crypto.PublicKeyPair, error) { sender, err := crypto.ParseKeyBytes(e.Sender) if err != nil { @@ -33,7 +39,7 @@ func (e *Envelope) Decrypt(kp *crypto.KeyPair) (*Message, error) { } if ekp != kp.Public() { - return nil, errors.New("key pair mismatch") + return nil, errKeyPairMismatch } msg := &Message{} @@ -51,8 +57,12 @@ func (e *Message) Encrypt(kp *crypto.KeyPair) (*Envelope, error) { } func (e *Envelope) DeepCopyInto(out *Envelope) { - p := proto.Clone(e).(*Envelope) - *out = *p + p, ok := proto.Clone(e).(*Envelope) + if !ok { + panic("type assertion failed") + } + + *out = *p //nolint:govet } func (s *EncryptedMessage) Marshal(msg proto.Message, kp *crypto.KeyPair) error { @@ -76,12 +86,12 @@ func (s *EncryptedMessage) Marshal(msg proto.Message, kp *crypto.KeyPair) error func (s *EncryptedMessage) Unmarshal(msg proto.Message, kp *crypto.KeyPair) error { if len(s.Nonce) != 24 { - return errors.New("invalid nonce length") + return errInvalidNonceLength } body, ok := box.Open([]byte{}, s.Body, (*[24]byte)(s.Nonce), (*[32]byte)(&kp.Theirs), (*[32]byte)(&kp.Ours)) if !ok { - return errors.New("failed to open") + return errFailedToDecrypt } return proto.Unmarshal(body, msg) diff --git a/pkg/proto/signaling/signaling_test.go b/pkg/proto/signaling/signaling_test.go index e549097c..f591f0fd 100644 --- a/pkg/proto/signaling/signaling_test.go +++ b/pkg/proto/signaling/signaling_test.go @@ -3,13 +3,11 @@ package signaling_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "google.golang.org/protobuf/proto" - "github.com/stv0g/cunicu/pkg/crypto" - "github.com/stv0g/cunicu/test" - epdiscproto "github.com/stv0g/cunicu/pkg/proto/feature/epdisc" signalingproto "github.com/stv0g/cunicu/pkg/proto/signaling" + "github.com/stv0g/cunicu/test" + "google.golang.org/protobuf/proto" ) var _ = Describe("message encryption", func() { diff --git a/pkg/rpc/client.go b/pkg/rpc/client.go index 4e7b4b39..e3be4dd7 100644 --- a/pkg/rpc/client.go +++ b/pkg/rpc/client.go @@ -23,6 +23,11 @@ import ( "google.golang.org/grpc/status" ) +var ( + errTimeout = errors.New("timed out") + errChannelClosed = errors.New("event channel closed") +) + type Client struct { io.Closer @@ -54,7 +59,7 @@ func waitForSocket(path string) error { time.Sleep(10 * time.Millisecond) } - return fmt.Errorf("timed out") + return errTimeout } func Connect(path string) (*Client, error) { @@ -146,7 +151,7 @@ func (c *Client) WaitForEvent(ctx context.Context, t rpcproto.EventType, intf st select { case e, ok := <-c.Events: if !ok { - return nil, errors.New("event channel closed") + return nil, errChannelClosed } if e.Type != t { diff --git a/pkg/rpc/events.go b/pkg/rpc/events.go index 4d041855..f31432f1 100644 --- a/pkg/rpc/events.go +++ b/pkg/rpc/events.go @@ -3,14 +3,12 @@ package rpc import ( "net" - "golang.zx2c4.com/wireguard/wgctrl/wgtypes" - "github.com/stv0g/cunicu/pkg/core" "github.com/stv0g/cunicu/pkg/crypto" + rpcproto "github.com/stv0g/cunicu/pkg/proto/rpc" "github.com/stv0g/cunicu/pkg/signaling" "github.com/stv0g/cunicu/pkg/wg" - - rpcproto "github.com/stv0g/cunicu/pkg/proto/rpc" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) func (s *Server) OnInterfaceAdded(i *core.Interface) { @@ -82,5 +80,4 @@ func (s *Server) OnSignalingBackendReady(b signaling.Backend) { } func (s *Server) OnSignalingMessage(kp *crypto.PublicKeyPair, msg *signaling.Message) { - } diff --git a/pkg/rpc/server.go b/pkg/rpc/server.go index 3f4fd71e..eb491c0d 100644 --- a/pkg/rpc/server.go +++ b/pkg/rpc/server.go @@ -7,13 +7,11 @@ import ( "os" "sync" + "github.com/stv0g/cunicu/pkg/daemon" + rpcproto "github.com/stv0g/cunicu/pkg/proto/rpc" + "github.com/stv0g/cunicu/pkg/util" "go.uber.org/zap" "google.golang.org/grpc" - - "github.com/stv0g/cunicu/pkg/daemon" - "github.com/stv0g/cunicu/pkg/util" - - rpcproto "github.com/stv0g/cunicu/pkg/proto/rpc" ) type Server struct { @@ -56,7 +54,11 @@ func NewServer(d *daemon.Daemon, socket string) (*Server, error) { return nil, fmt.Errorf("failed to listen at %s: %w", socket, err) } - go s.grpc.Serve(l) + go func() { + if err := s.grpc.Serve(l); err != nil { + s.logger.Error("Failed to serve", zap.Error(err)) + } + }() return s, nil } diff --git a/pkg/rpc/server_daemon.go b/pkg/rpc/server_daemon.go index 4447a1f2..978070d7 100644 --- a/pkg/rpc/server_daemon.go +++ b/pkg/rpc/server_daemon.go @@ -12,23 +12,27 @@ import ( "strconv" "strings" - "go.uber.org/zap" - "go.uber.org/zap/zapcore" - "golang.zx2c4.com/wireguard/wgctrl/wgtypes" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - "github.com/stv0g/cunicu/pkg/core" "github.com/stv0g/cunicu/pkg/crypto" "github.com/stv0g/cunicu/pkg/daemon" "github.com/stv0g/cunicu/pkg/daemon/feature/epdisc" "github.com/stv0g/cunicu/pkg/log" "github.com/stv0g/cunicu/pkg/proto" - "github.com/stv0g/cunicu/pkg/util" - "github.com/stv0g/cunicu/pkg/util/buildinfo" - coreproto "github.com/stv0g/cunicu/pkg/proto/core" rpcproto "github.com/stv0g/cunicu/pkg/proto/rpc" + "github.com/stv0g/cunicu/pkg/util" + "github.com/stv0g/cunicu/pkg/util/buildinfo" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +var ( + errInvalidVerbosityLevel = errors.New("invalid verbosity level (must be between 0 and 10 inclusive)") + errInvalidSeverityLevel = errors.New("invalid severity level") + errNoSettingChanged = errors.New("no setting was changed") ) type DaemonServer struct { @@ -50,7 +54,6 @@ func NewDaemonServer(s *Server, d *daemon.Daemon) *DaemonServer { } func (s *DaemonServer) StreamEvents(params *proto.Empty, stream rpcproto.Daemon_StreamEventsServer) error { - // Send initial connection state of all peers if s.epdisc != nil { s.epdisc.SendConnectionStates(stream) @@ -63,7 +66,7 @@ out: for { select { case event := <-events: - if err := stream.Send(event); err == io.EOF { + if err := stream.Send(event); errors.Is(err, io.EOF) { break out } else if err != nil { return fmt.Errorf("failed to send event: %w", err) @@ -116,7 +119,7 @@ func (s *DaemonServer) Sync(ctx context.Context, params *proto.Empty) (*proto.Em return &proto.Empty{}, nil } -func (s *DaemonServer) GetStatus(ctx context.Context, p *rpcproto.GetStatusParams) (*rpcproto.GetStatusResp, error) { +func (s *DaemonServer) GetStatus(ctx context.Context, p *rpcproto.GetStatusParams) (*rpcproto.GetStatusResp, error) { //nolint:gocognit var err error var pk crypto.Key @@ -184,7 +187,7 @@ func (s *DaemonServer) SetConfig(ctx context.Context, p *rpcproto.SetConfigParam errs = append(errs, fmt.Errorf("invalid level: %w", err)) break } else if level > 10 || level < 0 { - errs = append(errs, fmt.Errorf("invalid level (must be between 0 and 10 inclusive)")) + errs = append(errs, errInvalidVerbosityLevel) break } @@ -196,7 +199,7 @@ func (s *DaemonServer) SetConfig(ctx context.Context, p *rpcproto.SetConfigParam errs = append(errs, fmt.Errorf("invalid level: %w", err)) break } else if level < zapcore.DebugLevel || level > zapcore.FatalLevel { - errs = append(errs, fmt.Errorf("invalid level")) + errs = append(errs, errInvalidSeverityLevel) break } @@ -215,7 +218,7 @@ func (s *DaemonServer) SetConfig(ctx context.Context, p *rpcproto.SetConfigParam if err != nil { errs = append(errs, err) } else if len(changes) == 0 { - errs = append(errs, errors.New("no setting was changed")) + errs = append(errs, errNoSettingChanged) } if len(errs) > 0 { @@ -305,20 +308,20 @@ func (s *DaemonServer) AddPeer(ctx context.Context, params *rpcproto.AddPeerPara // Detect our own endpoint if f, ok := i.Features["epdisc"]; ok { - epi := f.(*epdisc.Interface) + if epi, ok := f.(*epdisc.Interface); ok { + if ep, err := epi.Endpoint(); err != nil { + s.logger.Warn("Failed to determine our own endpoint address", zap.Error(err)) + } else if ep != nil { + resp.Invitation.Endpoint = ep.String() - if ep, err := epi.Endpoint(); err != nil { - s.logger.Warn("Failed to determine our own endpoint address", zap.Error(err)) - } else if ep != nil { - resp.Invitation.Endpoint = ep.String() + // Perform reverse lookup of our own endpoint + if names, err := net.LookupAddr(resp.Invitation.Endpoint); err == nil && len(names) >= 1 { + epName := names[0] - // Perform reverse lookup of our own endpoint - if names, err := net.LookupAddr(resp.Invitation.Endpoint); err == nil && len(names) >= 1 { - epName := names[0] - - // Do not use auto-generated IPv4 rDNS names - if match, _ := regexp.MatchString(`\d{1,3}-\d{1,3}-\d{1,3}-\d{1,3}`, epName); !match { - resp.Invitation.Endpoint = fmt.Sprintf("%s:%d", epName, ep.Port) + // Do not use auto-generated IPv4 rDNS names + if match, _ := regexp.MatchString(`\d{1,3}-\d{1,3}-\d{1,3}-\d{1,3}`, epName); !match { + resp.Invitation.Endpoint = fmt.Sprintf("%s:%d", epName, ep.Port) + } } } } @@ -337,11 +340,12 @@ func settingToString(value any) (string, error) { in := e.Interface() if tm, ok := in.(encoding.TextMarshaler); ok { - if b, err := tm.MarshalText(); err != nil { + b, err := tm.MarshalText() + if err != nil { return "", err - } else { - s = append(s, string(b)) } + + s = append(s, string(b)) } else { s = append(s, fmt.Sprint(in)) } diff --git a/pkg/rpc/server_epdisc.go b/pkg/rpc/server_epdisc.go index 44ba1bb3..e2fa5a21 100644 --- a/pkg/rpc/server_epdisc.go +++ b/pkg/rpc/server_epdisc.go @@ -2,20 +2,19 @@ package rpc import ( "context" + "errors" "io" - "go.uber.org/zap" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - "github.com/stv0g/cunicu/pkg/crypto" "github.com/stv0g/cunicu/pkg/daemon" "github.com/stv0g/cunicu/pkg/daemon/feature/epdisc" - "github.com/stv0g/cunicu/pkg/proto" - icex "github.com/stv0g/cunicu/pkg/ice" + "github.com/stv0g/cunicu/pkg/proto" epdiscproto "github.com/stv0g/cunicu/pkg/proto/feature/epdisc" rpcproto "github.com/stv0g/cunicu/pkg/proto/rpc" + "go.uber.org/zap" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) type EndpointDiscoveryServer struct { @@ -99,7 +98,7 @@ func (s *EndpointDiscoveryServer) SendConnectionStates(stream rpcproto.Daemon_St }, } - if err := stream.Send(e); err == io.EOF { + if err := stream.Send(e); errors.Is(err, io.EOF) { continue } else if err != nil { s.logger.Error("Failed to send connection states", zap.Error(err)) @@ -112,7 +111,7 @@ func (s *EndpointDiscoveryServer) SendConnectionStates(stream rpcproto.Daemon_St } } -func (s *EndpointDiscoveryServer) OnConnectionStateChange(p *epdisc.Peer, new, prev icex.ConnectionState) { +func (s *EndpointDiscoveryServer) OnConnectionStateChange(p *epdisc.Peer, newState, prevState icex.ConnectionState) { s.events.Send(&rpcproto.Event{ Type: rpcproto.EventType_PEER_CONNECTION_STATE_CHANGED, @@ -121,8 +120,8 @@ func (s *EndpointDiscoveryServer) OnConnectionStateChange(p *epdisc.Peer, new, p Event: &rpcproto.Event_PeerConnectionStateChange{ PeerConnectionStateChange: &rpcproto.PeerConnectionStateChangeEvent{ - NewState: epdiscproto.NewConnectionState(new), - PrevState: epdiscproto.NewConnectionState(prev), + NewState: epdiscproto.NewConnectionState(newState), + PrevState: epdiscproto.NewConnectionState(prevState), }, }, }) diff --git a/pkg/rpc/server_signaling.go b/pkg/rpc/server_signaling.go index 93e8b17c..bbee8886 100644 --- a/pkg/rpc/server_signaling.go +++ b/pkg/rpc/server_signaling.go @@ -3,15 +3,13 @@ package rpc import ( "context" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - - "github.com/stv0g/cunicu/pkg/signaling" - "github.com/stv0g/cunicu/pkg/signaling/grpc" - proto "github.com/stv0g/cunicu/pkg/proto" rpcproto "github.com/stv0g/cunicu/pkg/proto/rpc" signalingproto "github.com/stv0g/cunicu/pkg/proto/signaling" + "github.com/stv0g/cunicu/pkg/signaling" + "github.com/stv0g/cunicu/pkg/signaling/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) type SignalingServer struct { @@ -22,7 +20,10 @@ type SignalingServer struct { } func NewSignalingServer(s *Server, b *signaling.MultiBackend) *SignalingServer { - gb := b.ByType(signalingproto.BackendType_GRPC).(*grpc.Backend) + gb, ok := b.ByType(signalingproto.BackendType_GRPC).(*grpc.Backend) + if !ok { + return nil + } ss := &SignalingServer{ Server: s, diff --git a/pkg/selfupdate/download.go b/pkg/selfupdate/download.go index e8f566f8..3c22d88b 100644 --- a/pkg/selfupdate/download.go +++ b/pkg/selfupdate/download.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "crypto/sha256" + "errors" "fmt" "runtime" @@ -11,6 +12,11 @@ import ( "go.uber.org/zap" ) +var ( + errSignatureInvalid = errors.New("invalid GPG signature") + errChecksumMismatch = errors.New("checksum mismatch") +) + // DownloadAndVerifyRelease downloads a released version of // cunīcu and saves it to target. It returns the version string for the newest // version. The function printf is used to print progress information. @@ -30,9 +36,9 @@ func DownloadAndVerifyRelease(ctx context.Context, rel *Release, target string, logger.Info("Downloaded", zap.String("filename", fn)) if ok, err := GPGVerify(sha256sums, sig); err != nil { - return fmt.Errorf("GPG signature verification of %s failed: %w", checksumsSigFile, err) + return fmt.Errorf("GPG signature verification failed: %s: %w", checksumsSigFile, err) } else if !ok { - return fmt.Errorf("GPG signature verification of %s failed", checksumsSigFile) + return fmt.Errorf("%w: %s", errSignatureInvalid, checksumsSigFile) } logger.Info("GPG signature verification succeeded") @@ -57,7 +63,7 @@ func DownloadAndVerifyRelease(ctx context.Context, rel *Release, target string, gotHash := sha256.Sum256(buf) if !bytes.Equal(wantHash, gotHash[:]) { - return fmt.Errorf("checksum mismatch, want hash %02x, got %02x", wantHash, gotHash) + return fmt.Errorf("%w, want hash %02x, got %02x", errChecksumMismatch, wantHash, gotHash) } logger.Info("Checksum verification succeeded") diff --git a/pkg/selfupdate/github.go b/pkg/selfupdate/github.go index b4d83ba2..302ec622 100644 --- a/pkg/selfupdate/github.go +++ b/pkg/selfupdate/github.go @@ -5,19 +5,27 @@ package selfupdate import ( "context" "encoding/json" + "errors" "fmt" "io" "net/http" "strings" + "syscall" "time" ) +var ( + errUnexpectedResponse = errors.New("unexpected response") + errEmptyTag = errors.New("tag name for latest release is empty") + errInvalidTag = errors.New("invalid tag name") +) + // Release collects data about a single release on GitHub. type Release struct { Name string `json:"name"` TagName string `json:"tag_name"` Draft bool `json:"draft"` - PreRelease bool `json:"prerelease"` + PreRelease bool `json:"prerelease"` //nolint:tagliatelle PublishedAt time.Time `json:"published_at"` Assets []Asset `json:"assets"` @@ -71,12 +79,12 @@ func GitHubLatestRelease(ctx context.Context) (*Release, error) { // try to decode error message var msg githubError if err := json.NewDecoder(res.Body).Decode(&msg); err == nil { - return nil, fmt.Errorf("unexpected status %v (%v) returned, message: %v", res.StatusCode, res.Status, msg.Message) + return nil, fmt.Errorf("%w %v (%v) returned, message: %v", errUnexpectedResponse, res.StatusCode, res.Status, msg.Message) } } _ = res.Body.Close() - return nil, fmt.Errorf("unexpected status %v (%v) returned", res.StatusCode, res.Status) + return nil, fmt.Errorf("%w %v (%v) returned", errUnexpectedResponse, res.StatusCode, res.Status) } buf, err := io.ReadAll(res.Body) @@ -95,11 +103,11 @@ func GitHubLatestRelease(ctx context.Context) (*Release, error) { } if release.TagName == "" { - return nil, fmt.Errorf("tag name for latest release is empty") + return nil, errEmptyTag } if !strings.HasPrefix(release.TagName, "v") { - return nil, fmt.Errorf("tag name %q is invalid, does not start with 'v'", release.TagName) + return nil, fmt.Errorf("%w: %s, does not start with 'v'", errInvalidTag, release.TagName) } release.Version = release.TagName[1:] @@ -122,7 +130,7 @@ func getGithubData(ctx context.Context, url string) ([]byte, error) { } if res.StatusCode != http.StatusOK { - return nil, fmt.Errorf("unexpected status %v (%v) returned", res.StatusCode, res.Status) + return nil, fmt.Errorf("%w %v (%v) returned", errUnexpectedResponse, res.StatusCode, res.Status) } buf, err := io.ReadAll(res.Body) @@ -149,7 +157,7 @@ func getGithubDataFile(ctx context.Context, assets []Asset, suffix string) (file } if url == "" { - return "", nil, fmt.Errorf("unable to find file with suffix %v", suffix) + return "", nil, fmt.Errorf("%w: unable to find file with suffix %v", syscall.ENOENT, suffix) } data, err = getGithubData(ctx, url) diff --git a/pkg/selfupdate/selfupdate.go b/pkg/selfupdate/selfupdate.go index 2b3404c1..2658af89 100644 --- a/pkg/selfupdate/selfupdate.go +++ b/pkg/selfupdate/selfupdate.go @@ -18,6 +18,7 @@ import ( "os" "path/filepath" "strings" + "syscall" "github.com/stv0g/cunicu/pkg/util/buildinfo" "go.uber.org/zap" @@ -31,6 +32,13 @@ const ( checksumsSigFile = checksumsFile + ".asc" ) +var ( + errOutputIsNotDir = errors.New("output parent path is not a directory") + errOutputIsNotNormalFile = errors.New("output path is not a normal file") + errHashNotFound = errors.New("hash not found") + errArchiveMultipleFiles = errors.New("archive contains more than one file") +) + func SelfUpdate(output string, logger *zap.Logger) (*Release, error) { fi, err := os.Lstat(output) if err != nil { @@ -40,12 +48,10 @@ func SelfUpdate(output string, logger *zap.Logger) (*Release, error) { return nil, fmt.Errorf("failed to stat: %w", err) } if !di.Mode().IsDir() { - return nil, errors.New("output parent path is not a directory") - } - } else { - if !fi.Mode().IsRegular() { - return nil, errors.New("output path is not a normal file") + return nil, errOutputIsNotDir } + } else if !fi.Mode().IsRegular() { + return nil, errOutputIsNotNormalFile } curVersion := strings.TrimPrefix(buildinfo.Version, "v") @@ -60,13 +66,14 @@ func SelfUpdate(output string, logger *zap.Logger) (*Release, error) { logger.Info("Latest version", zap.String("version", rel.Version)) // We do a lexicographic comparison here to compare the semver versions. - if rel.Version == curVersion { + switch { + case rel.Version == curVersion: logger.Info("Your cunicu version is up to date. Nothing to update.") return rel, nil - } else if rel.Version < curVersion { + case rel.Version < curVersion: logger.Warn("You are running an unreleased version of cunicu. Nothing to update.") return rel, nil - } else { + default: logger.Info("Your cunicu version is outdated. Updating now!") } @@ -99,11 +106,11 @@ func findHash(buf []byte, filename string) (hash []byte, err error) { } } - return nil, fmt.Errorf("hash for file %v not found", filename) + return nil, fmt.Errorf("%w for: %s", errHashNotFound, filename) } func extractToFile(buf []byte, filename, target string) (int64, error) { - var mode = os.FileMode(0755) + mode := os.FileMode(0o755) // get information about the target file fi, err := os.Lstat(target) @@ -131,7 +138,7 @@ func extractToFile(buf []byte, filename, target string) (int64, error) { rd = nil for { if hdr, err := trd.Next(); err != nil { - if err == io.EOF { + if errors.Is(err, io.EOF) { break } @@ -142,7 +149,7 @@ func extractToFile(buf []byte, filename, target string) (int64, error) { } } if rd == nil { - return -1, fmt.Errorf("no such file '%s'", binaryFile) + return -1, fmt.Errorf("%w: %s", syscall.ENOENT, binaryFile) } case ".zip": zrd, err := zip.NewReader(bytes.NewReader(buf), int64(len(buf))) @@ -151,7 +158,7 @@ func extractToFile(buf []byte, filename, target string) (int64, error) { } if len(zrd.File) != 1 { - return -1, fmt.Errorf("ZIP archive contains more than one file") + return -1, errArchiveMultipleFiles } file, err := zrd.File[0].Open() @@ -168,7 +175,7 @@ func extractToFile(buf []byte, filename, target string) (int64, error) { // Delete old file if err := os.Remove(target); err != nil && !os.IsNotExist(err) { - return -1, fmt.Errorf("failed to remove target file: %v", err) + return -1, fmt.Errorf("failed to remove target file: %w", err) } //#nosec G304 -- No file inclusion possible as we are writing only. diff --git a/pkg/selfupdate/verify.go b/pkg/selfupdate/verify.go index e30035ac..a406409f 100644 --- a/pkg/selfupdate/verify.go +++ b/pkg/selfupdate/verify.go @@ -5,20 +5,21 @@ package selfupdate import ( "bytes" "embed" + "errors" "fmt" "os/exec" "path" - //lint:ignore SA1019 We still need to find an alternative - pgp "golang.org/x/crypto/openpgp" - "google.golang.org/protobuf/encoding/protojson" - "github.com/stv0g/cunicu/pkg/proto" + pgp "golang.org/x/crypto/openpgp" //nolint:staticcheck + "google.golang.org/protobuf/encoding/protojson" ) //go:embed keys/*.gpg var keys embed.FS +var errVersionMismatch = errors.New("version mismatch") + func loadKeyRing() (pgp.EntityList, error) { el := pgp.EntityList{} @@ -82,7 +83,7 @@ func VersionVerify(binaryFile, expectedVersion string) error { } if "v"+expectedVersion != bi.Client.Version { - return fmt.Errorf("version mismatch: dowloaded %s != expected v%s", bi.Client.Version, expectedVersion) + return fmt.Errorf("%w: dowloaded %s != expected v%s", errVersionMismatch, bi.Client.Version, expectedVersion) } return nil diff --git a/pkg/signaling/backend.go b/pkg/signaling/backend.go index ca9c907d..42605216 100644 --- a/pkg/signaling/backend.go +++ b/pkg/signaling/backend.go @@ -2,20 +2,21 @@ package signaling import ( "context" + "errors" "fmt" "io" "net/url" "strings" - "go.uber.org/zap" - "github.com/stv0g/cunicu/pkg/crypto" - signalingproto "github.com/stv0g/cunicu/pkg/proto/signaling" + "go.uber.org/zap" ) var ( - Backends = map[BackendType]*BackendPlugin{} + Backends = map[BackendType]*BackendPlugin{} //nolint:gochecknoglobals + + errInvalidBackend = errors.New("unknown backend type") ) type BackendType string // URL schemes @@ -59,7 +60,7 @@ func NewBackend(cfg *BackendConfig) (Backend, error) { p, ok := Backends[typ] if !ok { - return nil, fmt.Errorf("unknown backend type: %s", typ) + return nil, fmt.Errorf("%w: %s", errInvalidBackend, typ) } if len(typs) > 1 { diff --git a/pkg/signaling/backend_test.go b/pkg/signaling/backend_test.go index 29995276..f6ec1712 100644 --- a/pkg/signaling/backend_test.go +++ b/pkg/signaling/backend_test.go @@ -3,11 +3,10 @@ package signaling_test import ( "net/url" - "github.com/stv0g/cunicu/pkg/signaling" - "github.com/stv0g/cunicu/pkg/signaling/inprocess" - . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/stv0g/cunicu/pkg/signaling" + "github.com/stv0g/cunicu/pkg/signaling/inprocess" ) type readyHandler struct { diff --git a/pkg/signaling/grpc/backend.go b/pkg/signaling/grpc/backend.go index f3f1ebff..0b68a836 100644 --- a/pkg/signaling/grpc/backend.go +++ b/pkg/signaling/grpc/backend.go @@ -4,16 +4,14 @@ import ( "context" "fmt" + "github.com/stv0g/cunicu/pkg/crypto" + signalingproto "github.com/stv0g/cunicu/pkg/proto/signaling" + "github.com/stv0g/cunicu/pkg/signaling" "go.uber.org/zap" "google.golang.org/grpc" - - "github.com/stv0g/cunicu/pkg/crypto" - "github.com/stv0g/cunicu/pkg/signaling" - - signalingproto "github.com/stv0g/cunicu/pkg/proto/signaling" ) -func init() { +func init() { //nolint:gochecknoinits signaling.Backends["grpc"] = &signaling.BackendPlugin{ New: NewBackend, Description: "gRPC", @@ -113,14 +111,14 @@ func (b *Backend) subscribeFromServer(ctx context.Context, pk *crypto.Key) error stream, err := b.client.Subscribe(ctx, params, grpc.WaitForReady(true)) if err != nil { - return fmt.Errorf("failed to subscribe to offers: %s", err) + return fmt.Errorf("failed to subscribe to offers: %w", err) } // Wait until subscription has been created // This avoids a race between Subscribe() / Publish() when two subscribers are subscribing // to each other. if _, err := stream.Recv(); err != nil { - return fmt.Errorf("failed receive synchronization envelope: %s", err) + return fmt.Errorf("failed receive synchronization envelope: %w", err) } b.logger.Debug("Created new subscription", zap.Any("pk", pk)) diff --git a/pkg/signaling/grpc/backend_test.go b/pkg/signaling/grpc/backend_test.go index 2256ce8f..19353682 100644 --- a/pkg/signaling/grpc/backend_test.go +++ b/pkg/signaling/grpc/backend_test.go @@ -32,7 +32,7 @@ var _ = Describe("gRPC backend", func() { // Start local dummy gRPC server svr = grpc.NewSignalingServer() - go svr.Serve(l) + go svr.Serve(l) //nolint:errcheck u = url.URL{ Scheme: "grpc", diff --git a/pkg/signaling/grpc/config.go b/pkg/signaling/grpc/config.go index 96737b89..69a1bb02 100644 --- a/pkg/signaling/grpc/config.go +++ b/pkg/signaling/grpc/config.go @@ -3,9 +3,8 @@ package grpc import ( "fmt" - "google.golang.org/grpc" - "github.com/stv0g/cunicu/pkg/signaling" + "google.golang.org/grpc" ) type BackendConfig struct { diff --git a/pkg/signaling/grpc/grpc.go b/pkg/signaling/grpc/grpc.go index 6c7848cd..52b8c8fe 100644 --- a/pkg/signaling/grpc/grpc.go +++ b/pkg/signaling/grpc/grpc.go @@ -17,6 +17,8 @@ import ( "google.golang.org/grpc/keepalive" ) +var errInvalidServerHostname = errors.New("missing gRPC server url") + func ParseURL(urlStr string) (string, []grpc.DialOption, error) { opts := []grpc.DialOption{} @@ -49,7 +51,8 @@ func ParseURL(urlStr string) (string, []grpc.DialOption, error) { } else { // Use system certificate store cfg := &tls.Config{ - //#nosec G402 -- Users should have the freedom to disable verification for self-signed certificates + // Users should have the freedom to disable verification for self-signed certificates + //nolint:gosec InsecureSkipVerify: skipVerify, } @@ -57,7 +60,7 @@ func ParseURL(urlStr string) (string, []grpc.DialOption, error) { var err error //#nosec G304 -- Filename is only controlled by env var - if cfg.KeyLogWriter, err = os.OpenFile(fn, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600); err != nil { + if cfg.KeyLogWriter, err = os.OpenFile(fn, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o600); err != nil { return "", nil, fmt.Errorf("failed to open SSL keylog file: %w", err) } } @@ -74,7 +77,7 @@ func ParseURL(urlStr string) (string, []grpc.DialOption, error) { ) if u.Host == "" { - return "", nil, errors.New("missing gRPC server url") + return "", nil, errInvalidServerHostname } return u.Host, opts, nil diff --git a/pkg/signaling/grpc/server.go b/pkg/signaling/grpc/server.go index 408d42d2..28c81d09 100644 --- a/pkg/signaling/grpc/server.go +++ b/pkg/signaling/grpc/server.go @@ -3,23 +3,22 @@ package grpc import ( "context" "crypto/tls" + "errors" "fmt" "io" "os" "time" + "github.com/stv0g/cunicu/pkg/crypto" + "github.com/stv0g/cunicu/pkg/proto" + signalingproto "github.com/stv0g/cunicu/pkg/proto/signaling" + "github.com/stv0g/cunicu/pkg/signaling" + "github.com/stv0g/cunicu/pkg/util/buildinfo" "go.uber.org/zap" "golang.org/x/exp/slices" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/keepalive" - - "github.com/stv0g/cunicu/pkg/crypto" - "github.com/stv0g/cunicu/pkg/proto" - "github.com/stv0g/cunicu/pkg/signaling" - "github.com/stv0g/cunicu/pkg/util/buildinfo" - - signalingproto "github.com/stv0g/cunicu/pkg/proto/signaling" ) type Server struct { @@ -51,7 +50,7 @@ func NewSignalingServer(opts ...grpc.ServerOption) *Server { func NewServer(opts ...grpc.ServerOption) (*grpc.Server, error) { if fn := os.Getenv("SSLKEYLOGFILE"); fn != "" { //#nosec G304 -- Filename is only controlled via env var - wr, err := os.OpenFile(fn, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) + wr, err := os.OpenFile(fn, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o600) if err != nil { return nil, fmt.Errorf("failed to open SSL keylog file: %w", err) } @@ -99,7 +98,7 @@ out: break out } - if err := stream.Send(env); err == io.EOF { + if err := stream.Send(env); errors.Is(err, io.EOF) { break out } else if err != nil { s.logger.Error("Failed to send envelope", zap.Error(err)) diff --git a/pkg/signaling/grpc/server_relay.go b/pkg/signaling/grpc/server_relay.go index 119e1586..6d71d19c 100644 --- a/pkg/signaling/grpc/server_relay.go +++ b/pkg/signaling/grpc/server_relay.go @@ -3,9 +3,7 @@ package grpc import ( "context" "crypto/hmac" - - //#nosec G501 -- SHA1 is required by TURN REST API - "crypto/sha1" + "crypto/sha1" //nolint:gosec "encoding/base64" "fmt" "time" @@ -13,12 +11,11 @@ import ( "github.com/stv0g/cunicu/pkg/crypto" icex "github.com/stv0g/cunicu/pkg/ice" "github.com/stv0g/cunicu/pkg/proto" + signalingproto "github.com/stv0g/cunicu/pkg/proto/signaling" "go.uber.org/zap" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - - signalingproto "github.com/stv0g/cunicu/pkg/proto/signaling" ) const ( diff --git a/pkg/signaling/inprocess/backend.go b/pkg/signaling/inprocess/backend.go index 14ee8cbd..b4f810c7 100644 --- a/pkg/signaling/inprocess/backend.go +++ b/pkg/signaling/inprocess/backend.go @@ -3,19 +3,16 @@ package inprocess import ( "context" - "go.uber.org/zap" - "github.com/stv0g/cunicu/pkg/crypto" - "github.com/stv0g/cunicu/pkg/signaling" - signalingproto "github.com/stv0g/cunicu/pkg/proto/signaling" + "github.com/stv0g/cunicu/pkg/signaling" + "go.uber.org/zap" ) -var ( - subs = signaling.NewSubscriptionsRegistry() -) +//nolint:gochecknoglobals +var subs = signaling.NewSubscriptionsRegistry() -func init() { +func init() { //nolint:gochecknoinits signaling.Backends["inprocess"] = &signaling.BackendPlugin{ New: NewBackend, Description: "In-Process", diff --git a/pkg/signaling/k8s/backend.go b/pkg/signaling/k8s/backend.go index 86f62f96..57d38100 100644 --- a/pkg/signaling/k8s/backend.go +++ b/pkg/signaling/k8s/backend.go @@ -5,20 +5,17 @@ import ( "fmt" "time" - "go.uber.org/zap" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/cache" - "k8s.io/client-go/tools/clientcmd" - "github.com/stv0g/cunicu/pkg/crypto" - "github.com/stv0g/cunicu/pkg/signaling" - signalingproto "github.com/stv0g/cunicu/pkg/proto/signaling" - + "github.com/stv0g/cunicu/pkg/signaling" v1 "github.com/stv0g/cunicu/pkg/signaling/k8s/apis/cunicu/v1" cunicuv1 "github.com/stv0g/cunicu/pkg/signaling/k8s/client/clientset/versioned" informers "github.com/stv0g/cunicu/pkg/signaling/k8s/client/informers/externalversions" + "go.uber.org/zap" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/tools/clientcmd" ) const ( @@ -39,7 +36,7 @@ type Backend struct { logger *zap.Logger } -func init() { +func init() { //nolint:gochecknoinits signaling.Backends["k8s"] = &signaling.BackendPlugin{ New: NewBackend, Description: "Exchange candidates via annotation in Kubernetes Node resource", @@ -50,6 +47,11 @@ func NewBackend(cfg *signaling.BackendConfig, logger *zap.Logger) (signaling.Bac var config *rest.Config var err error + defaultConfig := BackendConfig{ + GenerateName: "cunicu-", + Namespace: "cunicu", + } + b := &Backend{ SubscriptionsRegistry: signaling.NewSubscriptionsRegistry(), stop: make(chan struct{}), @@ -61,23 +63,20 @@ func NewBackend(cfg *signaling.BackendConfig, logger *zap.Logger) (signaling.Bac return nil, fmt.Errorf("failed to parse configuration: %w", err) } - if b.config.Kubeconfig == "" { + switch b.config.Kubeconfig { + case "": loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() - // if you want to change the loading rules (which files in which order), you can do so here - configOverrides := &clientcmd.ConfigOverrides{} - // if you want to change override values or bind them to flags, there are methods to help you - kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides) if config, err = kubeConfig.ClientConfig(); err != nil { return nil, fmt.Errorf("failed to load config: %w", err) } - } else if b.config.Kubeconfig == "incluster" { + case "incluster": if config, err = rest.InClusterConfig(); err != nil { return nil, fmt.Errorf("failed to get incluster configuration: %w", err) } - } else { + default: if config, err = clientcmd.BuildConfigFromFlags("", b.config.Kubeconfig); err != nil { return nil, fmt.Errorf("failed to get configuration from flags: %w", err) } @@ -171,7 +170,10 @@ func (b *Backend) Close() error { } func (b *Backend) onEnvelopeAdded(obj any) { - env := obj.(*v1.SignalingEnvelope) + env, ok := obj.(*v1.SignalingEnvelope) + if !ok { + panic("not an envelope") + } b.logger.Debug("New envelope found on API server", zap.String("name", env.ObjectMeta.Name)) if err := b.process(env); err != nil { @@ -179,8 +181,11 @@ func (b *Backend) onEnvelopeAdded(obj any) { } } -func (b *Backend) onEnvelopeUpdated(old any, new any) { - newEnv := new.(*v1.SignalingEnvelope) +func (b *Backend) onEnvelopeUpdated(oldEnv, newEnve any) { + newEnv, ok := newEnve.(*v1.SignalingEnvelope) + if !ok { + panic("not an envelope") + } b.logger.Debug("Envelope updated", zap.String("name", newEnv.ObjectMeta.Name)) if err := b.process(newEnv); err != nil { @@ -196,7 +201,8 @@ func (b *Backend) process(env *v1.SignalingEnvelope) error { sub, err := b.GetSubscription(&pkp.Ours) if err != nil { - return nil // ignore envelopes not addressed to us + // ignore envelopes not addressed to us + return nil //nolint:nilerr } if err := sub.NewMessage(&env.Envelope); err != nil { diff --git a/pkg/signaling/k8s/backend_test.go b/pkg/signaling/k8s/backend_test.go index 0c889d2d..870ae879 100644 --- a/pkg/signaling/k8s/backend_test.go +++ b/pkg/signaling/k8s/backend_test.go @@ -25,8 +25,10 @@ func TestSuite(t *testing.T) { var logger = test.SetupLogging() -var testenv *envtest.Environment -var kcfg *os.File +var ( + testenv *envtest.Environment + kcfg *os.File +) var _ = BeforeSuite(func() { log.SetLogger(zapr.NewLogger(logger.Named("k8s"))) diff --git a/pkg/signaling/k8s/config.go b/pkg/signaling/k8s/config.go index 18fa83fc..d2654ccd 100644 --- a/pkg/signaling/k8s/config.go +++ b/pkg/signaling/k8s/config.go @@ -12,11 +12,6 @@ type BackendConfig struct { GenerateName string } -var defaultConfig = BackendConfig{ - GenerateName: "cunicu-", - Namespace: "cunicu", -} - func (c *BackendConfig) Parse(cfg *signaling.BackendConfig) error { c.BackendConfig = *cfg diff --git a/pkg/signaling/multi.go b/pkg/signaling/multi.go index 08dc8254..680f7574 100644 --- a/pkg/signaling/multi.go +++ b/pkg/signaling/multi.go @@ -5,7 +5,6 @@ import ( "net/url" "github.com/stv0g/cunicu/pkg/crypto" - signalingproto "github.com/stv0g/cunicu/pkg/proto/signaling" ) diff --git a/pkg/signaling/signaling.go b/pkg/signaling/signaling.go index 58b73e74..f98a0ede 100644 --- a/pkg/signaling/signaling.go +++ b/pkg/signaling/signaling.go @@ -3,12 +3,13 @@ package signaling import ( "github.com/stv0g/cunicu/pkg/crypto" - signalingproto "github.com/stv0g/cunicu/pkg/proto/signaling" ) -type Message = signalingproto.Message -type Envelope = signalingproto.Envelope +type ( + Message = signalingproto.Message + Envelope = signalingproto.Envelope +) type MessageHandler interface { OnSignalingMessage(*crypto.PublicKeyPair, *Message) diff --git a/pkg/signaling/subscriptions.go b/pkg/signaling/subscriptions.go index af1cba98..d0aaffe1 100644 --- a/pkg/signaling/subscriptions.go +++ b/pkg/signaling/subscriptions.go @@ -11,9 +11,8 @@ import ( ) var ( - ErrNotSubscribed = errors.New("missing subscription") - - AnyKey crypto.Key + ErrNotSubscribed = errors.New("missing subscription") + errAlreadyExisting = errors.New("already existing") ) type Subscription struct { @@ -54,7 +53,7 @@ func (s *SubscriptionsRegistry) NewSubscription(k *crypto.Key) (*Subscription, e defer s.mu.Unlock() if _, ok := s.subs[k.PublicKey()]; ok { - return nil, errors.New("already existing") + return nil, errAlreadyExisting } sub := &Subscription{ @@ -137,7 +136,7 @@ func (s *Subscription) NewMessage(env *Envelope) error { s.mu.RLock() defer s.mu.RUnlock() - if cbs, ok := s.onMessages[AnyKey]; ok { + if cbs, ok := s.onMessages[crypto.Key{}]; ok { for _, cb := range cbs { cb.OnSignalingMessage(&pkp, msg) } diff --git a/pkg/util/atomic_enum.go b/pkg/util/atomic_enum.go index 90e9fffa..1241003f 100644 --- a/pkg/util/atomic_enum.go +++ b/pkg/util/atomic_enum.go @@ -12,10 +12,10 @@ func (a *AtomicEnum[T]) Store(v T) { (*atomic.Uint64)(a).Store(uint64(v)) } -func (a *AtomicEnum[T]) CompareAndSwap(old, new T) bool { - return (*atomic.Uint64)(a).CompareAndSwap(uint64(old), uint64(new)) +func (a *AtomicEnum[T]) CompareAndSwap(oldVal, newVal T) bool { + return (*atomic.Uint64)(a).CompareAndSwap(uint64(oldVal), uint64(newVal)) } -func (a *AtomicEnum[T]) Swap(new T) T { - return T((*atomic.Uint64)(a).Swap(uint64(new))) +func (a *AtomicEnum[T]) Swap(newVal T) T { + return T((*atomic.Uint64)(a).Swap(uint64(newVal))) } diff --git a/pkg/util/buildinfo/buildinfo.go b/pkg/util/buildinfo/buildinfo.go index 3277a3f9..e4c3ebd9 100644 --- a/pkg/util/buildinfo/buildinfo.go +++ b/pkg/util/buildinfo/buildinfo.go @@ -11,6 +11,7 @@ import ( "github.com/stv0g/cunicu/pkg/proto" ) +//nolint:gochecknoglobals var ( // set via ldflags -X / goreleaser or from debug.ReadBuildInfo() Version = "dev" @@ -23,7 +24,7 @@ var ( Dirty bool ) -func init() { +func init() { //nolint:gochecknoinits if Version == "dev" { _, Commit, Dirty, Date = ReadVCSInfos() } else { diff --git a/pkg/util/human.go b/pkg/util/human.go index 3a95b1cf..50efef9a 100644 --- a/pkg/util/human.go +++ b/pkg/util/human.go @@ -32,7 +32,7 @@ func PrettyDuration(left time.Duration) string { num := left / comp.divisor if num > 0 { - left -= num * comp.divisor + left -= num * comp.divisor //nolint:durationcheck unit := comp.name if num > 1 { @@ -40,7 +40,6 @@ func PrettyDuration(left time.Duration) string { } out = append(out, fmt.Sprintf("%d "+t.Mods("%s", t.FgCyan), num, unit)) - } } @@ -72,7 +71,7 @@ func PrettyBytes(b int64) string { return fmt.Sprintf("%d "+t.Mods("B", t.FgCyan), b) } - var suffices = []rune{'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'} + suffices := []rune{'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'} var f float32 var i int diff --git a/pkg/util/listen.go b/pkg/util/listen.go index 6957b640..131e79ab 100644 --- a/pkg/util/listen.go +++ b/pkg/util/listen.go @@ -1,18 +1,25 @@ package util import ( + "errors" "fmt" "math/rand" "net" "strings" ) +var ( + errInvalidPortRange = errors.New("minimal port must be larger than maximal port number") + errInvalidNetwork = errors.New("unsupported network") + errNoPortFound = errors.New("failed to find port") +) + func FindRandomPortToListen(network string, min, max int) (int, error) { if max < min { - return -1, fmt.Errorf("minimal port must be larger than maximal port number") + return -1, errInvalidPortRange } if !strings.HasPrefix(network, "udp") { - return -1, fmt.Errorf("unsupported network: %s", network) + return -1, fmt.Errorf("%w: %s", errInvalidNetwork, network) } for attempts := 100; attempts > 0; attempts-- { @@ -23,15 +30,15 @@ func FindRandomPortToListen(network string, min, max int) (int, error) { } } - return -1, fmt.Errorf("failed to find port") + return -1, errNoPortFound } func FindNextPortToListen(network string, start, end int) (int, error) { if end < start { - return -1, fmt.Errorf("minimal port must be larger than maximal port number") + return -1, errInvalidPortRange } if !strings.HasPrefix(network, "udp") { - return -1, fmt.Errorf("unsupported network: %s", network) + return -1, fmt.Errorf("%w: %s", errInvalidNetwork, network) } for port := start; port <= end; port++ { @@ -40,7 +47,7 @@ func FindNextPortToListen(network string, start, end int) (int, error) { } } - return -1, fmt.Errorf("failed to find port") + return -1, errNoPortFound } func canListenOnPort(network string, port int) bool { diff --git a/pkg/util/listen_test.go b/pkg/util/listen_test.go index 7ae7b3ee..4f9aaba2 100644 --- a/pkg/util/listen_test.go +++ b/pkg/util/listen_test.go @@ -30,7 +30,7 @@ var _ = Describe("find bindable port in range", func() { Context("with used port", func() { var conn net.Conn - var port = 10024 + port := 10024 BeforeEach(func() { var err error @@ -72,7 +72,7 @@ var _ = Describe("find bindable port in range", func() { Context("finds multiple ports without collisions", func() { var conns []net.Conn - var cnt = 10 + cnt := 10 BeforeEach(func() { conns = []net.Conn{} @@ -121,7 +121,7 @@ var _ = Describe("find bindable port in range", func() { Context("with used port", func() { var conn net.Conn - var port = 10011 + port := 10011 BeforeEach(func() { var err error @@ -143,5 +143,4 @@ var _ = Describe("find bindable port in range", func() { }) }) }) - }) diff --git a/pkg/util/slice.go b/pkg/util/slice.go index 48621178..3e9af971 100644 --- a/pkg/util/slice.go +++ b/pkg/util/slice.go @@ -8,19 +8,20 @@ import ( "golang.org/x/exp/slices" ) -func SliceDiff[T constraints.Ordered](old, new []T) (added, removed, kept []T) { - return SliceDiffFunc(old, new, func(a, b T) int { - if a == b { +func SliceDiff[T constraints.Ordered](oldSlice, newSlice []T) (added, removed, kept []T) { + return SliceDiffFunc(oldSlice, newSlice, func(a, b T) int { + switch { + case a == b: return 0 - } else if a < b { + case a < b: return -1 - } else { + default: return 1 } }) } -func SliceDiffFunc[T any](old, new []T, cmp func(a, b T) int) (added, removed, kept []T) { +func SliceDiffFunc[T any](oldSlice, newSlice []T, cmp func(a, b T) int) (added, removed, kept []T) { added = []T{} removed = []T{} kept = []T{} @@ -29,23 +30,23 @@ func SliceDiffFunc[T any](old, new []T, cmp func(a, b T) int) (added, removed, k return cmp(a, b) < 0 } - slices.SortFunc(new, less) - slices.SortFunc(old, less) + slices.SortFunc(newSlice, less) + slices.SortFunc(oldSlice, less) i, j := 0, 0 - for i < len(old) && j < len(new) { - c := cmp(old[i], new[j]) + for i < len(oldSlice) && j < len(newSlice) { + c := cmp(oldSlice[i], newSlice[j]) switch { case c < 0: // removed - removed = append(removed, old[i]) + removed = append(removed, oldSlice[i]) i++ case c > 0: // added - added = append(added, new[j]) + added = append(added, newSlice[j]) j++ default: // kept - kept = append(kept, new[j]) + kept = append(kept, newSlice[j]) i++ j++ } @@ -53,15 +54,15 @@ func SliceDiffFunc[T any](old, new []T, cmp func(a, b T) int) (added, removed, k // Add rest - for ; i < len(old); i++ { - removed = append(removed, old[i]) + for ; i < len(oldSlice); i++ { + removed = append(removed, oldSlice[i]) } - for ; j < len(new); j++ { - added = append(added, new[j]) + for ; j < len(newSlice); j++ { + added = append(added, newSlice[j]) } - return + return added, removed, kept } func SliceShuffle[T any](s []T) { diff --git a/pkg/util/slice_test.go b/pkg/util/slice_test.go index d2ec828d..58c27754 100644 --- a/pkg/util/slice_test.go +++ b/pkg/util/slice_test.go @@ -55,7 +55,7 @@ var _ = Context("slice", func() { Context("diff", func() { var a, b, c, d, bc, cd []int - var f func(old, new []int) (added, removed, kept []int) + var f func(oldSlice, newSlice []int) (added, removed, kept []int) BeforeEach(func() { a = []int{} @@ -125,8 +125,8 @@ var _ = Context("slice", func() { Describe("func", func() { BeforeEach(func() { - f = func(old, new []int) (added, removed, kept []int) { - return util.SliceDiffFunc(old, new, func(a, b int) int { + f = func(oldSlice, newSlice []int) (added, removed, kept []int) { + return util.SliceDiffFunc(oldSlice, newSlice, func(a, b int) int { return a - b }) } diff --git a/pkg/util/sysctl_linux.go b/pkg/util/sysctl_linux.go index d1d53d4b..d74b3f09 100644 --- a/pkg/util/sysctl_linux.go +++ b/pkg/util/sysctl_linux.go @@ -12,7 +12,7 @@ func SetSysctl(name string, value any) error { path := filepath.Join("/proc/sys", parts) //#nosec G304 -- Filename is always under /proc/sys - f, err := os.OpenFile(path, os.O_WRONLY, 0600) + f, err := os.OpenFile(path, os.O_WRONLY, 0o600) if err != nil { return err } diff --git a/pkg/util/terminal/terminal.go b/pkg/util/terminal/terminal.go index 3850e1a4..b6d09847 100644 --- a/pkg/util/terminal/terminal.go +++ b/pkg/util/terminal/terminal.go @@ -44,13 +44,14 @@ func Mods(str string, mods ...string) string { } func FprintKV(wr io.Writer, k string, v ...any) (int, error) { - if len(v) == 0 { + switch { + case len(v) == 0: return fmt.Fprintf(wr, Mods("%s", Bold)+":\n", k) - } else if len(v) == 1 { + case len(v) == 1: return fmt.Fprintf(wr, Mods("%s", Bold)+": %v\n", k, v[0]) - } else if len(v) > 1 { + case len(v) > 1: return fmt.Fprintf(wr, Mods("%s", Bold)+": %v\n", k, v) - } else { + default: return 0, nil } } diff --git a/pkg/util/terminal/terminal_test.go b/pkg/util/terminal/terminal_test.go index d69abae7..50043f58 100644 --- a/pkg/util/terminal/terminal_test.go +++ b/pkg/util/terminal/terminal_test.go @@ -25,7 +25,7 @@ var _ = Context("tty", func() { It("is false", func() { fn := filepath.Join(GinkgoT().TempDir(), "file") - f, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY, 0600) + f, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY, 0o600) Expect(err).To(Succeed()) Expect(terminal.IsATTY(f)).To(BeFalse()) diff --git a/pkg/util/util_test.go b/pkg/util/util_test.go index 91c40f25..63730479 100644 --- a/pkg/util/util_test.go +++ b/pkg/util/util_test.go @@ -98,13 +98,13 @@ var _ = Context("contains net", func() { }) var _ = It("rand", func() { - a := rand.Float32() + a := rand.Float32() //nolint:gosec util.SetupRand() - b := rand.Float32() + b := rand.Float32() //nolint:gosec util.SetupRand() - c := rand.Float32() + c := rand.Float32() //nolint:gosec Expect(a).NotTo(Equal(b)) Expect(a).NotTo(Equal(c)) @@ -120,7 +120,6 @@ var _ = Context("offset ip", func() { Expect(ip2).NotTo(BeNil()) Expect(util.OffsetIP(ip1, 10)).To(Equal(ip2)) - }) It("ipv6", func() { diff --git a/pkg/watcher/watcher.go b/pkg/watcher/watcher.go index 455ada23..d30d2c2a 100644 --- a/pkg/watcher/watcher.go +++ b/pkg/watcher/watcher.go @@ -10,13 +10,12 @@ import ( "github.com/stv0g/cunicu/pkg/core" "github.com/stv0g/cunicu/pkg/crypto" + xerrors "github.com/stv0g/cunicu/pkg/errors" "github.com/stv0g/cunicu/pkg/log" "github.com/stv0g/cunicu/pkg/util" "go.uber.org/zap" "golang.zx2c4.com/wireguard/wgctrl" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" - - xerrors "github.com/stv0g/cunicu/pkg/errors" ) const ( @@ -26,11 +25,13 @@ const ( type InterfaceFilterFunc func(string) bool -type InterfaceEventOp int -type InterfaceEvent struct { - Op InterfaceEventOp - Name string -} +type ( + InterfaceEventOp int + InterfaceEvent struct { + Op InterfaceEventOp + Name string + } +) func (ls InterfaceEventOp) String() string { switch ls { @@ -159,22 +160,22 @@ func (w *Watcher) Sync() error { func (w *Watcher) sync() error { var err error - var new = []*wgtypes.Device{} - var old = w.devices + var newDevs []*wgtypes.Device + oldDevs := w.devices w.mu.Lock() - if new, err = w.client.Devices(); err != nil { + if newDevs, err = w.client.Devices(); err != nil { w.mu.Unlock() return fmt.Errorf("failed to list WireGuard interfaces: %w", err) } // Ignore devices which do not match the filter - new = util.SliceFilter(new, func(d *wgtypes.Device) bool { + newDevs = util.SliceFilter(newDevs, func(d *wgtypes.Device) bool { return w.filter == nil || w.filter(d.Name) }) - added, removed, kept := util.SliceDiffFunc(old, new, func(a, b *wgtypes.Device) int { + added, removed, kept := util.SliceDiffFunc(oldDevs, newDevs, func(a, b *wgtypes.Device) int { return strings.Compare(a.Name, b.Name) }) @@ -229,7 +230,7 @@ func (w *Watcher) sync() error { i.Sync(wgd) } - w.devices = new + w.devices = newDevs return nil } @@ -298,7 +299,7 @@ func (w *Watcher) ForEachPeer(cb func(p *core.Peer) error) error { return w.ForEachInterface(func(i *core.Interface) error { for _, p := range i.Peers { if err := cb(p); err != nil { - return nil + return err } } diff --git a/pkg/watcher/watcher_linux_test.go b/pkg/watcher/watcher_linux_test.go index b911bf27..c6c506ed 100644 --- a/pkg/watcher/watcher_linux_test.go +++ b/pkg/watcher/watcher_linux_test.go @@ -30,7 +30,7 @@ var _ = Describe("watcher", func() { JustBeforeEach(OncePerOrdered, func() { By("Creating net namespace") - nsName := fmt.Sprintf("wg-test-ns-%d", rand.Intn(1000)) + nsName := fmt.Sprintf("wg-test-ns-%d", rand.Intn(1000)) //nolint:gosec ns, err = g.NewNamespace(nsName) Expect(err).To(Succeed()) @@ -218,7 +218,7 @@ var _ = Describe("watcher", func() { Context("user", Ordered, func() { BeforeAll(func() { - devName = fmt.Sprintf("wg-user-%d", rand.Intn(1000)) + devName = fmt.Sprintf("wg-user-%d", rand.Intn(1000)) //nolint:gosec devUser = true }) @@ -228,15 +228,12 @@ var _ = Describe("watcher", func() { Context("watch", Pending, func() { Context("periodic", func() { - }) Context("user", func() { - }) Context("kernel", func() { - }) }) }) diff --git a/pkg/watcher/watcher_test.go b/pkg/watcher/watcher_test.go index 8061a311..8e1d54d4 100644 --- a/pkg/watcher/watcher_test.go +++ b/pkg/watcher/watcher_test.go @@ -6,7 +6,6 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/stv0g/cunicu/pkg/util" "github.com/stv0g/cunicu/test" ) diff --git a/pkg/watcher/watcher_user.go b/pkg/watcher/watcher_user.go index 090ed9a1..029547f6 100644 --- a/pkg/watcher/watcher_user.go +++ b/pkg/watcher/watcher_user.go @@ -32,24 +32,24 @@ func (w *Watcher) watchUser() error { out: for { select { - // Fsnotify events case event := <-watcher.Events: w.logger.Debug("Received fsnotify event", zap.Any("event", event)) name := normalizeSocketName(event.Name) - if event.Op&fsnotify.Create == fsnotify.Create { + switch { + case event.Op&fsnotify.Create == fsnotify.Create: w.events <- InterfaceEvent{ Op: InterfaceAdded, Name: name, } - } else if event.Op&fsnotify.Remove == fsnotify.Remove { + case event.Op&fsnotify.Remove == fsnotify.Remove: w.events <- InterfaceEvent{ Op: InterfaceDeleted, Name: name, } - } else { + default: w.logger.Warn("Unknown fsnotify event", zap.Any("event", event)) } diff --git a/pkg/wg/bind.go b/pkg/wg/bind.go index 75829f14..3cfff891 100644 --- a/pkg/wg/bind.go +++ b/pkg/wg/bind.go @@ -11,6 +11,12 @@ import ( "golang.zx2c4.com/wireguard/conn" ) +var ( + errIncompleteWrite = errors.New("incomplete write") + errNoEndpointFound = errors.New("failed to find endpoint") + errFailedToParseAddr = errors.New("failed to parse addr from slice") +) + type userPacket struct { endpoint *UserEndpoint buffer []byte @@ -71,7 +77,10 @@ func (b *UserBind) SetMark(mark uint32) error { // Send writes a packet b to address ep. func (b *UserBind) Send(buf []byte, ep conn.Endpoint) error { - uep := ep.(*UserEndpoint) + uep, ok := ep.(*UserEndpoint) + if !ok { + panic("invalid endpoint type") + } // b.logger.Debug("Send", // zap.Int("len", len(buf)), @@ -81,7 +90,7 @@ func (b *UserBind) Send(buf []byte, ep conn.Endpoint) error { if n, err := uep.conn.Write(buf); err != nil { return fmt.Errorf("failed to write: %w", err) } else if n != len(buf) { - return fmt.Errorf("incomplete write: %d != %d", n, len(buf)) + return fmt.Errorf("%w: %d != %d", errIncompleteWrite, n, len(buf)) } return nil @@ -101,7 +110,7 @@ func (b *UserBind) ParseEndpoint(s string) (ep conn.Endpoint, err error) { ep, ok := b.endpoints[ap] if !ok { - return nil, fmt.Errorf("failed to find endpoint") + return nil, errNoEndpointFound } return ep, nil @@ -111,19 +120,19 @@ func (b *UserBind) UpdateEndpoint(ep *net.UDPAddr, c net.Conn) (*UserEndpoint, e // b.logger.Debug("UpdateEndpoint", zap.Any("ep", ep)) // Remove v4-in-v6 prefix - epip := ep.IP - if epipv4 := epip.To4(); epipv4 != nil { - epip = epipv4 + epIP := ep.IP + if epIPv4 := epIP.To4(); epIPv4 != nil { + epIP = epIPv4 } - a, ok := netip.AddrFromSlice(epip) + a, ok := netip.AddrFromSlice(epIP) if !ok { - return nil, errors.New("failed to parse addr from slice") + return nil, errFailedToParseAddr } ap := netip.AddrPortFrom(a, uint16(ep.Port)) - uep := &UserEndpoint{ + uEP := &UserEndpoint{ StdNetEndpoint: conn.StdNetEndpoint(ap), conn: c, } @@ -132,9 +141,9 @@ func (b *UserBind) UpdateEndpoint(ep *net.UDPAddr, c net.Conn) (*UserEndpoint, e defer b.endpointsLock.Unlock() // TODO: Remove old endpoints - b.endpoints[ap] = uep + b.endpoints[ap] = uEP - return uep, nil + return uEP, nil } func (b *UserBind) receive(buf []byte) (int, conn.Endpoint, error) { diff --git a/pkg/wg/compare.go b/pkg/wg/compare.go index c77a266c..7a8e7a2a 100644 --- a/pkg/wg/compare.go +++ b/pkg/wg/compare.go @@ -24,11 +24,12 @@ func CmpPeerHandshakeTime(a, b wgtypes.Peer) int { } diff := a.LastHandshakeTime.UnixMilli() - b.LastHandshakeTime.UnixMilli() - if diff < 0 { + switch { + case diff < 0: return 1 - } else if diff > 0 { + case diff > 0: return -1 - } else { + default: return 0 } } diff --git a/pkg/wg/compare_test.go b/pkg/wg/compare_test.go index f2724d17..deb4c0b9 100644 --- a/pkg/wg/compare_test.go +++ b/pkg/wg/compare_test.go @@ -3,15 +3,13 @@ package wg_test import ( "time" - "github.com/stv0g/cunicu/pkg/wg" - "golang.zx2c4.com/wireguard/wgctrl/wgtypes" - . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/stv0g/cunicu/pkg/wg" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) var _ = Context("compare", func() { - Context("peer", func() { When("equal", func() { var a wgtypes.Peer diff --git a/pkg/wg/device.go b/pkg/wg/device.go index 3acca942..1b5c318d 100644 --- a/pkg/wg/device.go +++ b/pkg/wg/device.go @@ -45,25 +45,35 @@ func (d *Device) DumpEnv(wr io.Writer) error { return d.Dump(wr, hideKeys) } -func (d *Device) Dump(wr io.Writer, hideKeys bool) error { +func (d *Device) Dump(wr io.Writer, hideKeys bool) error { //nolint:gocognit wri := t.NewIndenter(wr, " ") fmt.Fprintf(wr, t.Mods("interface", t.Bold, t.FgGreen)+": "+t.Mods("%s", t.FgGreen)+"\n", d.Name) if crypto.Key(d.PrivateKey).IsSet() { - t.FprintKV(wri, "public key", d.PublicKey) + if _, err := t.FprintKV(wri, "public key", d.PublicKey); err != nil { + return err + } if hideKeys { - t.FprintKV(wri, "private key", "(hidden)") + if _, err := t.FprintKV(wri, "private key", "(hidden)"); err != nil { + return err + } } else { - t.FprintKV(wri, "private key", d.PrivateKey) + if _, err := t.FprintKV(wri, "private key", d.PrivateKey); err != nil { + return err + } } } - t.FprintKV(wri, "listening port", d.ListenPort) + if _, err := t.FprintKV(wri, "listening port", d.ListenPort); err != nil { + return err + } if d.FirewallMark > 0 { - t.FprintKV(wri, "fwmark", fmt.Sprintf("%d", d.FirewallMark)) + if _, err := t.FprintKV(wri, "fwmark", fmt.Sprintf("%d", d.FirewallMark)); err != nil { + return err + } } // Sort peers by last handshake time @@ -76,18 +86,26 @@ func (d *Device) Dump(wr io.Writer, hideKeys bool) error { if crypto.Key(p.PresharedKey).IsSet() { if hideKeys { - t.FprintKV(wri, "preshared key", "(hidden)") + if _, err := t.FprintKV(wri, "preshared key", "(hidden)"); err != nil { + return err + } } else { - t.FprintKV(wri, "preshared key", p.PresharedKey) + if _, err := t.FprintKV(wri, "preshared key", p.PresharedKey); err != nil { + return err + } } } if p.Endpoint != nil { - t.FprintKV(wri, "endpoint", p.Endpoint) + if _, err := t.FprintKV(wri, "endpoint", p.Endpoint); err != nil { + return err + } } if !p.LastHandshakeTime.IsZero() { - t.FprintKV(wri, "latest handshake", util.Ago(p.LastHandshakeTime)) + if _, err := t.FprintKV(wri, "latest handshake", util.Ago(p.LastHandshakeTime)); err != nil { + return err + } } if len(p.AllowedIPs) > 0 { @@ -96,19 +114,27 @@ func (d *Device) Dump(wr io.Writer, hideKeys bool) error { allowedIPs = append(allowedIPs, allowedIP.String()) } - t.FprintKV(wri, "allowed ips", strings.Join(allowedIPs, ", ")) + if _, err := t.FprintKV(wri, "allowed ips", strings.Join(allowedIPs, ", ")); err != nil { + return err + } } else { - t.FprintKV(wri, "allowed ips", "(none)") + if _, err := t.FprintKV(wri, "allowed ips", "(none)"); err != nil { + return err + } } if p.ReceiveBytes > 0 || p.TransmitBytes > 0 { - t.FprintKV(wri, "transfer", fmt.Sprintf("%s received, %s sent", + if _, err := t.FprintKV(wri, "transfer", fmt.Sprintf("%s received, %s sent", util.PrettyBytes(p.ReceiveBytes), - util.PrettyBytes(p.TransmitBytes))) + util.PrettyBytes(p.TransmitBytes))); err != nil { + return err + } } if p.PersistentKeepaliveInterval > 0 { - t.FprintKV(wri, "persistent keepalive", util.Every(p.PersistentKeepaliveInterval)) + if _, err := t.FprintKV(wri, "persistent keepalive", util.Every(p.PersistentKeepaliveInterval)); err != nil { + return err + } } } diff --git a/pkg/wg/device_config.go b/pkg/wg/device_config.go index 279972a1..b903cecc 100644 --- a/pkg/wg/device_config.go +++ b/pkg/wg/device_config.go @@ -79,10 +79,8 @@ func parseCIDRs(nets []string, ip bool) ([]net.IPNet, error) { } else { n.Mask = net.CIDRMask(128, 128) } - } else { - if ip { - n.IP = i - } + } else if ip { + n.IP = i } pn = append(pn, *n) @@ -217,7 +215,7 @@ func ParseConfig(data []byte) (*Config, error) { iniCfg := &config{} if err := iniFile.StrictMapTo(iniCfg); err != nil { - return nil, fmt.Errorf("failed to parse Interface section: %s", err) + return nil, fmt.Errorf("failed to parse Interface section: %w", err) } // Remove fake peer section again @@ -247,7 +245,7 @@ func ParseConfig(data []byte) (*Config, error) { func (c *config) Config() (*Config, error) { var err error - var cfg = &Config{ + cfg := &Config{ Config: wgtypes.Config{ Peers: []wgtypes.PeerConfig{}, ListenPort: c.Interface.ListenPort, diff --git a/proto/feature/epdisc_candidate.proto b/proto/feature/epdisc_candidate.proto index 9de74146..0d010bc0 100644 --- a/proto/feature/epdisc_candidate.proto +++ b/proto/feature/epdisc_candidate.proto @@ -170,7 +170,7 @@ message CandidatePairStats { // CurrentRoundTripTime represents the latest round trip time measured in seconds, // computed from both STUN connectivity checks, including those that are sent // for consent verification. - double curren_troundtrip_time = 16; + double current_roundtrip_time = 16; // AvailableOutgoingBitrate is calculated by the underlying congestion control // by combining the available bitrate for all the outgoing RTP streams using diff --git a/test/backend.go b/test/backend.go index 89aa8024..d9ee8e56 100644 --- a/test/backend.go +++ b/test/backend.go @@ -9,11 +9,9 @@ import ( "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" - "github.com/stv0g/cunicu/pkg/crypto" - "github.com/stv0g/cunicu/pkg/signaling" - epdiscproto "github.com/stv0g/cunicu/pkg/proto/feature/epdisc" + "github.com/stv0g/cunicu/pkg/signaling" ) type readyHandler struct { @@ -67,11 +65,12 @@ func (h *msgHandler) Check(p, o *peer) error { found := len(msgs2) - if found > 1 { + switch { + case found > 1: return fmt.Errorf("peer %d received %d messages from peer %d", p.id, found, o.id) - } else if found == 0 { + case found == 0: return fmt.Errorf("peer %d received no messages from peer %d", p.id, o.id) - } else { + default: msg := msgs2[0] if msg.Candidate.Port != int32(o.id) { return fmt.Errorf("received invalid msg: epoch == %d != %d", msg.Candidate.Port, o.id) @@ -106,7 +105,7 @@ func (p *peer) publish(o *peer) error { // TestBackend creates n peers with separate connections to the signaling backend u // and exchanges a test message between each pair of backends -func BackendTest(u *url.URL, n int) { +func BackendTest(u *url.URL, n int) { //nolint:gocognit var err error var ps []*peer @@ -169,7 +168,10 @@ func BackendTest(u *url.URL, n int) { gomega.Expect(err).To(gomega.Succeed()) } - kp := &crypto.KeyPair{Ours: p.key, Theirs: signaling.AnyKey} + kp := &crypto.KeyPair{ + Ours: p.key, + Theirs: crypto.Key{}, + } _, err = p.backend.Subscribe(context.Background(), kp, mh3) gomega.Expect(err).To(gomega.Succeed()) diff --git a/test/e2e/network_test.go b/test/e2e/network_test.go index 48591271..3023c5fa 100644 --- a/test/e2e/network_test.go +++ b/test/e2e/network_test.go @@ -10,19 +10,16 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/stv0g/cunicu/pkg/util" + "github.com/stv0g/cunicu/test" + "github.com/stv0g/cunicu/test/e2e/nodes" g "github.com/stv0g/gont/pkg" gopt "github.com/stv0g/gont/pkg/options" copt "github.com/stv0g/gont/pkg/options/capture" "go.uber.org/zap" - - "github.com/stv0g/cunicu/pkg/util" - "github.com/stv0g/cunicu/test" - "github.com/stv0g/cunicu/test/e2e/nodes" ) -var ( - logger *zap.Logger -) +var logger *zap.Logger type Network struct { *g.Network @@ -60,7 +57,7 @@ func (n *Network) Start() { }) Expect(err).To(Succeed(), "Failed to configure WireGuard interface: %s", err) - if setup { + if options.setup { Skip("Aborting test as only network setup has been requested") } @@ -71,7 +68,7 @@ func (n *Network) Start() { By("Writing network hosts file") hfn := filepath.Join(n.BasePath, "hosts") - hf, err := os.OpenFile(hfn, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) + hf, err := os.OpenFile(hfn, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644) Expect(err).To(Succeed(), "Failed to open hosts file: %s", err) err = n.Network.WriteHostsFile(hf) @@ -83,13 +80,14 @@ func (n *Network) Start() { By("Saving network nodes file") nfn := filepath.Join(n.BasePath, "nodes") - nf, err := os.OpenFile(nfn, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) + nf, err := os.OpenFile(nfn, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644) Expect(err).To(Succeed(), "Failed to open nodes file: %s", err) - n.AgentNodes.ForEachInterface(func(i *nodes.WireGuardInterface) error { + err = n.AgentNodes.ForEachInterface(func(i *nodes.WireGuardInterface) error { _, err := fmt.Fprintf(nf, "%s %s %s\n", i.Agent.Name(), i.Name, i.PrivateKey.PublicKey()) return err }) + Expect(err).To(Succeed()) err = nf.Close() Expect(err).To(Succeed(), "Failed to close nodes file: %s", err) @@ -172,7 +170,7 @@ func (n *Network) WriteSpecReport() { Expect(err).To(Succeed(), "Failed to indent report: %s", err) reportFileName := filepath.Join(n.BasePath, "report.json") - reportFile, err := os.OpenFile(reportFileName, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) + reportFile, err := os.OpenFile(reportFileName, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644) Expect(err).To(Succeed(), "Failed to open report file: %s", err) _, err = reportFile.Write(reportJSON) @@ -199,7 +197,7 @@ func (n *Network) ConnectivityTests() { func (n *Network) Init() { *n = Network{} - n.Name = fmt.Sprintf("cunicu-%d", rand.Uint32()) + n.Name = fmt.Sprintf("cunicu-%d", rand.Uint32()) //nolint:gosec n.BasePath = filepath.Join(SpecName()...) n.BasePath = filepath.Join("logs", n.BasePath) @@ -225,7 +223,7 @@ func (n *Network) Init() { By("Creating directory for new test case results") - err = os.MkdirAll(n.BasePath, 0755) + err = os.MkdirAll(n.BasePath, 0o755) Expect(err).To(Succeed(), "Failed to create test case result directory: %s", err) // Ginkgo log @@ -236,10 +234,10 @@ func (n *Network) Init() { ) n.NetworkOptions = append(n.NetworkOptions, - gopt.Persistent(persist), + gopt.Persistent(options.persist), ) - if capture { + if options.capture { n.NetworkOptions = append(n.NetworkOptions, gopt.CaptureAll( copt.Filename(pcapFilename), diff --git a/test/e2e/nodes/agent.go b/test/e2e/nodes/agent.go index 9dfbd8ae..871a0e5c 100644 --- a/test/e2e/nodes/agent.go +++ b/test/e2e/nodes/agent.go @@ -9,14 +9,12 @@ import ( "path/filepath" "strings" + "github.com/stv0g/cunicu/pkg/crypto" + rpcproto "github.com/stv0g/cunicu/pkg/proto/rpc" + "github.com/stv0g/cunicu/pkg/rpc" g "github.com/stv0g/gont/pkg" "go.uber.org/zap" "golang.zx2c4.com/wireguard/wgctrl" - - "github.com/stv0g/cunicu/pkg/crypto" - "github.com/stv0g/cunicu/pkg/rpc" - - rpcproto "github.com/stv0g/cunicu/pkg/proto/rpc" ) type AgentOption interface { @@ -79,8 +77,8 @@ func NewAgent(m *g.Network, name string, opts ...g.Option) (*Agent, error) { func (a *Agent) Start(_, dir string, extraArgs ...any) error { var err error var stdout, stderr io.Reader - var rpcSockPath = fmt.Sprintf("/var/run/cunicu.%s.sock", a.Name()) - var logPath = fmt.Sprintf("%s/%s.log", dir, a.Name()) + rpcSockPath := fmt.Sprintf("/var/run/cunicu.%s.sock", a.Name()) + logPath := fmt.Sprintf("%s/%s.log", dir, a.Name()) // Old RPC sockets are also removed by cunīcu. // However we also need to do it here to avoid racing @@ -118,12 +116,12 @@ func (a *Agent) Start(_, dir string, extraArgs ...any) error { //#nosec G304 -- Test code is not controllable by attackers //#nosec G302 -- Log file should be readable by user - a.logFile, err = os.OpenFile(logPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) + a.logFile, err = os.OpenFile(logPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644) if err != nil { return fmt.Errorf("failed to open log file: %w", err) } - go io.Copy(a.logFile, multi) + go io.Copy(a.logFile, multi) //nolint:errcheck if a.Client, err = rpc.Connect(rpcSockPath); err != nil { return fmt.Errorf("failed to connect to to control socket: %w", err) @@ -157,7 +155,7 @@ func (a *Agent) Stop() error { func (a *Agent) Close() error { if a.Client != nil { if err := a.Client.Close(); err != nil { - return fmt.Errorf("failed to close RPC connection: %s", err) + return fmt.Errorf("failed to close RPC connection: %w", err) } } diff --git a/test/e2e/nodes/agent_list.go b/test/e2e/nodes/agent_list.go index 37f48b0b..7eecd7be 100644 --- a/test/e2e/nodes/agent_list.go +++ b/test/e2e/nodes/agent_list.go @@ -49,7 +49,6 @@ func (al AgentList) ForEachInterface(cb func(i *WireGuardInterface) error) error g.Go(func() error { return cb(ni) }) - } } diff --git a/test/e2e/nodes/build.go b/test/e2e/nodes/build.go index dc548f48..de86df10 100644 --- a/test/e2e/nodes/build.go +++ b/test/e2e/nodes/build.go @@ -8,13 +8,12 @@ import ( "github.com/stv0g/cunicu/test" ) -var ( - testBinaryPath string -) +//nolint:gochecknoglobals +var testBinaryPath string func BuildTestBinary(name string) (string, []any, error) { var err error - var runArgs = []any{} + runArgs := []any{} profileFlags := getProfileFlags() diff --git a/test/e2e/nodes/options/wg/interface.go b/test/e2e/nodes/options/wg/interface.go index 95bd01e0..b3cefdd5 100644 --- a/test/e2e/nodes/options/wg/interface.go +++ b/test/e2e/nodes/options/wg/interface.go @@ -65,8 +65,11 @@ func (ski SetupKernelInterface) Apply(i *nodes.WireGuardInterface) { type PeerSelector nodes.WireGuardPeerSelectorFunc -var FullMeshPeers PeerSelector = func(i, j *nodes.WireGuardInterface) bool { return true } -var NoPeers PeerSelector = func(i, j *nodes.WireGuardInterface) bool { return false } +//nolint:gochecknoglobals +var ( + FullMeshPeers PeerSelector = func(i, j *nodes.WireGuardInterface) bool { return true } + NoPeers PeerSelector = func(i, j *nodes.WireGuardInterface) bool { return false } +) func (ps PeerSelector) Apply(i *nodes.WireGuardInterface) { i.PeerSelector = nodes.WireGuardPeerSelectorFunc(ps) @@ -79,8 +82,7 @@ func Interface(name string, opts ...g.Option) *nodes.WireGuardInterface { } for _, o := range opts { - switch opt := o.(type) { - case nodes.WireGuardInterfaceOption: + if opt, ok := o.(nodes.WireGuardInterfaceOption); ok { opt.Apply(i) } } diff --git a/test/e2e/nodes/options/wg/peer.go b/test/e2e/nodes/options/wg/peer.go index efd80838..efee81b4 100644 --- a/test/e2e/nodes/options/wg/peer.go +++ b/test/e2e/nodes/options/wg/peer.go @@ -84,10 +84,7 @@ func Peer(pk crypto.Key, opts ...nodes.WireGuardPeerOption) *nodes.WireGuardPeer } for _, o := range opts { - switch opt := o.(type) { - case nodes.WireGuardPeerOption: - opt.Apply(p) - } + o.Apply(p) } return p diff --git a/test/e2e/nodes/relay_coturn.go b/test/e2e/nodes/relay_coturn.go index 9d07a7b7..da12d85a 100644 --- a/test/e2e/nodes/relay_coturn.go +++ b/test/e2e/nodes/relay_coturn.go @@ -1,6 +1,7 @@ package nodes import ( + "errors" "fmt" "net" "os" @@ -14,6 +15,8 @@ import ( "go.uber.org/zap" ) +var errTimeout = errors.New("timed out") + type CoturnNode struct { *g.Host @@ -102,7 +105,8 @@ func (c *CoturnNode) Stop() error { if err := GracefullyTerminate(c.Command); err != nil { // Coturn exits with exit code 143 (SIGTERM received) - if err, ok := err.(*exec.ExitError); ok && err.ExitCode() == 143 { + exitErr := &exec.ExitError{} + if ok := errors.As(err, &exitErr); ok && exitErr.ExitCode() == 143 { return nil } } @@ -130,7 +134,7 @@ func (c *CoturnNode) isReachable() bool { func (c *CoturnNode) WaitReady() error { for tries := 1000; !c.isReachable(); tries-- { if tries == 0 { - return fmt.Errorf("timed out") + return errTimeout } time.Sleep(20 * time.Millisecond) diff --git a/test/e2e/nodes/relay_list.go b/test/e2e/nodes/relay_list.go index 0d93611c..840d60f8 100644 --- a/test/e2e/nodes/relay_list.go +++ b/test/e2e/nodes/relay_list.go @@ -1,3 +1,4 @@ +//nolint:dupl package nodes import ( diff --git a/test/e2e/nodes/signaling_grpc.go b/test/e2e/nodes/signaling_grpc.go index 4ada7a71..9c4c2e40 100644 --- a/test/e2e/nodes/signaling_grpc.go +++ b/test/e2e/nodes/signaling_grpc.go @@ -104,7 +104,7 @@ func (s *GrpcSignalingNode) isReachable() bool { func (s *GrpcSignalingNode) WaitReady() error { for tries := 1000; !s.isReachable(); tries-- { if tries == 0 { - return fmt.Errorf("timed out") + return errTimeout } time.Sleep(20 * time.Millisecond) diff --git a/test/e2e/nodes/signaling_list.go b/test/e2e/nodes/signaling_list.go index ad43633a..8ba84a97 100644 --- a/test/e2e/nodes/signaling_list.go +++ b/test/e2e/nodes/signaling_list.go @@ -1,3 +1,4 @@ +//nolint:dupl package nodes import ( diff --git a/test/e2e/nodes/wg_interface.go b/test/e2e/nodes/wg_interface.go index 97aff9e1..ac074eff 100644 --- a/test/e2e/nodes/wg_interface.go +++ b/test/e2e/nodes/wg_interface.go @@ -2,6 +2,7 @@ package nodes import ( "context" + "errors" "fmt" "io" "net" @@ -17,6 +18,8 @@ import ( "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) +var errNoTunnelAddr = errors.New("no WireGuard tunnel address configured") + type WireGuardPeerSelectorFunc func(i, j *WireGuardInterface) bool type WireGuardInterfaceOption interface { @@ -41,7 +44,7 @@ type WireGuardInterface struct { func (i *WireGuardInterface) Apply(a *Agent) { if i.Agent != nil { - panic(fmt.Errorf("can not assign interface to more than a single agent")) + panic("can not assign interface to more than a single agent") } i.Agent = a @@ -89,7 +92,7 @@ func (i *WireGuardInterface) WriteConfig() error { fn := filepath.Join(wgcpath, fmt.Sprintf("%s.conf", i.Name)) //#nosec G304 -- Test code is not controllable by attackers - f, err := os.OpenFile(fn, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0600) + f, err := os.OpenFile(fn, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0o600) if err != nil { return fmt.Errorf("failed to open config file: %w", err) } @@ -152,7 +155,7 @@ func (i *WireGuardInterface) AddPeer(peer *WireGuardInterface) { } i.Peers = append(i.Peers, wgtypes.PeerConfig{ - PublicKey: wgtypes.Key(peer.PrivateKey.PublicKey()), + PublicKey: peer.PrivateKey.PublicKey(), AllowedIPs: aIPs, }) } @@ -161,7 +164,7 @@ func (i *WireGuardInterface) PingPeer(ctx context.Context, peer *WireGuardInterf env := []string{"LC_ALL=C"} // fix issues with parsing of -W and -i options if len(peer.Addresses) < 1 { - return fmt.Errorf("no WireGuard tunnel address configured") + return errNoTunnelAddr } stdout, stderr, cmd, err := i.Agent.Host.StartWith("ping", env, "", "-c", 1, "-i", 0.2, "-w", time.Hour.Seconds(), peer.Addresses[0].IP) diff --git a/test/e2e/restart_test.go b/test/e2e/restart_test.go index e97e420b..e07ddc50 100644 --- a/test/e2e/restart_test.go +++ b/test/e2e/restart_test.go @@ -5,6 +5,8 @@ import ( "fmt" "time" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" "github.com/stv0g/cunicu/pkg/crypto" "github.com/stv0g/cunicu/pkg/proto" "github.com/stv0g/cunicu/pkg/util" @@ -12,9 +14,6 @@ import ( "github.com/stv0g/cunicu/test/e2e/nodes" opt "github.com/stv0g/cunicu/test/e2e/nodes/options" wopt "github.com/stv0g/cunicu/test/e2e/nodes/options/wg" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" g "github.com/stv0g/gont/pkg" gopt "github.com/stv0g/gont/pkg/options" ) @@ -178,6 +177,7 @@ var _ = Context("restart: Restart ICE agents", func() { By("Deleting old addresses from agent interface") for _, a := range i.Addresses { + a := a err = i.DeleteAddress(&a) Expect(err).To(Succeed(), "Failed to remove IP address '%s': %s", a, err) } @@ -197,13 +197,13 @@ var _ = Context("restart: Restart ICE agents", func() { } out, _, _ := n1.Run("ip", "a") - GinkgoWriter.Write(out) + GinkgoWriter.Write(out) //nolint:errcheck out, _, _ = n1.Run("wg") - GinkgoWriter.Write(out) + GinkgoWriter.Write(out) //nolint:errcheck out, _, _ = n2.Run("wg") - GinkgoWriter.Write(out) + GinkgoWriter.Write(out) //nolint:errcheck }) }) diff --git a/test/e2e/simple_test.go b/test/e2e/simple_test.go index ac0c1038..a19ba635 100644 --- a/test/e2e/simple_test.go +++ b/test/e2e/simple_test.go @@ -3,17 +3,16 @@ package e2e_test import ( "fmt" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" "github.com/stv0g/cunicu/pkg/wg" "github.com/stv0g/cunicu/test/e2e/nodes" opt "github.com/stv0g/cunicu/test/e2e/nodes/options" wopt "github.com/stv0g/cunicu/test/e2e/nodes/options/wg" - "golang.org/x/sys/unix" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" g "github.com/stv0g/gont/pkg" gopt "github.com/stv0g/gont/pkg/options" gfopt "github.com/stv0g/gont/pkg/options/filters" + "golang.org/x/sys/unix" ) /* Simple local-area switched topology with variable number of agents @@ -259,13 +258,14 @@ var _ = Context("simple: Simple local-area switched topology with variable numbe It("", func() { By("Check existing peers 2") - n.AgentNodes.ForEachAgent(func(a *nodes.Agent) error { + err := n.AgentNodes.ForEachAgent(func(a *nodes.Agent) error { out, _, err := a.Run("wg") Expect(err).To(Succeed()) - GinkgoWriter.Write(out) + GinkgoWriter.Write(out) //nolint:errcheck return nil }) + Expect(err).To(Succeed()) }) }) }) diff --git a/test/e2e/single_test.go b/test/e2e/single_test.go index 81f1ca52..76dcf7e1 100644 --- a/test/e2e/single_test.go +++ b/test/e2e/single_test.go @@ -1,11 +1,10 @@ package e2e_test import ( - "github.com/stv0g/cunicu/pkg/wg" - "github.com/stv0g/cunicu/test/e2e/nodes" - . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/stv0g/cunicu/pkg/wg" + "github.com/stv0g/cunicu/test/e2e/nodes" g "github.com/stv0g/gont/pkg" gopt "github.com/stv0g/gont/pkg/options" ) @@ -45,57 +44,44 @@ var _ = Context("single: A single node to test RPC and watcher", Pending, func() Context("create: Create a new interface", func() { Context("kernel: Kernel-space", func() { - }) Context("userspace: User-space", func() { - }) }) Context("watcher: Watch for changes of WireGuard interfaces and peers", Ordered, func() { It("detects a new interface", func() { - }) It("detects a change of the interface", func() { - }) It("detects a new peer", func() { - }) It("detects a change of the peer", func() { - }) It("detects the removal of the peer", func() { - }) It("detects the removal of the interface", func() { - }) }) Context("autocfg: Auto-configuration of missing interface parameters", Pending, func() { - }) Context("cfgsync: Config file synchronization", Pending, func() { - }) Context("hooks: Hook execution", Pending, func() { - }) Context("hsync: /etc/hosts synchronization", Pending, func() { - }) Context("rtsync: Route synchronization", Pending, func() { - }) }) diff --git a/test/e2e/suite_test.go b/test/e2e/suite_test.go index 3dda7b9e..122f84bc 100644 --- a/test/e2e/suite_test.go +++ b/test/e2e/suite_test.go @@ -5,38 +5,35 @@ import ( "math/rand" "strings" "testing" - "time" . "github.com/onsi/ginkgo/v2" + "github.com/onsi/ginkgo/v2/reporters" . "github.com/onsi/gomega" - "github.com/onsi/gomega/gexec" "github.com/stv0g/cunicu/pkg/util" - - "github.com/onsi/ginkgo/v2/reporters" - "github.com/onsi/ginkgo/v2/types" ) -var ( +//nolint:gochecknoglobals +var options testOptions + +type testOptions struct { setup bool persist bool capture bool -) +} // Register your flags in an init function. This ensures they are registered _before_ `go test` calls flag.Parse(). -func init() { - flag.BoolVar(&setup, "setup", false, "Do not run the actual tests, but stop after test-network setup") - flag.BoolVar(&persist, "persist", false, "Do not tear-down virtual network") - flag.BoolVar(&capture, "capture", false, "Captures network-traffic to PCAPng file") +func init() { //nolint:gochecknoinits + flag.BoolVar(&options.setup, "setup", false, "Do not run the actual tests, but stop after test-network setup") + flag.BoolVar(&options.persist, "persist", false, "Do not tear-down virtual network") + flag.BoolVar(&options.capture, "capture", false, "Captures network-traffic to PCAPng file") } func TestSuite(t *testing.T) { rand.Seed(GinkgoRandomSeed() + int64(GinkgoParallelProcess())) RegisterFailHandler(Fail) - RunSpecs(t, "E2E Test Suite", types.ReporterConfig{ - SlowSpecThreshold: 1 * time.Minute, - }) + RunSpecs(t, "E2E Test Suite") } var _ = BeforeSuite(func() { @@ -49,7 +46,9 @@ var _ = BeforeSuite(func() { var _ = ReportAfterSuite("Write report", func(r Report) { r.SpecReports = nil - reporters.GenerateJSONReport(r, "logs/report.json") + if err := reporters.GenerateJSONReport(r, "logs/report.json"); err != nil { + panic(err) + } }) func SpecName() []string { diff --git a/test/log.go b/test/log.go index 19886c46..65e1c984 100644 --- a/test/log.go +++ b/test/log.go @@ -43,7 +43,7 @@ func SetupLoggingWithFile(fn string, truncate bool) *zap.Logger { if fn != "" { // Create parent directories for log file if path := path.Dir(fn); path != "" { - if err := os.MkdirAll(path, 0750); err != nil { + if err := os.MkdirAll(path, 0o750); err != nil { panic(fmt.Errorf("failed to directory of log file: %w", err)) } } @@ -55,7 +55,7 @@ func SetupLoggingWithFile(fn string, truncate bool) *zap.Logger { //#nosec G304 -- Test code is not controllable by attackers //#nosec G302 -- Log file should be readable by users - f, err := os.OpenFile(fn, fl, 0644) + f, err := os.OpenFile(fn, fl, 0o644) if err != nil { panic(fmt.Errorf("failed to open log file '%s': %w", fn, err)) } diff --git a/test/matchers.go b/test/matchers.go index b87f64a6..46dcd862 100644 --- a/test/matchers.go +++ b/test/matchers.go @@ -2,12 +2,11 @@ package test import ( "errors" + "fmt" "time" "github.com/onsi/gomega/types" "github.com/stv0g/cunicu/pkg/core" - - "fmt" ) func BeRandom() types.GomegaMatcher { diff --git a/test/test.go b/test/test.go index 2f49ccc2..71af8b33 100644 --- a/test/test.go +++ b/test/test.go @@ -6,11 +6,11 @@ import ( "crypto/rand" "math" "math/big" + "os" "github.com/stv0g/cunicu/pkg/crypto" - "github.com/stv0g/cunicu/pkg/signaling" - epdiscproto "github.com/stv0g/cunicu/pkg/proto/feature/epdisc" + "github.com/stv0g/cunicu/pkg/signaling" ) func GenerateKeyPairs() (*crypto.KeyPair, *crypto.KeyPair, error) { @@ -51,8 +51,8 @@ func Entropy(data []byte) float64 { return 0 } - var length = float64(len(data)) - var entropy = 0.0 + length := float64(len(data)) + entropy := 0.0 for i := 0; i < 256; i++ { if p := float64(bytes.Count(data, []byte{byte(i)})) / length; p > 0 { @@ -62,3 +62,7 @@ func Entropy(data []byte) float64 { return entropy } + +func IsCI() bool { + return os.Getenv("CI") == "true" +}