From a981ca1cb09f9cec415e631cf8fec3b4a0254332 Mon Sep 17 00:00:00 2001 From: Hendrik Schlehlein Date: Fri, 2 Feb 2024 04:18:19 +0000 Subject: [PATCH] feat: add rate limiter (#201) * feat: add server addresses * feat: add config file loading * fix: proxy protocol Signed-off-by: haveachin * feat: add rate limiter Signed-off-by: haveachin * refactor: middleware to filter Signed-off-by: haveachin * refactor: more testability * refactor: listener to only one * refactor: config files * feat: add config file loading * refactor: filter for only one listener * feat: add docs * docs: add config docs * refactor: file loading * refactor: remove unnecessary mutex * docs: clean up readme * feat: add linting, ci and release automation * docs: add rate limiter * fix: linting errors * fix: linting errors * fix: linting errors * chore: clean up * refactor: ServerRequester * feat: add zerolog * chore: clean up --------- Signed-off-by: haveachin --- .github/FUNDING.yml | 12 + .github/workflows/ci.yml | 52 ++ .github/workflows/lint.yml | 46 ++ .github/workflows/release.yml | 49 ++ .gitignore | 5 +- .golangci.yml | 330 +++++++++ .goreleaser.yml | 95 +++ CONTRIBUTING.md | 45 ++ Makefile | 17 +- README.md | 88 +-- build/packages/Dockerfile | 12 + build/packages/Dockerfile.goreleaser | 4 + cmd/infrared/config.go | 6 +- cmd/infrared/main.go | 49 +- configs/config.yml | 27 +- configs/proxy.yml | 2 +- deployments/docker-compose.dev.yml | 8 +- deployments/docker-compose.yml | 11 + docs/.vitepress/config.mts | 21 +- docs/config/cli-and-env-vars.md | 24 +- docs/config/index.md | 18 +- docs/config/proxies.md | 13 +- docs/contribute.md | 1 - docs/features/filters.md | 20 + docs/features/forward-player-ips.md | 16 + docs/features/rate-limit-ips.md | 20 + docs/getting-started.md | 2 +- docs/guide/forward-player-ips.md | 15 - docs/index.md | 17 +- docs/package-lock.json | 637 +++++++++--------- docs/package.json | 2 +- go.mod | 13 +- go.sum | 27 +- pkg/infrared/config/file.go | 8 +- pkg/infrared/conn.go | 39 +- pkg/infrared/filter.go | 65 ++ pkg/infrared/infrared.go | 105 +-- pkg/infrared/infrared_test.go | 194 +----- .../handshaking/serverbound_handshake.go | 16 +- .../handshaking/serverbound_handshake_test.go | 139 ++-- .../protocol/login/clientbound_disconnect.go | 8 +- .../login/clientbound_disconnect_test.go | 13 +- .../login/clientbound_encryptionrequest.go | 12 +- .../login/serverbound_encryptionresponse.go | 13 +- .../protocol/login/serverbound_loginstart.go | 35 +- .../login/serverbound_loginstart_test.go | 15 +- pkg/infrared/protocol/packet.go | 22 +- pkg/infrared/protocol/peeker.go | 20 +- pkg/infrared/protocol/peeker_test.go | 37 +- .../protocol/play/clientbound_disconnect.go | 8 +- .../protocol/status/clientbound_response.go | 15 +- .../status/clientbound_response_test.go | 22 +- .../status/serverbound_ping_request.go | 4 - .../protocol/status/serverbound_request.go | 8 +- .../status/serverbound_request_test.go | 11 +- pkg/infrared/protocol/types.go | 98 +-- pkg/infrared/protocol/types_test.go | 36 +- pkg/infrared/protocol/versions.go | 16 +- pkg/infrared/rate_limiter.go | 245 +++++++ pkg/infrared/server.go | 150 +++-- tools/dos/main.go | 38 +- tools/malpk/main.go | 51 ++ 62 files changed, 2122 insertions(+), 1025 deletions(-) create mode 100644 .github/FUNDING.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/lint.yml create mode 100644 .github/workflows/release.yml create mode 100644 .golangci.yml create mode 100644 .goreleaser.yml create mode 100644 CONTRIBUTING.md create mode 100644 build/packages/Dockerfile create mode 100644 build/packages/Dockerfile.goreleaser create mode 100644 deployments/docker-compose.yml delete mode 100644 docs/contribute.md create mode 100644 docs/features/filters.md create mode 100644 docs/features/forward-player-ips.md create mode 100644 docs/features/rate-limit-ips.md delete mode 100644 docs/guide/forward-player-ips.md create mode 100644 pkg/infrared/filter.go delete mode 100644 pkg/infrared/protocol/status/serverbound_ping_request.go create mode 100644 pkg/infrared/rate_limiter.go create mode 100644 tools/malpk/main.go diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..4ac9da1d --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: haveachin +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: haveachin +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: https://paypal.me/hendrikschlehlein # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..007e7e7e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,52 @@ +name: CI + +on: + push: + branches: + - master + - main + pull_request: + +permissions: + contents: read + +env: + GO_VERSION: 1.21 + +jobs: + test: + name: Test + strategy: + matrix: + os: [ ubuntu-latest, macos-latest, windows-latest ] + runs-on: ${{ matrix.os }} + steps: + - name: Install Go + uses: actions/setup-go@v3 + with: + go-version: ${{ env.GO_VERSION }} + - name: Checkout code + uses: actions/checkout@v3 + - name: Test + run: make test + + build: + name: Build + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: ${{ env.GO_VERSION }} + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v5 + with: + # either 'goreleaser' (default) or 'goreleaser-pro' + distribution: goreleaser + args: build --snapshot \ No newline at end of file diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..c5ea5b91 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,46 @@ +name: Lint + +on: + push: + branches: + - master + - main + pull_request: + +permissions: + contents: read + pull-requests: read + checks: write + +env: + GO_VERSION: 1.21 + +jobs: + golangci: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 + with: + go-version: ${{ env.GO_VERSION }} + cache: false + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + # Require: The version of golangci-lint to use. + # When `install-mode` is `binary` (default) the value can be v1.2 or v1.2.3 or `latest` to use the latest version. + # When `install-mode` is `goinstall` the value can be v1.2.3, `latest`, or the hash of a commit. + version: v1.55.2 + + # Optional: working directory, useful for monorepos + # working-directory: somedir + + # Optional: golangci-lint command line arguments. + # + # Note: By default, the `.golangci.yml` file should be at the root of the repository. + # The location of the configuration file can be changed by using `--config=` + # args: --timeout=30m --config=/my/path/.golangci.yml --issues-exit-code=0 + + # Optional: show only new issues if it's a pull request. The default value is `false`. + only-new-issues: true \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..1d1a4214 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,49 @@ +name: Release + +on: + push: + tags: + - "*" + +permissions: + contents: write + packages: write + +env: + GO_VERSION: 1.21 + +jobs: + release: + name: Build & Release + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: ${{ env.GO_VERSION }} + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v5 + with: + # either 'goreleaser' (default) or 'goreleaser-pro' + distribution: goreleaser + version: latest + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 2571254d..a4636156 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Artifacts out/ __debug_bin* +dist/ # Benchmark/Profiler *.bench @@ -11,10 +12,10 @@ __debug_bin* .dev/ cmd/infrared/config.yml -# VSCode +# VSCode Dev Files .vscode/ -# IntelliJ +# IntelliJ Dev Files .idea/ # Docs diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 00000000..dea3674a --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,330 @@ +# This code is licensed under the terms of the MIT license https://opensource.org/license/mit +# Copyright (c) 2021 Marat Reymers + +## Golden config for golangci-lint v1.55.2 +# +# This is the best config for golangci-lint based on my experience and opinion. +# It is very strict, but not extremely strict. +# Feel free to adapt and change it for your needs. + +run: + # Timeout for analysis, e.g. 30s, 5m. + # Default: 1m + timeout: 3m + + +# This file contains only configs which differ from defaults. +# All possible options can be found here https://github.com/golangci/golangci-lint/blob/master/.golangci.reference.yml +linters-settings: + cyclop: + # The maximal code complexity to report. + # Default: 10 + max-complexity: 30 + # The maximal average package complexity. + # If it's higher than 0.0 (float) the check is enabled + # Default: 0.0 + package-average: 10.0 + + errcheck: + # Report about not checking of errors in type assertions: `a := b.(MyStruct)`. + # Such cases aren't reported by default. + # Default: false + check-type-assertions: true + + exhaustive: + # Program elements to check for exhaustiveness. + # Default: [ switch ] + check: + - switch + - map + + exhaustruct: + # List of regular expressions to exclude struct packages and names from check. + # Default: [] + exclude: + # std libs + - "^net/http.Client$" + - "^net/http.Cookie$" + - "^net/http.Request$" + - "^net/http.Response$" + - "^net/http.Server$" + - "^net/http.Transport$" + - "^net/url.URL$" + - "^os/exec.Cmd$" + - "^reflect.StructField$" + # public libs + - "^github.com/Shopify/sarama.Config$" + - "^github.com/Shopify/sarama.ProducerMessage$" + - "^github.com/mitchellh/mapstructure.DecoderConfig$" + - "^github.com/prometheus/client_golang/.+Opts$" + - "^github.com/spf13/cobra.Command$" + - "^github.com/spf13/cobra.CompletionOptions$" + - "^github.com/stretchr/testify/mock.Mock$" + - "^github.com/testcontainers/testcontainers-go.+Request$" + - "^github.com/testcontainers/testcontainers-go.FromDockerfile$" + - "^golang.org/x/tools/go/analysis.Analyzer$" + - "^google.golang.org/protobuf/.+Options$" + - "^gopkg.in/yaml.v3.Node$" + + funlen: + # Checks the number of lines in a function. + # If lower than 0, disable the check. + # Default: 60 + lines: 100 + # Checks the number of statements in a function. + # If lower than 0, disable the check. + # Default: 40 + statements: 50 + # Ignore comments when counting lines. + # Default false + ignore-comments: true + + gocognit: + # Minimal code complexity to report. + # Default: 30 (but we recommend 10-20) + min-complexity: 20 + + gocritic: + # Settings passed to gocritic. + # The settings key is the name of a supported gocritic checker. + # The list of supported checkers can be find in https://go-critic.github.io/overview. + settings: + captLocal: + # Whether to restrict checker to params only. + # Default: true + paramsOnly: false + underef: + # Whether to skip (*x).method() calls where x is a pointer receiver. + # Default: true + skipRecvDeref: false + + gomnd: + # List of function patterns to exclude from analysis. + # Values always ignored: `time.Date`, + # `strconv.FormatInt`, `strconv.FormatUint`, `strconv.FormatFloat`, + # `strconv.ParseInt`, `strconv.ParseUint`, `strconv.ParseFloat`. + # Default: [] + ignored-functions: + - flag.Arg + - flag.Duration.* + - flag.Float.* + - flag.Int.* + - flag.Uint.* + - os.Chmod + - os.Mkdir.* + - os.OpenFile + - os.WriteFile + - prometheus.ExponentialBuckets.* + - prometheus.LinearBuckets + + gomodguard: + blocked: + # List of blocked modules. + # Default: [] + modules: + - github.com/golang/protobuf: + recommendations: + - google.golang.org/protobuf + reason: "see https://developers.google.com/protocol-buffers/docs/reference/go/faq#modules" + - github.com/satori/go.uuid: + recommendations: + - github.com/google/uuid + reason: "satori's package is not maintained" + - github.com/gofrs/uuid: + recommendations: + - github.com/google/uuid + reason: "gofrs' package is not go module" + + govet: + # Enable all analyzers. + # Default: false + enable-all: true + # Disable analyzers by name. + # Run `go tool vet help` to see all analyzers. + # Default: [] + disable: + - fieldalignment # too strict + # Settings per analyzer. + settings: + shadow: + # Whether to be strict about shadowing; can be noisy. + # Default: false + strict: true + + nakedret: + # Make an issue if func has more lines of code than this setting, and it has naked returns. + # Default: 30 + max-func-lines: 0 + + nolintlint: + # Exclude following linters from requiring an explanation. + # Default: [] + allow-no-explanation: [ funlen, gocognit, lll ] + # Enable to require an explanation of nonzero length after each nolint directive. + # Default: false + require-explanation: true + # Enable to require nolint directives to mention the specific linter being suppressed. + # Default: false + require-specific: true + + rowserrcheck: + # database/sql is always checked + # Default: [] + packages: + - github.com/jmoiron/sqlx + + tenv: + # The option `all` will run against whole test files (`_test.go`) regardless of method/function signatures. + # Otherwise, only methods that take `*testing.T`, `*testing.B`, and `testing.TB` as arguments are checked. + # Default: false + all: true + + +linters: + disable-all: true + enable: + ## enabled by default + - errcheck # checking for unchecked errors, these unchecked errors can be critical bugs in some cases + - gosimple # specializes in simplifying a code + - govet # reports suspicious constructs, such as Printf calls whose arguments do not align with the format string + - ineffassign # detects when assignments to existing variables are not used + - staticcheck # is a go vet on steroids, applying a ton of static analysis checks + - typecheck # like the front-end of a Go compiler, parses and type-checks Go code + - unused # checks for unused constants, variables, functions and types + ## disabled by default + - asasalint # checks for pass []any as any in variadic func(...any) + - asciicheck # checks 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 + - cyclop # checks function and package cyclomatic complexity + - dupl # tool for code clone detection + - durationcheck # checks for two durations multiplied together + - errname # checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error + - errorlint # finds code that will cause problems with the error wrapping scheme introduced in Go 1.13 + - execinquery # checks query string in Query function which reads your Go src files and warning it finds + - exhaustive # checks exhaustiveness of enum switch statements + - exportloopref # checks for pointers to enclosing loop variables + - forbidigo # forbids identifiers + - funlen # tool for detection of long functions + - gocheckcompilerdirectives # validates go compiler directive comments (//go:) + - gochecknoinits # checks that no init functions are present in Go code + - gochecksumtype # checks exhaustiveness on Go "sum types" + - gocognit # computes and checks the cognitive complexity of functions + - goconst # finds repeated strings that could be replaced by a constant + - gocritic # provides diagnostics that check for bugs, performance and style issues + - gocyclo # computes and checks the cyclomatic complexity of functions + - godot # checks if comments end in a period + - goimports # in addition to fixing imports, goimports also formats your code in the same style as gofmt + - gomoddirectives # manages the use of 'replace', 'retract', and 'excludes' directives in go.mod + - gomodguard # allow and block lists 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 + - lll # reports long lines + - loggercheck # checks key value pairs for common logger libraries (kitlog,klog,logr,zap) + - makezero # finds slice declarations with non-zero initial length + - mirror # reports wrong mirror patterns of bytes/strings usage + - musttag # enforces field tags in (un)marshaled structs + - nakedret # finds naked returns in functions greater than a specified function length + - nestif # reports deeply nested if statements + - 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 # finds sending http request without context.Context + - nolintlint # reports ill-formed or insufficient nolint directives + - nonamedreturns # reports all named returns + - nosprintfhostport # checks for misuse of Sprintf to construct a host with port in a URL + - perfsprint # checks that fmt.Sprintf can be replaced with a faster alternative + - predeclared # finds code that shadows one of Go's predeclared identifiers + - promlinter # checks Prometheus metrics naming via promlint + - protogetter # reports direct reads from proto message fields when getters should be used + - reassign # checks that package variables are not reassigned + - revive # fast, configurable, extensible, flexible, and beautiful linter for Go, drop-in replacement of golint + - rowserrcheck # checks whether Err of rows is checked successfully + - sloglint # ensure consistent code style when using log/slog + - sqlclosecheck # checks that sql.Rows and sql.Stmt are closed + - stylecheck # is a replacement for golint + - tenv # detects using os.Setenv instead of t.Setenv since Go1.17 + - testableexamples # checks if examples are testable (have an expected output) + - testifylint # checks usage of github.com/stretchr/testify + - tparallel # detects inappropriate usage of t.Parallel() method in your Go test codes + - unconvert # removes unnecessary type conversions + - unparam # reports unused function parameters + - usestdlibvars # detects the possibility to use variables/constants from the Go standard library + - wastedassign # finds wasted assignment statements + - whitespace # detects leading and trailing whitespace + - prealloc # [premature optimization, but can be used in some cases] finds slice declarations that could potentially be preallocated + + ## you may want to enable + #- decorder # checks declaration order and count of types, constants, variables and functions + #- exhaustruct # [highly recommend to enable] checks if all structure fields are initialized + #- gci # controls golang package import order and makes it always deterministic + #- ginkgolinter # [if you use ginkgo/gomega] enforces standards of using ginkgo and gomega + #- godox # detects FIXME, TODO and other comment keywords + #- goheader # checks is file header matches to pattern + #- inamedparam # [great idea, but too strict, need to ignore a lot of cases by default] reports interfaces with unnamed method parameters + #- interfacebloat # checks the number of methods inside an interface + #- ireturn # accept interfaces, return concrete types + #- tagalign # checks that struct tags are well aligned + #- varnamelen # [great idea, but too many false positives] checks that the length of a variable's name matches its scope + #- wrapcheck # checks that errors returned from external packages are wrapped + #- zerologlint # detects the wrong usage of zerolog that a user forgets to dispatch zerolog.Event + #- gochecknoglobals # checks that no global variables exist + #- gomnd # detects magic numbers + #- testpackage # makes you use a separate _test package + + ## disabled + #- containedctx # detects struct contained context.Context field + #- contextcheck # [too many false positives] checks the function whether use a non-inherited context + #- depguard # [replaced by gomodguard] checks if package imports are in a list of acceptable packages + #- dogsled # checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) + #- dupword # [useless without config] checks for duplicate words in the source code + #- errchkjson # [don't see profit + I'm against of omitting errors like in the first example https://github.com/breml/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 + #- forcetypeassert # [replaced by errcheck] finds forced type assertions + #- goerr113 # [too strict] checks the errors handling expressions + #- gofmt # [replaced by goimports] checks whether code was gofmt-ed + #- gofumpt # [replaced by goimports, gofumports is not available yet] checks whether code was gofumpt-ed + #- gosmopolitan # reports certain i18n/l10n anti-patterns in your Go codebase + #- grouper # analyzes expression groups + #- importas # enforces consistent import aliases + #- maintidx # measures the maintainability index of each function + #- misspell # [useless] finds commonly misspelled English words in comments + #- nlreturn # [too strict and mostly code is not more readable] checks for a new line before return and branch statements to increase code clarity + #- paralleltest # [too many false positives] detects missing usage of t.Parallel() method in your Go test + #- tagliatelle # checks the struct tags + #- thelper # detects golang test helpers without t.Helper() call and checks the consistency of test helpers + #- wsl # [too strict and mostly code is not more readable] whitespace linter forces you to use empty lines + + ## deprecated + #- deadcode # [deprecated, replaced by unused] finds unused code + #- exhaustivestruct # [deprecated, replaced by exhaustruct] checks if all struct's fields are initialized + #- golint # [deprecated, replaced by revive] golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes + #- ifshort # [deprecated] checks that your code uses short syntax for if-statements whenever possible + #- interfacer # [deprecated] suggests narrower interface types + #- maligned # [deprecated, replaced by govet fieldalignment] detects Go structs that would take less memory if their fields were sorted + #- nosnakecase # [deprecated, replaced by revive var-naming] detects snake case of variable naming and function name + #- scopelint # [deprecated, replaced by exportloopref] checks for unpinned variables in go programs + #- structcheck # [deprecated, replaced by unused] finds unused struct fields + #- varcheck # [deprecated, replaced by unused] finds unused global variables and constants + + +issues: + # Maximum count of issues with the same text. + # Set to 0 to disable. + # Default: 3 + max-same-issues: 50 + + exclude-rules: + - source: "(noinspection|TODO)" + linters: [ godot ] + - source: "//noinspection" + linters: [ gocritic ] + - path: "_test\\.go" + linters: + - bodyclose + - dupl + - funlen + - goconst + - gosec + - noctx + - wrapcheck + - text: 'shadow: declaration of "(err|ctx)" shadows declaration at' + linters: [ govet ] \ No newline at end of file diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 00000000..8ca7cf81 --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,95 @@ +# Make sure to check the documentation at https://goreleaser.com + +version: 1 + +env: + - REPO_URL=https://github.com/haveachin/infrared + - DOCKER_IMAGE_NAME=haveachin/infrared + +before: + hooks: + - go mod tidy + +builds: + - main: ./cmd/infrared + env: + - CGO_ENABLED=0 + binary: infrared + goos: + - linux + - windows + - darwin + +archives: + - format: tar.gz + # this name template makes the OS and Arch compatible with the results of `uname`. + name_template: >- + {{ .ProjectName }}_ + {{- title .Os }}_ + {{- if eq .Arch "amd64" }}x86_64 + {{- else if eq .Arch "386" }}i386 + {{- else }}{{ .Arch }}{{ end }} + {{- if .Arm }}v{{ .Arm }}{{ end }} + # use zip for windows archives + format_overrides: + - goos: windows + format: zip + +dockers: + - image_templates: + - "docker.io/{{ .Env.DOCKER_IMAGE_NAME }}:{{ .Version }}-amd64" + - "docker.io/{{ .Env.DOCKER_IMAGE_NAME }}:latest-amd64" + - "ghcr.io/{{ .Env.DOCKER_IMAGE_NAME }}:{{ .Version }}-amd64" + - "ghcr.io/{{ .Env.DOCKER_IMAGE_NAME }}:latest-amd64" + use: buildx + dockerfile: build/packages/Dockerfile.goreleaser + build_flag_templates: + - "--pull" + - "--platform=linux/amd64" + - "--label=org.opencontainers.image.created={{ .Date }}" + - "--label=org.opencontainers.image.title={{ .ProjectName }}" + - "--label=org.opencontainers.image.revision={{ .FullCommit }}" + - "--label=org.opencontainers.image.version={{ .Version }}" + - "--label=org.opencontainers.image.source={{ .Env.REPO_URL }}" + - image_templates: + - "docker.io/{{ .Env.DOCKER_IMAGE_NAME }}:{{ .Version }}-arm64v8" + - "docker.io/{{ .Env.DOCKER_IMAGE_NAME }}:latest-arm64v8" + - "ghcr.io/{{ .Env.DOCKER_IMAGE_NAME }}:{{ .Version }}-arm64v8" + - "ghcr.io/{{ .Env.DOCKER_IMAGE_NAME }}:latest-arm64v8" + use: buildx + goarch: arm64 + dockerfile: build/packages/Dockerfile.goreleaser + build_flag_templates: + - "--pull" + - "--platform=linux/arm64/v8" + - "--label=org.opencontainers.image.created={{ .Date }}" + - "--label=org.opencontainers.image.title={{ .ProjectName }}" + - "--label=org.opencontainers.image.revision={{ .FullCommit }}" + - "--label=org.opencontainers.image.version={{ .Version }}" + - "--label=org.opencontainers.image.source={{ .Env.REPO_URL }}" + +docker_manifests: + - name_template: "{{ .Env.DOCKER_IMAGE_NAME }}:{{ .Version }}" + image_templates: + - "{{ .Env.DOCKER_IMAGE_NAME }}:{{ .Version }}-amd64" + - "{{ .Env.DOCKER_IMAGE_NAME }}:{{ .Version }}-arm64v8" + - name_template: "{{ .Env.DOCKER_IMAGE_NAME }}:latest" + image_templates: + - "{{ .Env.DOCKER_IMAGE_NAME }}:latest-amd64" + - "{{ .Env.DOCKER_IMAGE_NAME }}:latest-arm64v8" + - name_template: "ghcr.io/{{ .Env.DOCKER_IMAGE_NAME }}:{{ .Version }}" + image_templates: + - "ghcr.io/{{ .Env.DOCKER_IMAGE_NAME }}:{{ .Version }}-amd64" + - "ghcr.io/{{ .Env.DOCKER_IMAGE_NAME }}:{{ .Version }}-arm64v8" + - name_template: "ghcr.io/{{ .Env.DOCKER_IMAGE_NAME }}:latest" + image_templates: + - "ghcr.io/{{ .Env.DOCKER_IMAGE_NAME }}:latest-amd64" + - "ghcr.io/{{ .Env.DOCKER_IMAGE_NAME }}:latest-arm64v8" + +changelog: + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" + - "^chore:" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..3427c012 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,45 @@ +# Contributing + +Feel free to add or modify the source code. On GitHub the best way of doing this is by forking this repository, then cloning your fork with Git to your local system. After adding or modifying the source code, push it back to your fork and open a pull request in this repository. + +If you can't contribute by adding or modifying the source code, then you might be able to reach out to someone who can. +You can also contribute indirectly by donation. + +## Tools + +- Coding + - [Go](https://go.dev/) + - [GNUMake](https://www.gnu.org/software/make/) (Optional) + - [Docker](https://www.docker.com/get-started/) (Optional) + - [golangci-lint](https://golangci-lint.run/) (Optional) +- Docs + - [Node.js](https://nodejs.org/) + + +## Contributing + +TL;DR + +- [Project Layout](https://github.com/golang-standards/project-layout) - where it makes sense. +- [Code Style](https://github.com/uber-go/guide/blob/master/style.md) - where it makes sense. +- [Commit Messages](https://www.conventionalcommits.org/en/v1.0.0/) - only the summary is good enough. +- [Versioning](https://semver.org/) + +### Project Layout + +We try to use [golang-standards/project-layout](https://github.com/golang-standards/project-layout) as a reference. This should give Infrared a good foundation to grow on. + +### Commit Messages + +When contributing to this project please follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) +specification for writing commit messages, so that changelogs and release versions can be generated automatically. + +Examples can be found here: https://www.conventionalcommits.org/en/v1.0.0/#examples + +Some tooling that can help you author those commit messages are the following plugins: + +- JetBrains Plugin [Conventional Commit](https://plugins.jetbrains.com/plugin/13389-conventional-commit) + by [Edoardo Luppi](https://github.com/lppedd) +- Visual Studio Code + Plugin [Conventional Commits](https://marketplace.visualstudio.com/items?itemName=vivaxy.vscode-conventional-commits) + by [vivaxy](https://marketplace.visualstudio.com/publishers/vivaxy) \ No newline at end of file diff --git a/Makefile b/Makefile index 32596f35..43567afb 100644 --- a/Makefile +++ b/Makefile @@ -3,11 +3,13 @@ test: go test -race -timeout 10s ./... -all: test +build: CGO_ENABLED=0 go build -ldflags "-s -w" -o ./out/infrared ./cmd/infrared -run: all - ./out/infrared +all: test build + +run: build + ./out/infrared -w .dev/infrared bench: go test -bench=. -run=x -benchmem -memprofile mem.prof -cpuprofile cpu.prof -benchtime=10s > 0.bench @@ -20,5 +22,12 @@ dos: CGO_ENABLED=0 go build -ldflags "-s -w" -o ./out/dos ./tools/dos ./out/dos +malpk: + CGO_ENABLED=0 go build -ldflags "-s -w" -o ./out/malpk ./tools/malpk + ./out/malpk + docs: - cd ./docs && npm run docs:dev + cd ./docs && npm i && npm run docs:dev + +lint: + golangci-lint run diff --git a/README.md b/README.md index a5c5b207..155118b7 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,23 @@

- +

+

Infrared

+

A Minecraft Reverse Proxy

-
- -[![Discord](https://img.shields.io/discord/800456341088370698?label=discord&logo=discord)](https://discord.gg/r98YPRsZAx) -[![Docker Pulls](https://img.shields.io/docker/pulls/haveachin/infrared?logo=docker)](https://hub.docker.com/r/haveachin/infrared) - -![Test, Build, Release](https://github.com/haveachin/infrared/actions/workflows/test-build-release.yml/badge.svg) - -
- -
- -
-
+

+ + Discord + + + Docker Pulls + +
+ CI +

-# Infrared - A Minecraft Reverse Proxy +> [!WARNING] +> Infrared is currently under active development: breaking changes can happen. +> Feedback and contributions are welcome. An ultra lightweight Minecraft reverse proxy and status placeholder: Ever wanted to have only one exposed port on your server for multiple Minecraft servers? @@ -29,36 +30,26 @@ Infrared works as a reverse proxy using a sub-/domains to connect clients to a s - [X] Wildcards Support - [X] Multi-Domain Support - [X] Status Response Caching -- [ ] Proxy Protocol Support -- [ ] Ratelimiter - -## Contributing +- [X] Proxy Protocol Support +- [X] Ratelimiter -Feel free to add or modify the source code. On GitHub the best way of doing this is by forking this repository, then cloning your fork with Git to your local system. After adding or modifying the source code, push it back to your fork and open a pull request in this repository. +## Useful Links -If you can't contribute by adding or modifying the source code, then you might be able to reach out to someone who can. -You can also contribute indirectly by donation. +- **[Docs](https://infrared.dev)** +- **[Ask Questions](https://github.com/haveachin/infrared/discussions)** +- [Latest Release](https://github.com/haveachin/infrared/releases/latest) +- [Discord Invite](https://discord.gg/r98YPRsZAx) +- [Contributing](CONTRIBUTING.md) -## Coding Guidelines +## Build -## Project Layout +Requirements: +- [Go](https://go.dev/) 1.21+ -We try to use [golang-standards/project-layout](https://github.com/golang-standards/project-layout) as a reference. This should give Infrared a good foundation to grow on. - -### Commit Messages - -When contributing to this project please follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) -specification for writing commit messages, so that changelogs and release versions can be generated automatically. - -Examples can be found here: https://www.conventionalcommits.org/en/v1.0.0/#examples - -Some tooling that can help you author those commit messages are the following plugins: - -* JetBrains Plugin [Conventional Commit](https://plugins.jetbrains.com/plugin/13389-conventional-commit) - by [Edoardo Luppi](https://github.com/lppedd) -* Visual Studio - Plugin [Conventional Commits](https://marketplace.visualstudio.com/items?itemName=vivaxy.vscode-conventional-commits) - by [vivaxy](https://marketplace.visualstudio.com/publishers/vivaxy) +``` +CGO_ENABLED=0 go build -ldflags "-s -w" -o ./out/infrared ./cmd/infrared +``` +or `make all` (requires GNU Make). The binary is in the `out/` directory. ## Similar Projects @@ -66,5 +57,18 @@ Some tooling that can help you author those commit messages are the following pl ## Attributions -* Free Software Foundation, Public domain, via Wikimedia Commons -* [Tnze/go-mc](https://github.com/Tnze/go-mc/blob/master/LICENSE), MIT License \ No newline at end of file +- [Free Software Foundation](https://commons.wikimedia.org/wiki/File:AGPLv3_Logo.svg), Public domain, via Wikimedia Commons +- [Tnze/go-mc](https://github.com/Tnze/go-mc) 🚀, MIT +- [IGLOU-EU/go-wildcard](https://github.com/IGLOU-EU/go-wildcard), Apache-2.0 +- [cespare/xxhash](https://github.com/cespare/xxhash), MIT +- [google/uuid](https://github.com/google/uuid), BSD-3-Clause +- [pires/go-proxyproto](https://github.com/pires/go-proxyproto), Apache-2.0 +- [spf13/pflag](https://github.com/spf13/pflag), BSD-3-Clause +- [go-yaml/yaml](https://github.com/go-yaml/yaml), Apache-2.0, MIT +- [vitepress](https://github.com/vuejs/vitepress), MIT +- [tollbooth](https://github.com/didip/tollbooth), MIT + +
+

+ +

\ No newline at end of file diff --git a/build/packages/Dockerfile b/build/packages/Dockerfile new file mode 100644 index 00000000..10c82d2d --- /dev/null +++ b/build/packages/Dockerfile @@ -0,0 +1,12 @@ +FROM --platform=$BUILDPLATFORM golang:alpine AS builder +ARG TARGETARCH +WORKDIR /tmp/build +COPY . /tmp/build +ENV GO111MODULE=on +RUN CGO_ENABLED=0 GOOS=linux GOARCH=$TARGETARCH go build -ldflags "-s -w" -o ./out/infrared ./cmd/infrared +RUN chmod +x ./out/infrared + +FROM alpine:latest +COPY --from=builder /tmp/build/out/infrared /usr/bin/ +WORKDIR /etc/infrared +ENTRYPOINT [ "/usr/bin/infrared" ] \ No newline at end of file diff --git a/build/packages/Dockerfile.goreleaser b/build/packages/Dockerfile.goreleaser new file mode 100644 index 00000000..4c3783dc --- /dev/null +++ b/build/packages/Dockerfile.goreleaser @@ -0,0 +1,4 @@ +FROM alpine:latest +COPY infrared /usr/bin/infrared +WORKDIR /etc/infrared +ENTRYPOINT [ "/usr/bin/infrared" ] \ No newline at end of file diff --git a/cmd/infrared/config.go b/cmd/infrared/config.go index 54e78c90..ba41533d 100644 --- a/cmd/infrared/config.go +++ b/cmd/infrared/config.go @@ -10,11 +10,13 @@ import ( func createConfigIfNotExist() error { info, err := os.Stat(configPath) if errors.Is(err, os.ErrNotExist) { - if err := os.Mkdir(proxiesDir, 0755); err != nil { + if err = os.Mkdir(proxiesDir, 0755); err != nil { return err } return createDefaultConfigFile() + } else if err != nil { + return err } if info.IsDir() { @@ -26,5 +28,5 @@ func createConfigIfNotExist() error { func createDefaultConfigFile() error { bb := configs.DefaultInfraredConfig - return os.WriteFile(configPath, bb, 0664) + return os.WriteFile(configPath, bb, 0600) } diff --git a/cmd/infrared/main.go b/cmd/infrared/main.go index e02493e1..2eb6098b 100644 --- a/cmd/infrared/main.go +++ b/cmd/infrared/main.go @@ -1,13 +1,15 @@ package main import ( - "log" "os" "os/signal" "syscall" + "time" ir "github.com/haveachin/infrared/pkg/infrared" "github.com/haveachin/infrared/pkg/infrared/config" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" "github.com/spf13/pflag" ) @@ -19,6 +21,7 @@ var ( configPath = "config.yml" workingDir = "." proxiesDir = "./proxies" + logLevel = "info" ) func envVarString(p *string, name string) { @@ -34,25 +37,56 @@ func initEnvVars() { envVarString(&configPath, "CONFIG") envVarString(&workingDir, "WORKING_DIR") envVarString(&proxiesDir, "PROXIES_DIR") + envVarString(&logLevel, "LOG_LEVEL") } func initFlags() { pflag.StringVarP(&configPath, "config", "c", configPath, "path to the config file") pflag.StringVarP(&workingDir, "working-dir", "w", workingDir, "changes the current working directory") pflag.StringVarP(&proxiesDir, "proxies-dir", "p", proxiesDir, "path to the proxies directory") + pflag.StringVarP(&logLevel, "log-level", "l", logLevel, "log level [debug, info, warn, error]") pflag.Parse() } -func init() { - initEnvVars() - initFlags() +func initLogger() { + log.Logger = log.Output(zerolog.ConsoleWriter{ + Out: os.Stdout, + TimeFormat: time.RFC3339, + }) + + var level zerolog.Level + switch logLevel { + case "debug": + level = zerolog.DebugLevel + case "info": + level = zerolog.InfoLevel + case "warn": + level = zerolog.WarnLevel + case "error": + level = zerolog.ErrorLevel + default: + log.Warn(). + Str("level", logLevel). + Msg("Invalid log level; defaulting to info") + } + + zerolog.SetGlobalLevel(level) + log.Debug(). + Str("level", logLevel). + Msg("Log level set") } func main() { - log.Println("Starting Infrared") + initEnvVars() + initFlags() + initLogger() + + log.Info().Msg("Starting Infrared") if err := run(); err != nil { - log.Fatal(err) + log.Fatal(). + Err(err). + Msg("Failed to run") } } @@ -69,6 +103,7 @@ func run() error { ConfigPath: configPath, ProxiesPath: proxiesDir, }) + srv.Logger = log.Logger errChan := make(chan error, 1) go func() { @@ -78,7 +113,7 @@ func run() error { sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) - log.Println("System is online") + log.Info().Msg("System is online") select { case sig := <-sigChan: diff --git a/configs/config.yml b/configs/config.yml index f9fbc90b..4af56ffe 100644 --- a/configs/config.yml +++ b/configs/config.yml @@ -4,16 +4,23 @@ # bind: 0.0.0.0:25565 -proxyProtocol: - # Receive proxy protocol - # - #receive: true - - # TODO - # - #trustedProxies: - # - "127.0.0.1" - # Maximum duration between packets before the client gets timed out. # keepAliveTimeout: 30s + +# Filter are hooks that trigger befor a connection is processed. +# They are used as preconditions to validate a connection. +# +filters: + # Rate Limiter will only allow an IP address to connect a specified + # amount of times in a given time frame. + # + rateLimiter: + # Request Limit is the amount of times an IP address can create + # a new connection before it gets blocked. + # + requestLimit: 10 + + # Windows Length is the time frame for the Request Limit. + # + windowLength: 1s \ No newline at end of file diff --git a/configs/proxy.yml b/configs/proxy.yml index 709b46da..eab5d5de 100644 --- a/configs/proxy.yml +++ b/configs/proxy.yml @@ -8,7 +8,7 @@ domains: - "*" addresses: - - example.com:25565 + - 127.0.0.1:25565 # Send a Proxy Protocol v2 Header to the server to # forward the players IP address diff --git a/deployments/docker-compose.dev.yml b/deployments/docker-compose.dev.yml index 9d2d88c3..9a4e1f3d 100644 --- a/deployments/docker-compose.dev.yml +++ b/deployments/docker-compose.dev.yml @@ -23,14 +23,14 @@ services: haproxy: image: haproxy container_name: infrared-dev-haproxy - networks: - - infrared - volumes: - - ../.dev/haproxy:/usr/local/etc/haproxy:ro sysctls: - net.ipv4.ip_unprivileged_port_start=0 + volumes: + - ../.dev/haproxy:/usr/local/etc/haproxy:ro ports: - 25567:25565/tcp + networks: + - infrared redis: image: redis diff --git a/deployments/docker-compose.yml b/deployments/docker-compose.yml new file mode 100644 index 00000000..18c2d387 --- /dev/null +++ b/deployments/docker-compose.yml @@ -0,0 +1,11 @@ +version: "3.8" + +services: + infrared: + image: haveachin/infrared:latest + container_name: infrared + restart: always + ports: + - 25565:25565/tcp + volumes: + - ./data/infrared:/etc/infrared diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index d4d4091d..b47ca5fe 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -1,6 +1,5 @@ import { defineConfig} from 'vitepress' -// https://vitepress.dev/reference/site-config export default defineConfig({ lang: 'en-US', title: 'Infrared', @@ -18,21 +17,21 @@ export default defineConfig({ ], ], themeConfig: { - // https://vitepress.dev/reference/default-theme-config logo: '/assets/logo.svg', nav: [ { - text: 'Guides', + text: 'Features', items: [ - { text: 'Forward Player IPs', link: '/guide/forward-player-ips' }, + { text: 'PROXY Protocol', link: '/features/forward-player-ips' }, + { text: 'Rate Limiter', link: '/features/rate-limit-ips' }, ] }, { text: 'Config', items: [ { text: 'Global', link: '/config/' }, - { text: 'Proxy', link: '/config/proxy' }, + { text: 'Proxies', link: '/config/proxies' }, { text: 'CLI & Env Vars', link: '/config/cli-and-env-vars' }, ] }, @@ -41,6 +40,7 @@ export default defineConfig({ items: [ { text: 'PayPal', link: 'https://paypal.me/hendrikschlehlein' }, { text: 'Ko-Fi', link: 'https://ko-fi.com/haveachin' }, + { text: 'Liberapay', link: 'https://liberapay.com/haveachin' }, ] }, ], @@ -56,9 +56,16 @@ export default defineConfig({ ], }, { - text: 'Guides', + text: 'Features', items: [ - { text: 'Forward Player IPs', link: '/guide/forward-player-ips' }, + { text: 'Forward Player IPs', link: '/features/forward-player-ips' }, + { + text: 'Filters', + link: '/features/filters', + items: [ + { text: 'Rate Limit IPs', link: '/features/rate-limit-ips' }, + ] + } ] }, { text: 'Report an Issue', link: 'https://github.com/haveachin/infrared/issues' }, diff --git a/docs/config/cli-and-env-vars.md b/docs/config/cli-and-env-vars.md index ea36d2eb..5465d1c1 100644 --- a/docs/config/cli-and-env-vars.md +++ b/docs/config/cli-and-env-vars.md @@ -2,18 +2,24 @@ ## Config Path -| Environment Variable | CLI Flag | Default | -|----------------------|--------------------|--------------| -| `INFRARED_CONFIG` | `--config` or `-c` | `config.yml` | +| Environment Variable | CLI Flag | Default | +|----------------------|------------------|--------------| +| `INFRARED_CONFIG` | `--config`, `-c` | `config.yml` | ## Working Directory -| Environment Variable | CLI Flag | Default | -|------------------------|-------------------------|---------| -| `INFRARED_WORKING_DIR` | `--working-dir` or `-w` | `.` | +| Environment Variable | CLI Flag | Default | +|------------------------|-----------------------|---------| +| `INFRARED_WORKING_DIR` | `--working-dir`, `-w` | `.` | ## Proxies Path -| Environment Variable | CLI Flag | Default | -|------------------------|-------------------------|-------------| -| `INFRARED_PROXIES_DIR` | `--proxies-dir` or `-p` | `./proxies` | \ No newline at end of file +| Environment Variable | CLI Flag | Default | +|------------------------|-----------------------|-------------| +| `INFRARED_PROXIES_DIR` | `--proxies-dir`, `-p` | `./proxies` | + +## Log Level + +| Environment Variable | CLI Flag | Default | +|----------------------|---------------------|---------| +| `INFRARED_LOG_LEVEL` | `--log-level`, `-l` | `info` | \ No newline at end of file diff --git a/docs/config/index.md b/docs/config/index.md index 55909108..34d267b6 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -1,26 +1,18 @@ # Config On fist start Infrared should generate a `config.yml` file and a `proxies` directory. -The default config file should look something like this: +Here is a minimal `config.yml` example: -```yml [config.yml] -# Infrared Config +```yml +# Minimal Infrared Config # Address that Infrared bind and listens to # bind: 0.0.0.0:25565 -proxyProtocol: - # Receive proxy protocol - # - #receive: true - - # TODO - # - #trustedProxies: - # - "127.0.0.1" - # Maximum duration between packets before the client gets timed out. # keepAliveTimeout: 30s ``` + +[Complete config example](https://github.com/haveachin/infrared/blob/main/configs/config.yml) \ No newline at end of file diff --git a/docs/config/proxies.md b/docs/config/proxies.md index b5167a67..1fafbf5a 100644 --- a/docs/config/proxies.md +++ b/docs/config/proxies.md @@ -3,7 +3,7 @@ All proxy configs should live in the `proxies` directory. The proxy directory can be changed via the [Proxies Path](cli-and-env-vars#proxies-path) -Proxy config example: +Minimal proxy config example: ```yml [my-server.yml] # This is the domain that players enter in their game client. # You can have multiple domains here or just one. @@ -12,13 +12,10 @@ Proxy config example: # Supports '*' and '?' wildcards in the pattern string. # domains: - - "*" + - "example.com" addresses: - - example.com:25565 + - 127.0.0.1:25565 +``` -# Send a Proxy Protocol v2 Header to the server to -# forward the players IP address -# -#sendProxyProtocol: true -``` \ No newline at end of file +[Complete proxy config example](https://github.com/haveachin/infrared/blob/main/configs/proxy.yml) \ No newline at end of file diff --git a/docs/contribute.md b/docs/contribute.md deleted file mode 100644 index 8bd99fdd..00000000 --- a/docs/contribute.md +++ /dev/null @@ -1 +0,0 @@ -# Contribute \ No newline at end of file diff --git a/docs/features/filters.md b/docs/features/filters.md new file mode 100644 index 00000000..ec8abf6e --- /dev/null +++ b/docs/features/filters.md @@ -0,0 +1,20 @@ +# Filters + +Filter are hooks that trigger befor a connection is processed. +They are used as preconditions to validate a connection. + +## Use Filters + +To use filters you just need to a this to your [**global config**](../config/index.md): + +```yml +# Filter are hooks that trigger befor a connection is processed. +# They are used as preconditions to validate a connection. +# +filters: +``` + +Now you actually need to add filters to your config. +This is a list of all the filters that currently exist: + +- [Rate Limiter](rate-limit-ips) \ No newline at end of file diff --git a/docs/features/forward-player-ips.md b/docs/features/forward-player-ips.md new file mode 100644 index 00000000..7fb6e969 --- /dev/null +++ b/docs/features/forward-player-ips.md @@ -0,0 +1,16 @@ +# Forward Player IPs + +You can forward the player IPs via proxy protocol. +To enable it in Infrared you just have to change this in you [**proxy config**](../config/proxies.md): +```yml +# Send a Proxy Protocol v2 Header to the server to +# forward the players IP address. +# +#sendProxyProtocol: true // [!code --] +sendProxyProtocol: true // [!code ++] +``` + +## Paper + +In Paper you have to enable it also to work. +See [the Paper documentation on Proxy Protocol](https://docs.papermc.io/paper/reference/global-configuration#proxies_proxy_protocol) for more. \ No newline at end of file diff --git a/docs/features/rate-limit-ips.md b/docs/features/rate-limit-ips.md new file mode 100644 index 00000000..15130e29 --- /dev/null +++ b/docs/features/rate-limit-ips.md @@ -0,0 +1,20 @@ +# Rate Limit IPs + +You can rate limit by IP address using the `rateLimit` filter. +This can be easily activated in your [**global config**](../config/index.md) by adding this: + +```yml{2-16} +filters: + # Rate Limiter will only allow an IP address to connect a specified + # amount of times in a given time frame. + # + rateLimiter: + # Request Limit is the amount of times an IP address can create + # a new connection before it gets blocked. + # + requestLimit: 10 + + # Windows Length is the time frame for the Request Limit. + # + windowLength: 1s +``` diff --git a/docs/getting-started.md b/docs/getting-started.md index 98f0b48c..123ceb29 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -75,5 +75,5 @@ services: ports: - 25565:25565/tcp volumes: - - ./data/infrared:/infrared + - ./data/infrared:/etc/infrared ``` diff --git a/docs/guide/forward-player-ips.md b/docs/guide/forward-player-ips.md deleted file mode 100644 index 809e986c..00000000 --- a/docs/guide/forward-player-ips.md +++ /dev/null @@ -1,15 +0,0 @@ -# Forward Player IPs - -You can forward the player IPs via proxy protocol. -To enable it in Infrared you just have to change this in you proxy config: -```yml -# Send a Proxy Protocol v2 Header to the server to -# forward the players IP address -# -#sendProxyProtocol: true // [!code --] -sendProxyProtocol: true // [!code ++] -``` - -## Paper - -In Paper you have to enable it also to work. See [the Paper documentation on Proxy Protocol](https://docs.papermc.io/paper/reference/global-configuration#proxies_proxy_protocol) for more. \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 21ec7cae..e6e66b55 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,11 +1,10 @@ --- -# https://vitepress.dev/reference/default-theme-home-page layout: home hero: name: Infrared - text: A Simple & Powerful Minecraft Proxy - tagline: + text: The Reverse Proxy for Minecraft + tagline: image: src: /assets/logo.svg alt: Infrared @@ -15,16 +14,16 @@ hero: link: /getting-started - theme: alt text: Contribute - link: /contribute + link: https://github.com/haveachin/infrared/blob/main/CONTRIBUTING.md features: - icon: 📖 title: Free and Open Source - details: Free as in speech. - - icon: ⚡ - title: High Performance - details: Written with scale in mind. + details: Infrared is developed as free software to ensure transparency and integrity. + - icon: 🧩 + title: Build as a Library + details: Use Infrared as a library for your projects and extend it's functionallty easily via it's rich API. - icon: 🪶 title: Simple and Lightweight - details: Easy to use on any hardware. + details: Low memory footprint and build for concurrnecy. --- \ No newline at end of file diff --git a/docs/package-lock.json b/docs/package-lock.json index 0e4d18c0..008de618 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -1,11 +1,11 @@ { - "name": "docs2", + "name": "docs", "lockfileVersion": 3, "requires": true, "packages": { "": { "devDependencies": { - "vitepress": "^1.0.0-rc.28" + "vitepress": "^1.0.0-rc.40" } }, "node_modules/@algolia/autocomplete-core": { @@ -183,9 +183,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz", - "integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", + "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -242,10 +242,26 @@ } } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@esbuild/android-arm": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.5.tgz", - "integrity": "sha512-bhvbzWFF3CwMs5tbjf3ObfGqbl/17ict2/uwOSfr3wmxDE6VdS2GqY/FuzIPe0q0bdhj65zQsvqfArI9MY6+AA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", "cpu": [ "arm" ], @@ -259,9 +275,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.5.tgz", - "integrity": "sha512-5d1OkoJxnYQfmC+Zd8NBFjkhyCNYwM4n9ODrycTFY6Jk1IGiZ+tjVJDDSwDt77nK+tfpGP4T50iMtVi4dEGzhQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", "cpu": [ "arm64" ], @@ -275,9 +291,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.5.tgz", - "integrity": "sha512-9t+28jHGL7uBdkBjL90QFxe7DVA+KGqWlHCF8ChTKyaKO//VLuoBricQCgwhOjA1/qOczsw843Fy4cbs4H3DVA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", "cpu": [ "x64" ], @@ -291,9 +307,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.5.tgz", - "integrity": "sha512-mvXGcKqqIqyKoxq26qEDPHJuBYUA5KizJncKOAf9eJQez+L9O+KfvNFu6nl7SCZ/gFb2QPaRqqmG0doSWlgkqw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", "cpu": [ "arm64" ], @@ -307,9 +323,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.5.tgz", - "integrity": "sha512-Ly8cn6fGLNet19s0X4unjcniX24I0RqjPv+kurpXabZYSXGM4Pwpmf85WHJN3lAgB8GSth7s5A0r856S+4DyiA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", "cpu": [ "x64" ], @@ -323,9 +339,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.5.tgz", - "integrity": "sha512-GGDNnPWTmWE+DMchq1W8Sd0mUkL+APvJg3b11klSGUDvRXh70JqLAO56tubmq1s2cgpVCSKYywEiKBfju8JztQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", "cpu": [ "arm64" ], @@ -339,9 +355,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.5.tgz", - "integrity": "sha512-1CCwDHnSSoA0HNwdfoNY0jLfJpd7ygaLAp5EHFos3VWJCRX9DMwWODf96s9TSse39Br7oOTLryRVmBoFwXbuuQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", "cpu": [ "x64" ], @@ -355,9 +371,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.5.tgz", - "integrity": "sha512-lrWXLY/vJBzCPC51QN0HM71uWgIEpGSjSZZADQhq7DKhPcI6NH1IdzjfHkDQws2oNpJKpR13kv7/pFHBbDQDwQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", "cpu": [ "arm" ], @@ -371,9 +387,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.5.tgz", - "integrity": "sha512-o3vYippBmSrjjQUCEEiTZ2l+4yC0pVJD/Dl57WfPwwlvFkrxoSO7rmBZFii6kQB3Wrn/6GwJUPLU5t52eq2meA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", "cpu": [ "arm64" ], @@ -387,9 +403,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.5.tgz", - "integrity": "sha512-MkjHXS03AXAkNp1KKkhSKPOCYztRtK+KXDNkBa6P78F8Bw0ynknCSClO/ztGszILZtyO/lVKpa7MolbBZ6oJtQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", "cpu": [ "ia32" ], @@ -403,9 +419,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.5.tgz", - "integrity": "sha512-42GwZMm5oYOD/JHqHska3Jg0r+XFb/fdZRX+WjADm3nLWLcIsN27YKtqxzQmGNJgu0AyXg4HtcSK9HuOk3v1Dw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", "cpu": [ "loong64" ], @@ -419,9 +435,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.5.tgz", - "integrity": "sha512-kcjndCSMitUuPJobWCnwQ9lLjiLZUR3QLQmlgaBfMX23UEa7ZOrtufnRds+6WZtIS9HdTXqND4yH8NLoVVIkcg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", "cpu": [ "mips64el" ], @@ -435,9 +451,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.5.tgz", - "integrity": "sha512-yJAxJfHVm0ZbsiljbtFFP1BQKLc8kUF6+17tjQ78QjqjAQDnhULWiTA6u0FCDmYT1oOKS9PzZ2z0aBI+Mcyj7Q==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", "cpu": [ "ppc64" ], @@ -451,9 +467,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.5.tgz", - "integrity": "sha512-5u8cIR/t3gaD6ad3wNt1MNRstAZO+aNyBxu2We8X31bA8XUNyamTVQwLDA1SLoPCUehNCymhBhK3Qim1433Zag==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", "cpu": [ "riscv64" ], @@ -467,9 +483,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.5.tgz", - "integrity": "sha512-Z6JrMyEw/EmZBD/OFEFpb+gao9xJ59ATsoTNlj39jVBbXqoZm4Xntu6wVmGPB/OATi1uk/DB+yeDPv2E8PqZGw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", "cpu": [ "s390x" ], @@ -483,9 +499,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.5.tgz", - "integrity": "sha512-psagl+2RlK1z8zWZOmVdImisMtrUxvwereIdyJTmtmHahJTKb64pAcqoPlx6CewPdvGvUKe2Jw+0Z/0qhSbG1A==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", "cpu": [ "x64" ], @@ -499,9 +515,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.5.tgz", - "integrity": "sha512-kL2l+xScnAy/E/3119OggX8SrWyBEcqAh8aOY1gr4gPvw76la2GlD4Ymf832UCVbmuWeTf2adkZDK+h0Z/fB4g==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", "cpu": [ "x64" ], @@ -515,9 +531,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.5.tgz", - "integrity": "sha512-sPOfhtzFufQfTBgRnE1DIJjzsXukKSvZxloZbkJDG383q0awVAq600pc1nfqBcl0ice/WN9p4qLc39WhBShRTA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", "cpu": [ "x64" ], @@ -531,9 +547,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.5.tgz", - "integrity": "sha512-dGZkBXaafuKLpDSjKcB0ax0FL36YXCvJNnztjKV+6CO82tTYVDSH2lifitJ29jxRMoUhgkg9a+VA/B03WK5lcg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", "cpu": [ "x64" ], @@ -547,9 +563,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.5.tgz", - "integrity": "sha512-dWVjD9y03ilhdRQ6Xig1NWNgfLtf2o/STKTS+eZuF90fI2BhbwD6WlaiCGKptlqXlURVB5AUOxUj09LuwKGDTg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", "cpu": [ "arm64" ], @@ -563,9 +579,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.5.tgz", - "integrity": "sha512-4liggWIA4oDgUxqpZwrDhmEfAH4d0iljanDOK7AnVU89T6CzHon/ony8C5LeOdfgx60x5cnQJFZwEydVlYx4iw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", "cpu": [ "ia32" ], @@ -579,9 +595,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.5.tgz", - "integrity": "sha512-czTrygUsB/jlM8qEW5MD8bgYU2Xg14lo6kBDXW6HdxKjh8M5PzETGiSHaz9MtbXBYDloHNUAUW2tMiKW4KM9Mw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", "cpu": [ "x64" ], @@ -601,9 +617,9 @@ "dev": true }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.5.0.tgz", - "integrity": "sha512-OINaBGY+Wc++U0rdr7BLuFClxcoWaVW3vQYqmQq6B3bqQ/2olkaoz+K8+af/Mmka/C2yN5j+L9scBkv4BtKsDA==", + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.6.tgz", + "integrity": "sha512-MVNXSSYN6QXOulbHpLMKYi60ppyO13W9my1qogeiAqtjb2yR4LSmfU2+POvDkLzhjYLXz9Rf9+9a3zFHW1Lecg==", "cpu": [ "arm" ], @@ -614,9 +630,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.5.0.tgz", - "integrity": "sha512-UdMf1pOQc4ZmUA/NTmKhgJTBimbSKnhPS2zJqucqFyBRFPnPDtwA8MzrGNTjDeQbIAWfpJVAlxejw+/lQyBK/w==", + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.6.tgz", + "integrity": "sha512-T14aNLpqJ5wzKNf5jEDpv5zgyIqcpn1MlwCrUXLrwoADr2RkWA0vOWP4XxbO9aiO3dvMCQICZdKeDrFl7UMClw==", "cpu": [ "arm64" ], @@ -627,9 +643,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.5.0.tgz", - "integrity": "sha512-L0/CA5p/idVKI+c9PcAPGorH6CwXn6+J0Ys7Gg1axCbTPgI8MeMlhA6fLM9fK+ssFhqogMHFC8HDvZuetOii7w==", + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.6.tgz", + "integrity": "sha512-CqNNAyhRkTbo8VVZ5R85X73H3R5NX9ONnKbXuHisGWC0qRbTTxnF1U4V9NafzJbgGM0sHZpdO83pLPzq8uOZFw==", "cpu": [ "arm64" ], @@ -640,9 +656,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.5.0.tgz", - "integrity": "sha512-QZCbVqU26mNlLn8zi/XDDquNmvcr4ON5FYAHQQsyhrHx8q+sQi/6xduoznYXwk/KmKIXG5dLfR0CvY+NAWpFYQ==", + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.6.tgz", + "integrity": "sha512-zRDtdJuRvA1dc9Mp6BWYqAsU5oeLixdfUvkTHuiYOHwqYuQ4YgSmi6+/lPvSsqc/I0Omw3DdICx4Tfacdzmhog==", "cpu": [ "x64" ], @@ -653,9 +669,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.5.0.tgz", - "integrity": "sha512-VpSQ+xm93AeV33QbYslgf44wc5eJGYfYitlQzAi3OObu9iwrGXEnmu5S3ilkqE3Pr/FkgOiJKV/2p0ewf4Hrtg==", + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.6.tgz", + "integrity": "sha512-oNk8YXDDnNyG4qlNb6is1ojTOGL/tRhbbKeE/YuccItzerEZT68Z9gHrY3ROh7axDc974+zYAPxK5SH0j/G+QQ==", "cpu": [ "arm" ], @@ -666,9 +682,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.5.0.tgz", - "integrity": "sha512-OrEyIfpxSsMal44JpEVx9AEcGpdBQG1ZuWISAanaQTSMeStBW+oHWwOkoqR54bw3x8heP8gBOyoJiGg+fLY8qQ==", + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.6.tgz", + "integrity": "sha512-Z3O60yxPtuCYobrtzjo0wlmvDdx2qZfeAWTyfOjEDqd08kthDKexLpV97KfAeUXPosENKd8uyJMRDfFMxcYkDQ==", "cpu": [ "arm64" ], @@ -679,9 +695,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.5.0.tgz", - "integrity": "sha512-1H7wBbQuE6igQdxMSTjtFfD+DGAudcYWhp106z/9zBA8OQhsJRnemO4XGavdzHpGhRtRxbgmUGdO3YQgrWf2RA==", + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.6.tgz", + "integrity": "sha512-gpiG0qQJNdYEVad+1iAsGAbgAnZ8j07FapmnIAQgODKcOTjLEWM9sRb+MbQyVsYCnA0Im6M6QIq6ax7liws6eQ==", "cpu": [ "arm64" ], @@ -691,10 +707,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.6.tgz", + "integrity": "sha512-+uCOcvVmFUYvVDr27aiyun9WgZk0tXe7ThuzoUTAukZJOwS5MrGbmSlNOhx1j80GdpqbOty05XqSl5w4dQvcOA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.5.0.tgz", - "integrity": "sha512-FVyFI13tXw5aE65sZdBpNjPVIi4Q5mARnL/39UIkxvSgRAIqCo5sCpCELk0JtXHGee2owZz5aNLbWNfBHzr71Q==", + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.6.tgz", + "integrity": "sha512-HUNqM32dGzfBKuaDUBqFB7tP6VMN74eLZ33Q9Y1TBqRDn+qDonkAUyKWwF9BR9unV7QUzffLnz9GrnKvMqC/fw==", "cpu": [ "x64" ], @@ -705,9 +734,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.5.0.tgz", - "integrity": "sha512-eBPYl2sLpH/o8qbSz6vPwWlDyThnQjJfcDOGFbNjmjb44XKC1F5dQfakOsADRVrXCNzM6ZsSIPDG5dc6HHLNFg==", + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.6.tgz", + "integrity": "sha512-ch7M+9Tr5R4FK40FHQk8VnML0Szi2KRujUgHXd/HjuH9ifH72GUmw6lStZBo3c3GB82vHa0ZoUfjfcM7JiiMrQ==", "cpu": [ "x64" ], @@ -718,9 +747,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.5.0.tgz", - "integrity": "sha512-xaOHIfLOZypoQ5U2I6rEaugS4IYtTgP030xzvrBf5js7p9WI9wik07iHmsKaej8Z83ZDxN5GyypfoyKV5O5TJA==", + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.6.tgz", + "integrity": "sha512-VD6qnR99dhmTQ1mJhIzXsRcTBvTjbfbGGwKAHcu+52cVl15AC/kplkhxzW/uT0Xl62Y/meBKDZvoJSJN+vTeGA==", "cpu": [ "arm64" ], @@ -731,9 +760,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.5.0.tgz", - "integrity": "sha512-Al6quztQUrHwcOoU2TuFblUQ5L+/AmPBXFR6dUvyo4nRj2yQRK0WIUaGMF/uwKulvRcXkpHe3k9A8Vf93VDktA==", + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.6.tgz", + "integrity": "sha512-J9AFDq/xiRI58eR2NIDfyVmTYGyIZmRcvcAoJ48oDld/NTR8wyiPUu2X/v1navJ+N/FGg68LEbX3Ejd6l8B7MQ==", "cpu": [ "ia32" ], @@ -744,9 +773,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.5.0.tgz", - "integrity": "sha512-8kdW+brNhI/NzJ4fxDufuJUjepzINqJKLGHuxyAtpPG9bMbn8P5mtaCcbOm0EzLJ+atg+kF9dwg8jpclkVqx5w==", + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.6.tgz", + "integrity": "sha512-jqzNLhNDvIZOrt69Ce4UjGRpXJBzhUBzawMwnaDAwyHriki3XollsewxWzOzz+4yOFDkuJHtTsZFwMxhYJWmLQ==", "cpu": [ "x64" ], @@ -756,6 +785,12 @@ "win32" ] }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, "node_modules/@types/linkify-it": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.5.tgz", @@ -763,9 +798,9 @@ "dev": true }, "node_modules/@types/markdown-it": { - "version": "13.0.6", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-13.0.6.tgz", - "integrity": "sha512-0VqpvusJn1/lwRegCxcHVdmLfF+wIsprsKMC9xW8UPcTxhFcQtoN/fBU1zMe8pH7D/RuueMh2CaBaNv+GrLqTw==", + "version": "13.0.7", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-13.0.7.tgz", + "integrity": "sha512-U/CBi2YUUcTHBt5tjO2r5QV/x0Po6nsYwQU4Y04fBS6vfoImaiZ6f8bi3CjTCxBPQSO1LMyUqkByzi8AidyxfA==", "dev": true, "dependencies": { "@types/linkify-it": "*", @@ -785,66 +820,66 @@ "dev": true }, "node_modules/@vitejs/plugin-vue": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.5.0.tgz", - "integrity": "sha512-a2WSpP8X8HTEww/U00bU4mX1QpLINNuz/2KMNpLsdu3BzOpak3AGI1CJYBTXcc4SPhaD0eNRUp7IyQK405L5dQ==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.3.tgz", + "integrity": "sha512-b8S5dVS40rgHdDrw+DQi/xOM9ed+kSRZzfm1T74bMmBDCd8XO87NKlFYInzCtwvtWwXZvo1QxE2OSspTATWrbA==", "dev": true, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^18.0.0 || >=20.0.0" }, "peerDependencies": { - "vite": "^4.0.0 || ^5.0.0", + "vite": "^5.0.0", "vue": "^3.2.25" } }, "node_modules/@vue/compiler-core": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.8.tgz", - "integrity": "sha512-hN/NNBUECw8SusQvDSqqcVv6gWq8L6iAktUR0UF3vGu2OhzRqcOiAno0FmBJWwxhYEXRlQJT5XnoKsVq1WZx4g==", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.15.tgz", + "integrity": "sha512-XcJQVOaxTKCnth1vCxEChteGuwG6wqnUHxAm1DO3gCz0+uXKaJNx8/digSz4dLALCy8n2lKq24jSUs8segoqIw==", "dev": true, "dependencies": { - "@babel/parser": "^7.23.0", - "@vue/shared": "3.3.8", + "@babel/parser": "^7.23.6", + "@vue/shared": "3.4.15", + "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.0.2" } }, "node_modules/@vue/compiler-dom": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.8.tgz", - "integrity": "sha512-+PPtv+p/nWDd0AvJu3w8HS0RIm/C6VGBIRe24b9hSyNWOAPEUosFZ5diwawwP8ip5sJ8n0Pe87TNNNHnvjs0FQ==", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.15.tgz", + "integrity": "sha512-wox0aasVV74zoXyblarOM3AZQz/Z+OunYcIHe1OsGclCHt8RsRm04DObjefaI82u6XDzv+qGWZ24tIsRAIi5MQ==", "dev": true, "dependencies": { - "@vue/compiler-core": "3.3.8", - "@vue/shared": "3.3.8" + "@vue/compiler-core": "3.4.15", + "@vue/shared": "3.4.15" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.8.tgz", - "integrity": "sha512-WMzbUrlTjfYF8joyT84HfwwXo+8WPALuPxhy+BZ6R4Aafls+jDBnSz8PDz60uFhuqFbl3HxRfxvDzrUf3THwpA==", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.15.tgz", + "integrity": "sha512-LCn5M6QpkpFsh3GQvs2mJUOAlBQcCco8D60Bcqmf3O3w5a+KWS5GvYbrrJBkgvL1BDnTp+e8q0lXCLgHhKguBA==", "dev": true, "dependencies": { - "@babel/parser": "^7.23.0", - "@vue/compiler-core": "3.3.8", - "@vue/compiler-dom": "3.3.8", - "@vue/compiler-ssr": "3.3.8", - "@vue/reactivity-transform": "3.3.8", - "@vue/shared": "3.3.8", + "@babel/parser": "^7.23.6", + "@vue/compiler-core": "3.4.15", + "@vue/compiler-dom": "3.4.15", + "@vue/compiler-ssr": "3.4.15", + "@vue/shared": "3.4.15", "estree-walker": "^2.0.2", "magic-string": "^0.30.5", - "postcss": "^8.4.31", + "postcss": "^8.4.33", "source-map-js": "^1.0.2" } }, "node_modules/@vue/compiler-ssr": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.8.tgz", - "integrity": "sha512-hXCqQL/15kMVDBuoBYpUnSYT8doDNwsjvm3jTefnXr+ytn294ySnT8NlsFHmTgKNjwpuFy7XVV8yTeLtNl/P6w==", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.15.tgz", + "integrity": "sha512-1jdeQyiGznr8gjFDadVmOJqZiLNSsMa5ZgqavkPZ8O2wjHv0tVuAEsw5hTdUoUW4232vpBbL/wJhzVW/JwY1Uw==", "dev": true, "dependencies": { - "@vue/compiler-dom": "3.3.8", - "@vue/shared": "3.3.8" + "@vue/compiler-dom": "3.4.15", + "@vue/shared": "3.4.15" } }, "node_modules/@vue/devtools-api": { @@ -854,76 +889,63 @@ "dev": true }, "node_modules/@vue/reactivity": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.8.tgz", - "integrity": "sha512-ctLWitmFBu6mtddPyOKpHg8+5ahouoTCRtmAHZAXmolDtuZXfjL2T3OJ6DL6ezBPQB1SmMnpzjiWjCiMYmpIuw==", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.15.tgz", + "integrity": "sha512-55yJh2bsff20K5O84MxSvXKPHHt17I2EomHznvFiJCAZpJTNW8IuLj1xZWMLELRhBK3kkFV/1ErZGHJfah7i7w==", "dev": true, "dependencies": { - "@vue/shared": "3.3.8" - } - }, - "node_modules/@vue/reactivity-transform": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.8.tgz", - "integrity": "sha512-49CvBzmZNtcHua0XJ7GdGifM8GOXoUMOX4dD40Y5DxI3R8OUhMlvf2nvgUAcPxaXiV5MQQ1Nwy09ADpnLQUqRw==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.23.0", - "@vue/compiler-core": "3.3.8", - "@vue/shared": "3.3.8", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.5" + "@vue/shared": "3.4.15" } }, "node_modules/@vue/runtime-core": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.8.tgz", - "integrity": "sha512-qurzOlb6q26KWQ/8IShHkMDOuJkQnQcTIp1sdP4I9MbCf9FJeGVRXJFr2mF+6bXh/3Zjr9TDgURXrsCr9bfjUw==", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.15.tgz", + "integrity": "sha512-6E3by5m6v1AkW0McCeAyhHTw+3y17YCOKG0U0HDKDscV4Hs0kgNT5G+GCHak16jKgcCDHpI9xe5NKb8sdLCLdw==", "dev": true, "dependencies": { - "@vue/reactivity": "3.3.8", - "@vue/shared": "3.3.8" + "@vue/reactivity": "3.4.15", + "@vue/shared": "3.4.15" } }, "node_modules/@vue/runtime-dom": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.8.tgz", - "integrity": "sha512-Noy5yM5UIf9UeFoowBVgghyGGPIDPy1Qlqt0yVsUdAVbqI8eeMSsTqBtauaEoT2UFXUk5S64aWVNJN4MJ2vRdA==", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.15.tgz", + "integrity": "sha512-EVW8D6vfFVq3V/yDKNPBFkZKGMFSvZrUQmx196o/v2tHKdwWdiZjYUBS+0Ez3+ohRyF8Njwy/6FH5gYJ75liUw==", "dev": true, "dependencies": { - "@vue/runtime-core": "3.3.8", - "@vue/shared": "3.3.8", - "csstype": "^3.1.2" + "@vue/runtime-core": "3.4.15", + "@vue/shared": "3.4.15", + "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.8.tgz", - "integrity": "sha512-zVCUw7RFskvPuNlPn/8xISbrf0zTWsTSdYTsUTN1ERGGZGVnRxM2QZ3x1OR32+vwkkCm0IW6HmJ49IsPm7ilLg==", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.15.tgz", + "integrity": "sha512-3HYzaidu9cHjrT+qGUuDhFYvF/j643bHC6uUN9BgM11DVy+pM6ATsG6uPBLnkwOgs7BpJABReLmpL3ZPAsUaqw==", "dev": true, "dependencies": { - "@vue/compiler-ssr": "3.3.8", - "@vue/shared": "3.3.8" + "@vue/compiler-ssr": "3.4.15", + "@vue/shared": "3.4.15" }, "peerDependencies": { - "vue": "3.3.8" + "vue": "3.4.15" } }, "node_modules/@vue/shared": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.8.tgz", - "integrity": "sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw==", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.15.tgz", + "integrity": "sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g==", "dev": true }, "node_modules/@vueuse/core": { - "version": "10.6.1", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.6.1.tgz", - "integrity": "sha512-Pc26IJbqgC9VG1u6VY/xrXXfxD33hnvxBnKrLlA2LJlyHII+BSrRoTPJgGYq7qZOu61itITFUnm6QbacwZ4H8Q==", + "version": "10.7.2", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.7.2.tgz", + "integrity": "sha512-AOyAL2rK0By62Hm+iqQn6Rbu8bfmbgaIMXcE3TSr7BdQ42wnSFlwIdPjInO62onYsEMK/yDMU8C6oGfDAtZ2qQ==", "dev": true, "dependencies": { "@types/web-bluetooth": "^0.0.20", - "@vueuse/metadata": "10.6.1", - "@vueuse/shared": "10.6.1", + "@vueuse/metadata": "10.7.2", + "@vueuse/shared": "10.7.2", "vue-demi": ">=0.14.6" }, "funding": { @@ -957,13 +979,13 @@ } }, "node_modules/@vueuse/integrations": { - "version": "10.6.1", - "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-10.6.1.tgz", - "integrity": "sha512-mPDupuofMJ4DPmtX/FfP1MajmWRzYDv8WSaTCo8LQ5kFznjWgmUQ16ApjYqgMquqffNY6+IRMdMgosLDRZOSZA==", + "version": "10.7.2", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-10.7.2.tgz", + "integrity": "sha512-+u3RLPFedjASs5EKPc69Ge49WNgqeMfSxFn+qrQTzblPXZg6+EFzhjarS5edj2qAf6xQ93f95TUxRwKStXj/sQ==", "dev": true, "dependencies": { - "@vueuse/core": "10.6.1", - "@vueuse/shared": "10.6.1", + "@vueuse/core": "10.7.2", + "@vueuse/shared": "10.7.2", "vue-demi": ">=0.14.6" }, "funding": { @@ -1049,18 +1071,18 @@ } }, "node_modules/@vueuse/metadata": { - "version": "10.6.1", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.6.1.tgz", - "integrity": "sha512-qhdwPI65Bgcj23e5lpGfQsxcy0bMjCAsUGoXkJ7DsoeDUdasbZ2DBa4dinFCOER3lF4gwUv+UD2AlA11zdzMFw==", + "version": "10.7.2", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.7.2.tgz", + "integrity": "sha512-kCWPb4J2KGrwLtn1eJwaJD742u1k5h6v/St5wFe8Quih90+k2a0JP8BS4Zp34XUuJqS2AxFYMb1wjUL8HfhWsQ==", "dev": true, "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@vueuse/shared": { - "version": "10.6.1", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.6.1.tgz", - "integrity": "sha512-TECVDTIedFlL0NUfHWncf3zF9Gc4VfdxfQc8JFwoVZQmxpONhLxFrlm0eHQeidHj4rdTPL3KXJa0TZCk1wnc5Q==", + "version": "10.7.2", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.7.2.tgz", + "integrity": "sha512-qFbXoxS44pi2FkgFjPvF4h7c9oMDutpyBdcJdMYIMg9XyXli2meFMuaKn+UMgsClo//Th6+beeCgqweT/79BVA==", "dev": true, "dependencies": { "vue-demi": ">=0.14.6" @@ -1117,22 +1139,28 @@ "@algolia/transporter": "4.20.0" } }, - "node_modules/ansi-sequence-parser": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", - "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==", - "dev": true - }, "node_modules/csstype": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "dev": true }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/esbuild": { - "version": "0.19.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.5.tgz", - "integrity": "sha512-bUxalY7b1g8vNhQKdB24QDmHeY4V4tw/s6Ak5z+jJX9laP5MoQseTOMemAr0gxssjNcH0MCViG8ONI2kksvfFQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", "dev": true, "hasInstallScript": true, "bin": { @@ -1142,28 +1170,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.19.5", - "@esbuild/android-arm64": "0.19.5", - "@esbuild/android-x64": "0.19.5", - "@esbuild/darwin-arm64": "0.19.5", - "@esbuild/darwin-x64": "0.19.5", - "@esbuild/freebsd-arm64": "0.19.5", - "@esbuild/freebsd-x64": "0.19.5", - "@esbuild/linux-arm": "0.19.5", - "@esbuild/linux-arm64": "0.19.5", - "@esbuild/linux-ia32": "0.19.5", - "@esbuild/linux-loong64": "0.19.5", - "@esbuild/linux-mips64el": "0.19.5", - "@esbuild/linux-ppc64": "0.19.5", - "@esbuild/linux-riscv64": "0.19.5", - "@esbuild/linux-s390x": "0.19.5", - "@esbuild/linux-x64": "0.19.5", - "@esbuild/netbsd-x64": "0.19.5", - "@esbuild/openbsd-x64": "0.19.5", - "@esbuild/sunos-x64": "0.19.5", - "@esbuild/win32-arm64": "0.19.5", - "@esbuild/win32-ia32": "0.19.5", - "@esbuild/win32-x64": "0.19.5" + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" } }, "node_modules/estree-walker": { @@ -1195,12 +1224,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", - "dev": true - }, "node_modules/magic-string": { "version": "0.30.5", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", @@ -1220,20 +1243,11 @@ "dev": true }, "node_modules/minisearch": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-6.2.0.tgz", - "integrity": "sha512-BECkorDF1TY2rGKt9XHdSeP9TP29yUbrAaCh/C03wpyf1vx3uYcP/+8XlMcpTkgoU0rBVnHMAOaP83Rc9Tm+TQ==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-6.3.0.tgz", + "integrity": "sha512-ihFnidEeU8iXzcVHy74dhkxh/dn8Dc08ERl0xwoMMGqp4+LvRSCgicb+zGqWthVokQKvCSxITlh3P08OzdTYCQ==", "dev": true }, - "node_modules/mrmime": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", - "integrity": "sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -1259,9 +1273,9 @@ "dev": true }, "node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "version": "8.4.33", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", + "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", "dev": true, "funding": [ { @@ -1278,7 +1292,7 @@ } ], "dependencies": { - "nanoid": "^3.3.6", + "nanoid": "^3.3.7", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, @@ -1297,10 +1311,13 @@ } }, "node_modules/rollup": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.5.0.tgz", - "integrity": "sha512-41xsWhzxqjMDASCxH5ibw1mXk+3c4TNI2UjKbLxe6iEzrSQnqOzmmK8/3mufCPbzHNJ2e04Fc1ddI35hHy+8zg==", + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.6.tgz", + "integrity": "sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==", "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, "bin": { "rollup": "dist/bin/rollup" }, @@ -1309,18 +1326,19 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.5.0", - "@rollup/rollup-android-arm64": "4.5.0", - "@rollup/rollup-darwin-arm64": "4.5.0", - "@rollup/rollup-darwin-x64": "4.5.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.5.0", - "@rollup/rollup-linux-arm64-gnu": "4.5.0", - "@rollup/rollup-linux-arm64-musl": "4.5.0", - "@rollup/rollup-linux-x64-gnu": "4.5.0", - "@rollup/rollup-linux-x64-musl": "4.5.0", - "@rollup/rollup-win32-arm64-msvc": "4.5.0", - "@rollup/rollup-win32-ia32-msvc": "4.5.0", - "@rollup/rollup-win32-x64-msvc": "4.5.0", + "@rollup/rollup-android-arm-eabi": "4.9.6", + "@rollup/rollup-android-arm64": "4.9.6", + "@rollup/rollup-darwin-arm64": "4.9.6", + "@rollup/rollup-darwin-x64": "4.9.6", + "@rollup/rollup-linux-arm-gnueabihf": "4.9.6", + "@rollup/rollup-linux-arm64-gnu": "4.9.6", + "@rollup/rollup-linux-arm64-musl": "4.9.6", + "@rollup/rollup-linux-riscv64-gnu": "4.9.6", + "@rollup/rollup-linux-x64-gnu": "4.9.6", + "@rollup/rollup-linux-x64-musl": "4.9.6", + "@rollup/rollup-win32-arm64-msvc": "4.9.6", + "@rollup/rollup-win32-ia32-msvc": "4.9.6", + "@rollup/rollup-win32-x64-msvc": "4.9.6", "fsevents": "~2.3.2" } }, @@ -1331,16 +1349,28 @@ "dev": true, "peer": true }, - "node_modules/shiki": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.5.tgz", - "integrity": "sha512-1gCAYOcmCFONmErGTrS1fjzJLA7MGZmKzrBNX7apqSwhyITJg2O102uFzXUeBxNnEkDA9vHIKLyeKq0V083vIw==", + "node_modules/shikiji": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/shikiji/-/shikiji-0.10.2.tgz", + "integrity": "sha512-wtZg3T0vtYV2PnqusWQs3mDaJBdCPWxFDrBM/SE5LfrX92gjUvfEMlc+vJnoKY6Z/S44OWaCRzNIsdBRWcTAiw==", + "dev": true, + "dependencies": { + "shikiji-core": "0.10.2" + } + }, + "node_modules/shikiji-core": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/shikiji-core/-/shikiji-core-0.10.2.tgz", + "integrity": "sha512-9Of8HMlF96usXJHmCL3Gd0Fcf0EcyJUF9m8EoAKKd98mHXi0La2AZl1h6PegSFGtiYcBDK/fLuKbDa1l16r1fA==", + "dev": true + }, + "node_modules/shikiji-transformers": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/shikiji-transformers/-/shikiji-transformers-0.10.2.tgz", + "integrity": "sha512-7IVTwl1af205ywYEq5bOAYOTOFW4V1dVX1EablP0nWKErqZeD1o93VMytxmtJomqS+YwbB8doY8SE3MFMn0aPQ==", "dev": true, "dependencies": { - "ansi-sequence-parser": "^1.1.0", - "jsonc-parser": "^3.2.0", - "vscode-oniguruma": "^1.7.0", - "vscode-textmate": "^8.0.0" + "shikiji": "0.10.2" } }, "node_modules/source-map-js": { @@ -1359,13 +1389,13 @@ "dev": true }, "node_modules/vite": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.0.tgz", - "integrity": "sha512-ESJVM59mdyGpsiNAeHQOR/0fqNoOyWPYesFto8FFZugfmhdHx8Fzd8sF3Q/xkVhZsyOxHfdM7ieiVAorI9RjFw==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", + "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", "dev": true, "dependencies": { "esbuild": "^0.19.3", - "postcss": "^8.4.31", + "postcss": "^8.4.32", "rollup": "^4.2.0" }, "bin": { @@ -1414,32 +1444,33 @@ } }, "node_modules/vitepress": { - "version": "1.0.0-rc.28", - "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.0.0-rc.28.tgz", - "integrity": "sha512-cB0DNhX1jgmyZxPSrH5E+YpgpSlLuDL4ec9UjeqTe/Si1+MEvIJRgifB0RjGfojKa+gkSo97nLO6WN+iFgtgXQ==", + "version": "1.0.0-rc.40", + "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.0.0-rc.40.tgz", + "integrity": "sha512-1x9PCrcsJwqhpccyTR93uD6jpiPDeRC98CBCAQLLBb44a3VSXYBPzhCahi+2kwAYylu49p0XhseMPVM4IVcWcw==", "dev": true, "dependencies": { "@docsearch/css": "^3.5.2", "@docsearch/js": "^3.5.2", - "@types/markdown-it": "^13.0.6", - "@vitejs/plugin-vue": "^4.5.0", + "@types/markdown-it": "^13.0.7", + "@vitejs/plugin-vue": "^5.0.3", "@vue/devtools-api": "^6.5.1", - "@vueuse/core": "^10.6.1", - "@vueuse/integrations": "^10.6.1", + "@vueuse/core": "^10.7.2", + "@vueuse/integrations": "^10.7.2", "focus-trap": "^7.5.4", "mark.js": "8.11.1", - "minisearch": "^6.2.0", - "mrmime": "^1.0.1", - "shiki": "^0.14.5", - "vite": "^5.0.0", - "vue": "^3.3.8" + "minisearch": "^6.3.0", + "shikiji": "^0.10.0", + "shikiji-core": "^0.10.0", + "shikiji-transformers": "^0.10.0", + "vite": "^5.0.12", + "vue": "^3.4.15" }, "bin": { "vitepress": "bin/vitepress.js" }, "peerDependencies": { "markdown-it-mathjax3": "^4.3.2", - "postcss": "^8.4.31" + "postcss": "^8.4.33" }, "peerDependenciesMeta": { "markdown-it-mathjax3": { @@ -1450,29 +1481,17 @@ } } }, - "node_modules/vscode-oniguruma": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", - "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", - "dev": true - }, - "node_modules/vscode-textmate": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", - "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", - "dev": true - }, "node_modules/vue": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.3.8.tgz", - "integrity": "sha512-5VSX/3DabBikOXMsxzlW8JyfeLKlG9mzqnWgLQLty88vdZL7ZJgrdgBOmrArwxiLtmS+lNNpPcBYqrhE6TQW5w==", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.15.tgz", + "integrity": "sha512-jC0GH4KkWLWJOEQjOpkqU1bQsBwf4R1rsFtw5GQJbjHVKWDzO6P0nWWBTmjp1xSemAioDFj1jdaK1qa3DnMQoQ==", "dev": true, "dependencies": { - "@vue/compiler-dom": "3.3.8", - "@vue/compiler-sfc": "3.3.8", - "@vue/runtime-dom": "3.3.8", - "@vue/server-renderer": "3.3.8", - "@vue/shared": "3.3.8" + "@vue/compiler-dom": "3.4.15", + "@vue/compiler-sfc": "3.4.15", + "@vue/runtime-dom": "3.4.15", + "@vue/server-renderer": "3.4.15", + "@vue/shared": "3.4.15" }, "peerDependencies": { "typescript": "*" diff --git a/docs/package.json b/docs/package.json index 4b777749..b9217567 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,6 +1,6 @@ { "devDependencies": { - "vitepress": "^1.0.0-rc.28" + "vitepress": "^1.0.0-rc.40" }, "scripts": { "docs:dev": "vitepress dev", diff --git a/go.mod b/go.mod index 1934a698..13ef75d9 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,19 @@ module github.com/haveachin/infrared -go 1.20 +go 1.21 require ( github.com/IGLOU-EU/go-wildcard v1.0.3 - github.com/cespare/xxhash v1.1.0 - github.com/google/uuid v1.3.0 + github.com/cespare/xxhash/v2 v2.2.0 + github.com/google/uuid v1.6.0 github.com/pires/go-proxyproto v0.7.0 + github.com/rs/zerolog v1.31.0 github.com/spf13/pflag v1.0.5 gopkg.in/yaml.v3 v3.0.1 ) + +require ( + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + golang.org/x/sys v0.12.0 // indirect +) diff --git a/go.sum b/go.sum index aef8b9eb..b531d8ae 100644 --- a/go.sum +++ b/go.sum @@ -1,17 +1,28 @@ github.com/IGLOU-EU/go-wildcard v1.0.3 h1:r8T46+8/9V1STciXJomTWRpPEv4nGJATDbJkdU0Nou0= github.com/IGLOU-EU/go-wildcard v1.0.3/go.mod h1:/qeV4QLmydCbwH0UMQJmXDryrFKJknWi/jjO8IiuQfY= -github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs= github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= +github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/pkg/infrared/config/file.go b/pkg/infrared/config/file.go index 700f11f9..874e949a 100644 --- a/pkg/infrared/config/file.go +++ b/pkg/infrared/config/file.go @@ -34,7 +34,7 @@ func newYamlDecoder() decoder { }) } -// FileProvider reads a config file and returns a populated infrared.Config struct +// FileProvider reads a config file and returns a populated infrared.Config struct. type FileProvider struct { ConfigPath string // Must be a directory @@ -69,7 +69,7 @@ func (p FileProvider) readAndUnmashalConfig(dcr decoder) (ir.Config, error) { defer f.Close() var cfg ir.Config - if err := dcr.Decode(f, &cfg); err != nil { + if err = dcr.Decode(f, &cfg); err != nil { return ir.Config{}, fmt.Errorf("failed to decode file %q: %w", p.ConfigPath, err) } @@ -89,7 +89,7 @@ func loadServerConfigs(dcr decoder, path string) ([]ir.ServerConfig, error) { } paths := make([]string, 0) - if err := filepath.WalkDir(path, walkServerDirFunc(&paths)); err != nil { + if err = filepath.WalkDir(path, walkServerDirFunc(&paths)); err != nil { return nil, err } @@ -139,7 +139,7 @@ func readAndUnmashalServerConfig(dcr decoder, path string) (ir.ServerConfig, err defer f.Close() cfg := ir.ServerConfig{} - if err := dcr.Decode(f, &cfg); err != nil && !errors.Is(err, io.EOF) { + if err = dcr.Decode(f, &cfg); err != nil && !errors.Is(err, io.EOF) { return ir.ServerConfig{}, err } diff --git a/pkg/infrared/conn.go b/pkg/infrared/conn.go index 02018fff..b58ab2e2 100644 --- a/pkg/infrared/conn.go +++ b/pkg/infrared/conn.go @@ -14,13 +14,13 @@ import ( var connPool = sync.Pool{ New: func() any { - return &conn{ + return &Conn{ readPks: [2]protocol.Packet{}, } }, } -type conn struct { +type Conn struct { net.Conn r *bufio.Reader @@ -30,11 +30,18 @@ type conn struct { handshake handshaking.ServerBoundHandshake loginStart login.ServerBoundLoginStart reqDomain ServerDomain - srvReqChan chan<- ServerRequest } -func newConn(c net.Conn) *conn { - conn := connPool.Get().(*conn) +func newConn(c net.Conn) *Conn { + if c == nil { + panic("c cannot be nil") + } + + conn, ok := connPool.Get().(*Conn) + if !ok { + panic("connPool contains other implementations of net.Conn") + } + conn.Conn = c conn.r = bufio.NewReader(c) conn.w = c @@ -43,22 +50,26 @@ func newConn(c net.Conn) *conn { return conn } -func (c *conn) Read(b []byte) (int, error) { - c.SetReadDeadline(time.Now().Add(c.timeout)) +func (c *Conn) Read(b []byte) (int, error) { + if err := c.SetReadDeadline(time.Now().Add(c.timeout)); err != nil { + return 0, err + } return c.r.Read(b) } -func (c *conn) Write(b []byte) (int, error) { - c.SetWriteDeadline(time.Now().Add(c.timeout)) +func (c *Conn) Write(b []byte) (int, error) { + if err := c.SetWriteDeadline(time.Now().Add(c.timeout)); err != nil { + return 0, err + } return c.w.Write(b) } -func (c *conn) ReadPacket(pk *protocol.Packet) error { +func (c *Conn) ReadPacket(pk *protocol.Packet) error { _, err := pk.ReadFrom(c.r) return err } -func (c *conn) ReadPackets(pks ...*protocol.Packet) error { +func (c *Conn) ReadPackets(pks ...*protocol.Packet) error { for i := 0; i < len(pks); i++ { if err := c.ReadPacket(pks[i]); err != nil { return err @@ -67,12 +78,12 @@ func (c *conn) ReadPackets(pks ...*protocol.Packet) error { return nil } -func (c *conn) WritePacket(pk protocol.Packet) error { +func (c *Conn) WritePacket(pk protocol.Packet) error { _, err := pk.WriteTo(c.w) return err } -func (c *conn) WritePackets(pks ...protocol.Packet) error { +func (c *Conn) WritePackets(pks ...protocol.Packet) error { for _, pk := range pks { if err := c.WritePacket(pk); err != nil { return err @@ -81,7 +92,7 @@ func (c *conn) WritePackets(pks ...protocol.Packet) error { return nil } -func (c *conn) ForceClose() error { +func (c *Conn) ForceClose() error { if conn, ok := c.Conn.(*net.TCPConn); ok { if err := conn.SetLinger(0); err != nil { return err diff --git a/pkg/infrared/filter.go b/pkg/infrared/filter.go new file mode 100644 index 00000000..d78cffad --- /dev/null +++ b/pkg/infrared/filter.go @@ -0,0 +1,65 @@ +package infrared + +import ( + "net" +) + +type Filterer interface { + Filter(c net.Conn) error +} + +type FilterFunc func(c net.Conn) error + +func (f FilterFunc) Filter(c net.Conn) error { + return f(c) +} + +type ( + FilterID string +) + +type FilterConfigFunc func(cfg *FiltersConfig) + +func WithFilterConfig(c FiltersConfig) FilterConfigFunc { + return func(cfg *FiltersConfig) { + *cfg = c + } +} + +type FiltersConfig struct { + RateLimiter *RateLimiterConfig `yaml:"rateLimiter"` +} + +type Filter struct { + cfg FiltersConfig + filterers []Filterer +} + +func NewFilter(fns ...FilterConfigFunc) Filter { + var cfg FiltersConfig + for _, fn := range fns { + fn(&cfg) + } + + filterers := make([]Filterer, 0) + + if cfg.RateLimiter != nil { + cfg := cfg.RateLimiter + f := RateLimitByIP(cfg.RequestLimit, cfg.WindowLength) + filterers = append(filterers, f) + } + + return Filter{ + cfg: cfg, + filterers: filterers, + } +} + +func (f Filter) Filter(c net.Conn) error { + for _, f := range f.filterers { + if err := f.Filter(c); err != nil { + return err + } + } + return nil +} diff --git a/pkg/infrared/infrared.go b/pkg/infrared/infrared.go index fad65b3d..cb6adbba 100644 --- a/pkg/infrared/infrared.go +++ b/pkg/infrared/infrared.go @@ -3,7 +3,6 @@ package infrared import ( "errors" "io" - "log" "net" "strings" "sync" @@ -11,11 +10,13 @@ import ( "github.com/haveachin/infrared/pkg/infrared/protocol" "github.com/pires/go-proxyproto" + "github.com/rs/zerolog" ) type Config struct { BindAddr string `yaml:"bind"` ServerConfigs []ServerConfig `yaml:"servers"` + FiltersConfig FiltersConfig `yaml:"filters"` KeepAliveTimeout time.Duration `yaml:"keepAliveTimeout"` } @@ -47,6 +48,12 @@ func DefaultConfig() Config { return Config{ BindAddr: ":25565", KeepAliveTimeout: 30 * time.Second, + FiltersConfig: FiltersConfig{ + RateLimiter: &RateLimiterConfig{ + RequestLimit: 10, + WindowLength: time.Second, + }, + }, } } @@ -64,12 +71,15 @@ func MustConfig(fn func() (Config, error)) Config { } type Infrared struct { + Logger zerolog.Logger + cfg Config l net.Listener - srvs []*Server + sg *ServerGateway + filter Filter bufPool sync.Pool - conns map[net.Addr]*conn + conns map[net.Addr]*Conn } func New(fns ...ConfigFunc) *Infrared { @@ -95,26 +105,36 @@ func NewWithConfig(cfg Config) *Infrared { return &b }, }, - conns: make(map[net.Addr]*conn), + conns: make(map[net.Addr]*Conn), } } func (ir *Infrared) init() error { - log.Printf("Listening on %s", ir.cfg.BindAddr) + ir.Logger.Info(). + Str("bind", ir.cfg.BindAddr). + Msg("Starting listener") + l, err := net.Listen("tcp", ir.cfg.BindAddr) if err != nil { return err } ir.l = l + srvs := make([]*Server, 0) for _, sCfg := range ir.cfg.ServerConfigs { srv, err := NewServer(WithServerConfig(sCfg)) if err != nil { return err } + srvs = append(srvs, srv) + } - ir.srvs = append(ir.srvs, srv) + ir.filter = NewFilter(WithFilterConfig(ir.cfg.FiltersConfig)) + sg, err := NewServerGateway(srvs, nil) + if err != nil { + return err } + ir.sg = sg return nil } @@ -124,47 +144,44 @@ func (ir *Infrared) ListenAndServe() error { return err } - sgInChan := make(chan ServerRequest) - defer close(sgInChan) - - sg := serverGateway{ - Servers: ir.srvs, - requestChan: sgInChan, - } - go sg.listenAndServe() - - return ir.listenAndServe(sgInChan) -} - -func (ir *Infrared) listenAndServe(srvReqChan chan<- ServerRequest) error { for { c, err := ir.l.Accept() if errors.Is(err, net.ErrClosed) { - return nil + return err } else if err != nil { - log.Printf("Error accepting new conn: %s", err) + ir.Logger.Debug(). + Err(err). + Msg("Error accepting new connection") + continue } - go ir.handleNewConn(c, srvReqChan) + go ir.handleNewConn(c) } } -func (ir *Infrared) handleNewConn(c net.Conn, srvReqChan chan<- ServerRequest) { +func (ir *Infrared) handleNewConn(c net.Conn) { + if err := ir.filter.Filter(c); err != nil { + ir.Logger.Debug(). + Err(err). + Msg("Filtered connection") + return + } + conn := newConn(c) defer func() { conn.ForceClose() connPool.Put(conn) }() - conn.srvReqChan = srvReqChan - if err := ir.handleConn(conn); err != nil { - log.Println(err) + ir.Logger.Debug(). + Err(err). + Msg("Error while handling connection") } } -func (ir *Infrared) handleConn(c *conn) error { +func (ir *Infrared) handleConn(c *Conn) error { if err := c.ReadPackets(&c.readPks[0], &c.readPks[1]); err != nil { return err } @@ -183,18 +200,14 @@ func (ir *Infrared) handleConn(c *conn) error { } c.reqDomain = ServerDomain(reqDomain) - respChan := make(chan ServerRequestResponse) - c.srvReqChan <- ServerRequest{ + resp, err := ir.sg.RequestServer(ServerRequest{ Domain: c.reqDomain, IsLogin: c.handshake.IsLoginRequest(), ProtocolVersion: protocol.Version(c.handshake.ProtocolVersion), - ReadPks: c.readPks, - ResponseChan: respChan, - } - - resp := <-respChan - if resp.Err != nil { - return resp.Err + ReadPackets: c.readPks, + }) + if err != nil { + return err } if c.handshake.IsStatusRequest() { @@ -204,7 +217,7 @@ func (ir *Infrared) handleConn(c *conn) error { return ir.handleLogin(c, resp) } -func handleStatus(c *conn, resp ServerRequestResponse) error { +func handleStatus(c *Conn, resp ServerResponse) error { if err := c.WritePacket(resp.StatusResponse); err != nil { return err } @@ -221,7 +234,7 @@ func handleStatus(c *conn, resp ServerRequestResponse) error { return nil } -func (ir *Infrared) handleLogin(c *conn, resp ServerRequestResponse) error { +func (ir *Infrared) handleLogin(c *Conn, resp ServerResponse) error { hsVersion := protocol.Version(c.handshake.ProtocolVersion) if err := c.loginStart.Unmarshal(c.readPks[1], hsVersion); err != nil { return err @@ -232,9 +245,9 @@ func (ir *Infrared) handleLogin(c *conn, resp ServerRequestResponse) error { return ir.handlePipe(c, resp) } -func (ir *Infrared) handlePipe(c *conn, resp ServerRequestResponse) error { +func (ir *Infrared) handlePipe(c *Conn, resp ServerResponse) error { rc := resp.ServerConn - defer rc.ForceClose() + defer rc.Close() if resp.SendProxyProtocol { if err := writeProxyProtocolHeader(c.RemoteAddr(), rc); err != nil { @@ -259,7 +272,7 @@ func (ir *Infrared) handlePipe(c *conn, resp ServerRequestResponse) error { var waitChan chan struct{} select { case <-cClosedChan: - rc.ForceClose() + rc.Close() waitChan = rcClosedChan case <-rcClosedChan: c.ForceClose() @@ -272,16 +285,16 @@ func (ir *Infrared) handlePipe(c *conn, resp ServerRequestResponse) error { } func (ir *Infrared) copy(dst io.WriteCloser, src io.ReadCloser, srcClosedChan chan struct{}) { - b := ir.bufPool.Get().(*[]byte) - defer ir.bufPool.Put(b) - - io.CopyBuffer(dst, src, *b) + _, _ = io.Copy(dst, src) srcClosedChan <- struct{}{} } func writeProxyProtocolHeader(addr net.Addr, rc net.Conn) error { rcAddr := rc.RemoteAddr() - tcpAddr := rcAddr.(*net.TCPAddr) + tcpAddr, ok := rcAddr.(*net.TCPAddr) + if !ok { + panic("not a tcp connection") + } tp := proxyproto.TCPv4 if tcpAddr.IP.To4() == nil { diff --git a/pkg/infrared/infrared_test.go b/pkg/infrared/infrared_test.go index 6efbdb2e..34f204d3 100644 --- a/pkg/infrared/infrared_test.go +++ b/pkg/infrared/infrared_test.go @@ -2,162 +2,43 @@ package infrared import ( "bufio" - "bytes" - "errors" - "io" "net" "testing" - "time" - "github.com/haveachin/infrared/pkg/infrared/protocol" - "github.com/haveachin/infrared/pkg/infrared/protocol/handshaking" - "github.com/haveachin/infrared/pkg/infrared/protocol/status" "github.com/pires/go-proxyproto" ) -type mockServerRequestResponder struct{} - -func (r mockServerRequestResponder) RespondeToServerRequest(req ServerRequest, srv *Server) { - req.ResponseChan <- ServerRequestResponse{} -} - -func BenchmarkHandleConn_Status(b *testing.B) { - var hsStatusPk protocol.Packet - handshaking.ServerBoundHandshake{ - ProtocolVersion: 1337, - ServerAddress: "localhost", - ServerPort: 25565, - NextState: handshaking.StateStatusServerBoundHandshake, - }.Marshal(&hsStatusPk) - var statusPk protocol.Packet - status.ServerBoundRequest{}.Marshal(&statusPk) - var pingPk protocol.Packet - pingPk.Encode(0x01) - - tt := []struct { - name string - pks []protocol.Packet - }{ - { - name: "status_handshake", - pks: []protocol.Packet{ - hsStatusPk, - statusPk, - pingPk, - }, - }, - } - - for _, tc := range tt { - in, out := net.Pipe() - sgInChan := make(chan ServerRequest) - srv, err := NewServer(func(cfg *ServerConfig) { - *cfg = ServerConfig{ - Domains: []ServerDomain{ - "localhost", - }, - } - }) - if err != nil { - b.Error(err) - } - - sg := serverGateway{ - Servers: []*Server{ - srv, - }, - requestChan: sgInChan, - responder: mockServerRequestResponder{}, - } - go func() { - if err := sg.listenAndServe(); err != nil { - b.Error(err) - } - }() - c := newConn(out) - c.srvReqChan = sgInChan - - var buf bytes.Buffer - for _, pk := range tc.pks { - if _, err := pk.WriteTo(&buf); err != nil { - b.Error(err) - } - } - - ir := New() - if err := ir.init(); err != nil { - b.Error(err) - } - - go func() { - b := make([]byte, 0xffff) - for { - _, err := in.Read(b) - if err != nil { - return - } - } - }() - - b.Run(tc.name, func(b *testing.B) { - for i := 0; i < b.N; i++ { - go in.Write(buf.Bytes()) - if err := ir.handleConn(c); err != nil && !errors.Is(err, io.EOF) { - b.Error(err) - } - } - }) - - in.Close() - out.Close() - } -} - -type ProxyProtocolTesterConn struct { +type TestConn struct { net.Conn - c net.Conn -} - -func (c *ProxyProtocolTesterConn) RemoteAddr() net.Addr { - return &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 25565} } -func (c *ProxyProtocolTesterConn) Read(b []byte) (int, error) { - return c.c.Read(b) -} - -func (c *ProxyProtocolTesterConn) Write(b []byte) (int, error) { - return c.c.Write(b) -} - -func (c *ProxyProtocolTesterConn) SetWriteDeadline(t time.Time) error { - return c.c.SetWriteDeadline(t) -} - -func (c *ProxyProtocolTesterConn) SetReadDeadline(t time.Time) error { - return c.c.SetReadDeadline(t) +func (c *TestConn) RemoteAddr() net.Addr { + return &net.TCPAddr{ + IP: net.IPv4(127, 0, 0, 1), + Port: 25565, + } } -func TestProxyProtocolhandlePipe(t *testing.T) { - serverConnIn, serverConnOut := net.Pipe() - _, clientConnOut := net.Pipe() +func TestInfrared_handlePipe_ProxyProtocol(t *testing.T) { + rcIn, rcOut := net.Pipe() + _, cOut := net.Pipe() - clientConn := ProxyProtocolTesterConn{c: clientConnOut} + c := TestConn{Conn: cOut} + rc := TestConn{Conn: rcIn} - ir := New() + srv := New() - testConn := ProxyProtocolTesterConn{c: serverConnIn} - - reqResponse := ServerRequestResponse{ - ServerConn: newConn(&testConn), - SendProxyProtocol: true, - } - - go ir.handlePipe(newConn(&clientConn), reqResponse) + go func() { + resp := ServerResponse{ + ServerConn: newConn(&rc), + SendProxyProtocol: true, + } - bufReader := bufio.NewReader(serverConnOut) - header, err := proxyproto.Read(bufReader) + _ = srv.handlePipe(newConn(&c), resp) + }() + r := bufio.NewReader(rcOut) + header, err := proxyproto.Read(r) if err != nil { t.Fatalf("Unexpected error reading proxy protocol header: %v", err) } @@ -173,31 +54,28 @@ func TestProxyProtocolhandlePipe(t *testing.T) { if header.Version != 2 { t.Fatalf("Unexpected proxy protocol version: %v", header.Version) } - } -func TestNoProxyProtocolhandlePipe(t *testing.T) { - serverConnIn, serverConnOut := net.Pipe() - _, clientConnOut := net.Pipe() - - clientConn := ProxyProtocolTesterConn{c: clientConnOut} +func TestInfrared_handlePipe_NoProxyProtocol(t *testing.T) { + rcIn, rcOut := net.Pipe() + _, cOut := net.Pipe() - ir := New() + c := TestConn{Conn: cOut} + rc := TestConn{Conn: rcIn} - testConn := ProxyProtocolTesterConn{c: serverConnIn} + srv := New() - reqResponse := ServerRequestResponse{ - ServerConn: newConn(&testConn), - SendProxyProtocol: false, - } - - go ir.handlePipe(newConn(&clientConn), reqResponse) + go func() { + resp := ServerResponse{ + ServerConn: newConn(&rc), + SendProxyProtocol: false, + } - bufReader := bufio.NewReader(serverConnOut) - _, err := proxyproto.Read(bufReader) + _ = srv.handlePipe(newConn(&c), resp) + }() - if err == nil { + r := bufio.NewReader(rcOut) + if _, err := proxyproto.Read(r); err == nil { t.Fatal("Expected error reading proxy protocol header, but got nothing") } - } diff --git a/pkg/infrared/protocol/handshaking/serverbound_handshake.go b/pkg/infrared/protocol/handshaking/serverbound_handshake.go index f6410892..58975cdc 100644 --- a/pkg/infrared/protocol/handshaking/serverbound_handshake.go +++ b/pkg/infrared/protocol/handshaking/serverbound_handshake.go @@ -12,9 +12,7 @@ import ( ) const ( - MaxSizeServerBoundHandshake = 1 + 2 + 255*4 + 2 + 1 - - IDServerBoundHandshake int32 = 0x00 + ServerBoundHandshakeID int32 = 0x00 StateStatusServerBoundHandshake = protocol.Byte(1) StateLoginServerBoundHandshake = protocol.Byte(2) @@ -30,9 +28,9 @@ type ServerBoundHandshake struct { NextState protocol.Byte } -func (pk ServerBoundHandshake) Marshal(packet *protocol.Packet) { - packet.Encode( - IDServerBoundHandshake, +func (pk ServerBoundHandshake) Marshal(packet *protocol.Packet) error { + return packet.Encode( + ServerBoundHandshakeID, pk.ProtocolVersion, pk.ServerAddress, pk.ServerPort, @@ -41,7 +39,7 @@ func (pk ServerBoundHandshake) Marshal(packet *protocol.Packet) { } func (pk *ServerBoundHandshake) Unmarshal(packet protocol.Packet) error { - if packet.ID != IDServerBoundHandshake { + if packet.ID != ServerBoundHandshakeID { return protocol.ErrInvalidPacketID } @@ -130,7 +128,9 @@ func (pk *ServerBoundHandshake) UpgradeToRealIP(clientAddr net.Addr, timestamp t addr := string(pk.ServerAddress) addrWithForge := strings.SplitN(addr, SeparatorForge, 3) - addr = fmt.Sprintf("%s///%s///%d", addrWithForge[0], clientAddr.String(), timestamp.Unix()) + if len(addrWithForge) > 0 { + addr = fmt.Sprintf("%s///%s///%d", addrWithForge[0], clientAddr.String(), timestamp.Unix()) + } if len(addrWithForge) > 1 { addr = fmt.Sprintf("%s\x00%s\x00", addr, addrWithForge[1]) diff --git a/pkg/infrared/protocol/handshaking/serverbound_handshake_test.go b/pkg/infrared/protocol/handshaking/serverbound_handshake_test.go index f995ff39..2185a695 100644 --- a/pkg/infrared/protocol/handshaking/serverbound_handshake_test.go +++ b/pkg/infrared/protocol/handshaking/serverbound_handshake_test.go @@ -1,4 +1,4 @@ -package handshaking +package handshaking_test import ( "bytes" @@ -9,44 +9,55 @@ import ( "time" "github.com/haveachin/infrared/pkg/infrared/protocol" + "github.com/haveachin/infrared/pkg/infrared/protocol/handshaking" ) func TestServerBoundHandshake_Marshal(t *testing.T) { tt := []struct { - packet ServerBoundHandshake + packet handshaking.ServerBoundHandshake marshaledPacket protocol.Packet }{ { - packet: ServerBoundHandshake{ + packet: handshaking.ServerBoundHandshake{ ProtocolVersion: 578, - ServerAddress: "spook.space", + ServerAddress: "example.com", ServerPort: 25565, - NextState: StateStatusServerBoundHandshake, + NextState: handshaking.StateStatusServerBoundHandshake, }, marshaledPacket: protocol.Packet{ - ID: 0x00, - Data: []byte{0xC2, 0x04, 0x0B, 0x73, 0x70, 0x6F, 0x6F, 0x6B, 0x2E, 0x73, 0x70, 0x61, 0x63, 0x65, 0x63, 0xDD, 0x01}, + ID: 0x00, + Data: []byte{ + 0xC2, 0x04, // ProtoVerion + 0x0B, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, 0x2E, 0x63, 0x6F, 0x6D, // Server Address + 0x63, 0xDD, // Server Port + 0x01, // Next State + }, }, }, { - packet: ServerBoundHandshake{ + packet: handshaking.ServerBoundHandshake{ ProtocolVersion: 578, ServerAddress: "example.com", ServerPort: 1337, - NextState: StateStatusServerBoundHandshake, + NextState: handshaking.StateStatusServerBoundHandshake, }, marshaledPacket: protocol.Packet{ - ID: 0x00, - Data: []byte{0xC2, 0x04, 0x0B, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, 0x2E, 0x63, 0x6F, 0x6D, 0x05, 0x39, 0x01}, + ID: 0x00, + Data: []byte{ + 0xC2, 0x04, // ProtoVerion + 0x0B, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, 0x2E, 0x63, 0x6F, 0x6D, // Server Address + 0x05, 0x39, // Server Port + 0x01, // Next State + }, }, }, } for _, tc := range tt { var pk protocol.Packet - tc.packet.Marshal(&pk) + _ = tc.packet.Marshal(&pk) - if pk.ID != IDServerBoundHandshake { + if pk.ID != handshaking.ServerBoundHandshakeID { t.Error("invalid packet id") } @@ -59,37 +70,45 @@ func TestServerBoundHandshake_Marshal(t *testing.T) { func TestUnmarshalServerBoundHandshake(t *testing.T) { tt := []struct { packet protocol.Packet - unmarshalledPacket ServerBoundHandshake + unmarshalledPacket handshaking.ServerBoundHandshake }{ { packet: protocol.Packet{ ID: 0x00, - // ProtoVer. | Server Address |Serv. Port | Nxt State - Data: []byte{0xC2, 0x04, 0x0B, 0x73, 0x70, 0x6F, 0x6F, 0x6B, 0x2E, 0x73, 0x70, 0x61, 0x63, 0x65, 0x63, 0xDD, 0x01}, + Data: []byte{ + 0xC2, 0x04, // ProtoVerion + 0x0B, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, 0x2E, 0x63, 0x6F, 0x6D, // Server Address + 0x63, 0xDD, // Server Port + 0x01, // Next State + }, }, - unmarshalledPacket: ServerBoundHandshake{ + unmarshalledPacket: handshaking.ServerBoundHandshake{ ProtocolVersion: 578, - ServerAddress: "spook.space", + ServerAddress: "example.com", ServerPort: 25565, - NextState: StateStatusServerBoundHandshake, + NextState: handshaking.StateStatusServerBoundHandshake, }, }, { packet: protocol.Packet{ ID: 0x00, - // ProtoVer. | Server Address |Serv. Port | Nxt State - Data: []byte{0xC2, 0x04, 0x0B, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, 0x2E, 0x63, 0x6F, 0x6D, 0x05, 0x39, 0x01}, + Data: []byte{ + 0xC2, 0x04, // ProtoVerion + 0x0B, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, 0x2E, 0x63, 0x6F, 0x6D, // Server Address + 0x05, 0x39, // Server Port + 0x01, // Next State + }, }, - unmarshalledPacket: ServerBoundHandshake{ + unmarshalledPacket: handshaking.ServerBoundHandshake{ ProtocolVersion: 578, ServerAddress: "example.com", ServerPort: 1337, - NextState: StateStatusServerBoundHandshake, + NextState: handshaking.StateStatusServerBoundHandshake, }, }, } - var actual ServerBoundHandshake + var actual handshaking.ServerBoundHandshake for _, tc := range tt { err := actual.Unmarshal(tc.packet) if err != nil { @@ -109,18 +128,18 @@ func TestUnmarshalServerBoundHandshake(t *testing.T) { func TestServerBoundHandshake_IsStatusRequest(t *testing.T) { tt := []struct { - handshake ServerBoundHandshake + handshake handshaking.ServerBoundHandshake result bool }{ { - handshake: ServerBoundHandshake{ - NextState: StateStatusServerBoundHandshake, + handshake: handshaking.ServerBoundHandshake{ + NextState: handshaking.StateStatusServerBoundHandshake, }, result: true, }, { - handshake: ServerBoundHandshake{ - NextState: StateLoginServerBoundHandshake, + handshake: handshaking.ServerBoundHandshake{ + NextState: handshaking.StateLoginServerBoundHandshake, }, result: false, }, @@ -135,18 +154,18 @@ func TestServerBoundHandshake_IsStatusRequest(t *testing.T) { func TestServerBoundHandshake_IsLoginRequest(t *testing.T) { tt := []struct { - handshake ServerBoundHandshake + handshake handshaking.ServerBoundHandshake result bool }{ { - handshake: ServerBoundHandshake{ - NextState: StateStatusServerBoundHandshake, + handshake: handshaking.ServerBoundHandshake{ + NextState: handshaking.StateStatusServerBoundHandshake, }, result: false, }, { - handshake: ServerBoundHandshake{ - NextState: StateLoginServerBoundHandshake, + handshake: handshaking.ServerBoundHandshake{ + NextState: handshaking.StateLoginServerBoundHandshake, }, result: true, }, @@ -165,19 +184,19 @@ func TestServerBoundHandshake_IsForgeAddress(t *testing.T) { result bool }{ { - addr: SeparatorForge, + addr: handshaking.SeparatorForge, result: true, }, { - addr: "example.com:1234" + SeparatorForge, + addr: "example.com:1234" + handshaking.SeparatorForge, result: true, }, { - addr: "example.com" + SeparatorForge + "some data", + addr: "example.com" + handshaking.SeparatorForge + "some data", result: true, }, { - addr: "example.com" + SeparatorForge + "some data" + SeparatorRealIP + "more", + addr: "example.com" + handshaking.SeparatorForge + "some data" + handshaking.SeparatorRealIP + "more", result: true, }, { @@ -191,7 +210,7 @@ func TestServerBoundHandshake_IsForgeAddress(t *testing.T) { } for _, tc := range tt { - hs := ServerBoundHandshake{ServerAddress: protocol.String(tc.addr)} + hs := handshaking.ServerBoundHandshake{ServerAddress: protocol.String(tc.addr)} if hs.IsForgeAddress() != tc.result { t.Errorf("%s: got: %v; want: %v", tc.addr, !tc.result, tc.result) } @@ -204,19 +223,19 @@ func TestServerBoundHandshake_IsRealIPAddress(t *testing.T) { result bool }{ { - addr: SeparatorRealIP, + addr: handshaking.SeparatorRealIP, result: true, }, { - addr: "example.com:25565" + SeparatorRealIP, + addr: "example.com:25565" + handshaking.SeparatorRealIP, result: true, }, { - addr: "example.com:1337" + SeparatorRealIP + "some data", + addr: "example.com:1337" + handshaking.SeparatorRealIP + "some data", result: true, }, { - addr: "example.com" + SeparatorForge + "some data" + SeparatorRealIP + "more", + addr: "example.com" + handshaking.SeparatorForge + "some data" + handshaking.SeparatorRealIP + "more", result: true, }, { @@ -230,7 +249,7 @@ func TestServerBoundHandshake_IsRealIPAddress(t *testing.T) { } for _, tc := range tt { - hs := ServerBoundHandshake{ServerAddress: protocol.String(tc.addr)} + hs := handshaking.ServerBoundHandshake{ServerAddress: protocol.String(tc.addr)} if hs.IsRealIPAddress() != tc.result { t.Errorf("%s: got: %v; want: %v", tc.addr, !tc.result, tc.result) } @@ -251,33 +270,33 @@ func TestServerBoundHandshake_ParseServerAddress(t *testing.T) { expectedAddr: "example.com:25565", }, { - addr: SeparatorForge, + addr: handshaking.SeparatorForge, expectedAddr: "", }, { - addr: SeparatorRealIP, + addr: handshaking.SeparatorRealIP, expectedAddr: "", }, { - addr: "example.com" + SeparatorForge, + addr: "example.com" + handshaking.SeparatorForge, expectedAddr: "example.com", }, { - addr: "example.com" + SeparatorForge + "some data", + addr: "example.com" + handshaking.SeparatorForge + "some data", expectedAddr: "example.com", }, { - addr: "example.com:25565" + SeparatorRealIP + "some data", + addr: "example.com:25565" + handshaking.SeparatorRealIP + "some data", expectedAddr: "example.com:25565", }, { - addr: "example.com:1234" + SeparatorForge + "some data" + SeparatorRealIP + "more", + addr: "example.com:1234" + handshaking.SeparatorForge + "some data" + handshaking.SeparatorRealIP + "more", expectedAddr: "example.com:1234", }, } for _, tc := range tt { - hs := ServerBoundHandshake{ServerAddress: protocol.String(tc.addr)} + hs := handshaking.ServerBoundHandshake{ServerAddress: protocol.String(tc.addr)} if hs.ParseServerAddress() != tc.expectedAddr { t.Errorf("got: %v; want: %v", hs.ParseServerAddress(), tc.expectedAddr) } @@ -325,20 +344,24 @@ func TestServerBoundHandshake_UpgradeToRealIP(t *testing.T) { } for _, tc := range tt { - hs := ServerBoundHandshake{ServerAddress: protocol.String(tc.addr)} + hs := handshaking.ServerBoundHandshake{ServerAddress: protocol.String(tc.addr)} hs.UpgradeToRealIP(&tc.clientAddr, tc.timestamp) if hs.ParseServerAddress() != tc.addr { t.Errorf("got: %v; want: %v", hs.ParseServerAddress(), tc.addr) } - realIpSegments := strings.Split(string(hs.ServerAddress), SeparatorRealIP) + realIPSegments := strings.Split(string(hs.ServerAddress), handshaking.SeparatorRealIP) + if len(realIPSegments) < 3 { + t.Error("no real ip to test") + return + } - if realIpSegments[1] != tc.clientAddr.String() { - t.Errorf("got: %v; want: %v", realIpSegments[1], tc.addr) + if realIPSegments[1] != tc.clientAddr.String() { + t.Errorf("got: %v; want: %v", realIPSegments[1], tc.addr) } - unixTimestamp, err := strconv.ParseInt(realIpSegments[2], 10, 64) + unixTimestamp, err := strconv.ParseInt(realIPSegments[2], 10, 64) if err != nil { t.Error(err) } @@ -350,7 +373,7 @@ func TestServerBoundHandshake_UpgradeToRealIP(t *testing.T) { } func BenchmarkHandshakingServerBoundHandshake_Marshal(b *testing.B) { - hsPk := ServerBoundHandshake{ + hsPk := handshaking.ServerBoundHandshake{ ProtocolVersion: 578, ServerAddress: "example.com", ServerPort: 25565, @@ -358,7 +381,7 @@ func BenchmarkHandshakingServerBoundHandshake_Marshal(b *testing.B) { } var pk protocol.Packet - hsPk.Marshal(&pk) + _ = hsPk.Marshal(&pk) for n := 0; n < b.N; n++ { if err := hsPk.Unmarshal(pk); err != nil { diff --git a/pkg/infrared/protocol/login/clientbound_disconnect.go b/pkg/infrared/protocol/login/clientbound_disconnect.go index e3e8b2d5..daeb0403 100644 --- a/pkg/infrared/protocol/login/clientbound_disconnect.go +++ b/pkg/infrared/protocol/login/clientbound_disconnect.go @@ -2,15 +2,15 @@ package login import "github.com/haveachin/infrared/pkg/infrared/protocol" -const IDClientBoundDisconnect int32 = 0x00 +const ClientBoundDisconnectID int32 = 0x00 type ClientBoundDisconnect struct { Reason protocol.Chat } -func (pk ClientBoundDisconnect) Marshal(packet *protocol.Packet) { - packet.Encode( - IDClientBoundDisconnect, +func (pk ClientBoundDisconnect) Marshal(packet *protocol.Packet) error { + return packet.Encode( + ClientBoundDisconnectID, pk.Reason, ) } diff --git a/pkg/infrared/protocol/login/clientbound_disconnect_test.go b/pkg/infrared/protocol/login/clientbound_disconnect_test.go index d148c7c0..c8a3608d 100644 --- a/pkg/infrared/protocol/login/clientbound_disconnect_test.go +++ b/pkg/infrared/protocol/login/clientbound_disconnect_test.go @@ -1,19 +1,20 @@ -package login +package login_test import ( "bytes" "testing" "github.com/haveachin/infrared/pkg/infrared/protocol" + "github.com/haveachin/infrared/pkg/infrared/protocol/login" ) func TestClientBoundDisconnect_Marshal(t *testing.T) { tt := []struct { - packet ClientBoundDisconnect + packet login.ClientBoundDisconnect marshaledPacket protocol.Packet }{ { - packet: ClientBoundDisconnect{ + packet: login.ClientBoundDisconnect{ Reason: protocol.Chat(""), }, marshaledPacket: protocol.Packet{ @@ -22,7 +23,7 @@ func TestClientBoundDisconnect_Marshal(t *testing.T) { }, }, { - packet: ClientBoundDisconnect{ + packet: login.ClientBoundDisconnect{ Reason: protocol.Chat("Hello, World!"), }, marshaledPacket: protocol.Packet{ @@ -34,9 +35,9 @@ func TestClientBoundDisconnect_Marshal(t *testing.T) { var pk protocol.Packet for _, tc := range tt { - tc.packet.Marshal(&pk) + _ = tc.packet.Marshal(&pk) - if pk.ID != IDClientBoundDisconnect { + if pk.ID != login.ClientBoundDisconnectID { t.Error("invalid packet id") } diff --git a/pkg/infrared/protocol/login/clientbound_encryptionrequest.go b/pkg/infrared/protocol/login/clientbound_encryptionrequest.go index e6a6d0fe..3c431ce1 100644 --- a/pkg/infrared/protocol/login/clientbound_encryptionrequest.go +++ b/pkg/infrared/protocol/login/clientbound_encryptionrequest.go @@ -2,9 +2,7 @@ package login import "github.com/haveachin/infrared/pkg/infrared/protocol" -const ( - IDClientBoundEncryptionRequest int32 = 0x01 -) +const ClientBoundEncryptionRequestID int32 = 0x01 type ClientBoundEncryptionRequest struct { ServerID protocol.String @@ -12,9 +10,9 @@ type ClientBoundEncryptionRequest struct { VerifyToken protocol.ByteArray } -func (pk ClientBoundEncryptionRequest) Marshal(packet *protocol.Packet) { - packet.Encode( - IDClientBoundEncryptionRequest, +func (pk ClientBoundEncryptionRequest) Marshal(packet *protocol.Packet) error { + return packet.Encode( + ClientBoundEncryptionRequestID, pk.ServerID, pk.PublicKey, pk.VerifyToken, @@ -22,7 +20,7 @@ func (pk ClientBoundEncryptionRequest) Marshal(packet *protocol.Packet) { } func (pk ClientBoundEncryptionRequest) Unmarshal(packet protocol.Packet) error { - if packet.ID != IDClientBoundEncryptionRequest { + if packet.ID != ClientBoundEncryptionRequestID { return protocol.ErrInvalidPacketID } diff --git a/pkg/infrared/protocol/login/serverbound_encryptionresponse.go b/pkg/infrared/protocol/login/serverbound_encryptionresponse.go index cc577913..96cf08a9 100644 --- a/pkg/infrared/protocol/login/serverbound_encryptionresponse.go +++ b/pkg/infrared/protocol/login/serverbound_encryptionresponse.go @@ -2,26 +2,23 @@ package login import "github.com/haveachin/infrared/pkg/infrared/protocol" -const ( - IDServerBoundEncryptionResponse int32 = 0x01 - MaxSizeServerBoundEncryptionResponse = 1 + 5 + 128 + 5 + 128 -) +const ServerBoundEncryptionResponseID int32 = 0x01 type ServerBoundEncryptionResponse struct { SharedSecret protocol.ByteArray VerifyToken protocol.ByteArray } -func (pk ServerBoundEncryptionResponse) Marshal(packet *protocol.Packet) { - packet.Encode( - IDServerBoundEncryptionResponse, +func (pk ServerBoundEncryptionResponse) Marshal(packet *protocol.Packet) error { + return packet.Encode( + ServerBoundEncryptionResponseID, pk.SharedSecret, pk.VerifyToken, ) } func (pk *ServerBoundEncryptionResponse) Unmarshal(packet protocol.Packet) error { - if packet.ID != IDServerBoundEncryptionResponse { + if packet.ID != ServerBoundEncryptionResponseID { return protocol.ErrInvalidPacketID } diff --git a/pkg/infrared/protocol/login/serverbound_loginstart.go b/pkg/infrared/protocol/login/serverbound_loginstart.go index f296972c..2f9ad5a4 100644 --- a/pkg/infrared/protocol/login/serverbound_loginstart.go +++ b/pkg/infrared/protocol/login/serverbound_loginstart.go @@ -6,11 +6,7 @@ import ( "github.com/haveachin/infrared/pkg/infrared/protocol" ) -const ( - // MaxSizeServerBoundLoginStart might be a bit generous, but there is no offical max size for the public key - MaxSizeServerBoundLoginStart = 1 + 16*4 + 1 + 8 + 3000 + 3000 + 1 + 16 - IDServerBoundLoginStart int32 = 0x00 -) +const ServerBoundLoginStartID int32 = 0x00 type ServerBoundLoginStart struct { Name protocol.String @@ -26,36 +22,37 @@ type ServerBoundLoginStart struct { PlayerUUID protocol.UUID } -func (pk ServerBoundLoginStart) Marshal(packet *protocol.Packet, version protocol.Version) { +func (pk ServerBoundLoginStart) Marshal(packet *protocol.Packet, version protocol.Version) error { fields := make([]protocol.FieldEncoder, 0, 7) fields = append(fields, pk.Name) switch { - case version >= protocol.Version_1_19 && - version < protocol.Version_1_19_3: + case version >= protocol.Version1_19 && + version < protocol.Version1_19_3: fields = append(fields, pk.HasSignature) if pk.HasSignature { fields = append(fields, pk.Timestamp, pk.PublicKey, pk.Signature) } fallthrough - case version >= protocol.Version_1_19_3 && - version < protocol.Version_1_20_2: + case version >= protocol.Version1_19_3 && + version < protocol.Version1_20_2: fields = append(fields, pk.HasPlayerUUID) if pk.HasPlayerUUID { fields = append(fields, pk.PlayerUUID) } - case version >= protocol.Version_1_20_2: + case version >= protocol.Version1_20_2: fields = append(fields, pk.PlayerUUID) } - packet.Encode( - IDServerBoundLoginStart, + return packet.Encode( + ServerBoundLoginStartID, fields..., ) } +//nolint:gocognit func (pk *ServerBoundLoginStart) Unmarshal(packet protocol.Packet, version protocol.Version) error { - if packet.ID != IDServerBoundLoginStart { + if packet.ID != ServerBoundLoginStartID { return protocol.ErrInvalidPacketID } @@ -65,8 +62,8 @@ func (pk *ServerBoundLoginStart) Unmarshal(packet protocol.Packet, version proto } switch { - case version >= protocol.Version_1_19 && - version < protocol.Version_1_19_3: + case version >= protocol.Version1_19 && + version < protocol.Version1_19_3: if err := protocol.ScanFields(r, &pk.HasSignature); err != nil { return err } @@ -77,8 +74,8 @@ func (pk *ServerBoundLoginStart) Unmarshal(packet protocol.Packet, version proto } } fallthrough - case version >= protocol.Version_1_19_3 && - version < protocol.Version_1_20_2: + case version >= protocol.Version1_19_3 && + version < protocol.Version1_20_2: if err := protocol.ScanFields(r, &pk.HasPlayerUUID); err != nil { return err } @@ -88,7 +85,7 @@ func (pk *ServerBoundLoginStart) Unmarshal(packet protocol.Packet, version proto return err } } - case version >= protocol.Version_1_20_2: + case version >= protocol.Version1_20_2: if err := protocol.ScanFields(r, &pk.PlayerUUID); err != nil { return err } diff --git a/pkg/infrared/protocol/login/serverbound_loginstart_test.go b/pkg/infrared/protocol/login/serverbound_loginstart_test.go index 21b4e7c2..afb55d3d 100644 --- a/pkg/infrared/protocol/login/serverbound_loginstart_test.go +++ b/pkg/infrared/protocol/login/serverbound_loginstart_test.go @@ -1,24 +1,25 @@ -package login +package login_test import ( "testing" "github.com/haveachin/infrared/pkg/infrared/protocol" + "github.com/haveachin/infrared/pkg/infrared/protocol/login" ) func TestUnmarshalServerBoundLoginStart(t *testing.T) { tt := []struct { packet protocol.Packet version protocol.Version - unmarshalledPacket ServerBoundLoginStart + unmarshalledPacket login.ServerBoundLoginStart }{ { packet: protocol.Packet{ ID: 0x00, Data: []byte{0x00}, }, - version: protocol.Version_1_18_2, - unmarshalledPacket: ServerBoundLoginStart{ + version: protocol.Version1_18_2, + unmarshalledPacket: login.ServerBoundLoginStart{ Name: protocol.String(""), }, }, @@ -27,14 +28,14 @@ func TestUnmarshalServerBoundLoginStart(t *testing.T) { ID: 0x00, Data: []byte{0x0d, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x21}, }, - version: protocol.Version_1_18_2, - unmarshalledPacket: ServerBoundLoginStart{ + version: protocol.Version1_18_2, + unmarshalledPacket: login.ServerBoundLoginStart{ Name: protocol.String("Hello, World!"), }, }, } - var loginStart ServerBoundLoginStart + var loginStart login.ServerBoundLoginStart for _, tc := range tt { if err := loginStart.Unmarshal(tc.packet, tc.version); err != nil { t.Error(err) diff --git a/pkg/infrared/protocol/packet.go b/pkg/infrared/protocol/packet.go index 4ecd2f35..79f16cd7 100644 --- a/pkg/infrared/protocol/packet.go +++ b/pkg/infrared/protocol/packet.go @@ -28,13 +28,16 @@ func ScanFields(r io.Reader, fields ...FieldDecoder) error { return nil } -func (pk *Packet) Encode(id int32, fields ...FieldEncoder) { +func (pk *Packet) Encode(id int32, fields ...FieldEncoder) error { buf := bytes.NewBuffer(pk.Data[:0]) for _, f := range fields { - f.WriteTo(buf) + if _, err := f.WriteTo(buf); err != nil { + return err + } } pk.ID = id pk.Data = buf.Bytes() + return nil } func (pk Packet) WriteTo(w io.Writer) (int64, error) { @@ -51,10 +54,12 @@ func (pk Packet) WriteTo(w io.Writer) (int64, error) { return n, err } - nData, err := w.Write(pk.Data) - n += int64(nData) - if err != nil { - return n, err + if len(pk.Data) > 0 { + nData, err := w.Write(pk.Data) + n += int64(nData) + if err != nil { + return n, err + } } return n, err @@ -62,11 +67,10 @@ func (pk Packet) WriteTo(w io.Writer) (int64, error) { func (pk *Packet) ReadFrom(r io.Reader) (int64, error) { var pkLen VarInt - nLen, err := pkLen.ReadFrom(r) + n, err := pkLen.ReadFrom(r) if err != nil { - return nLen, err + return n, err } - n := nLen var pkID VarInt nID, err := pkID.ReadFrom(r) diff --git a/pkg/infrared/protocol/peeker.go b/pkg/infrared/protocol/peeker.go index 62760c10..ac253553 100644 --- a/pkg/infrared/protocol/peeker.go +++ b/pkg/infrared/protocol/peeker.go @@ -7,34 +7,34 @@ type PeekReader interface { io.Reader } -type bytePeeker struct { +type BytePeeker struct { PeekReader - cursor int + Cursor int } -func (peeker *bytePeeker) Read(b []byte) (int, error) { - buf, err := peeker.Peek(len(b) + peeker.cursor) +func (p *BytePeeker) Read(b []byte) (int, error) { + buf, err := p.Peek(len(b) + p.Cursor) if err != nil { return 0, err } for i := 0; i < len(b); i++ { - b[i] = buf[i+peeker.cursor] + b[i] = buf[i+p.Cursor] } - peeker.cursor += len(b) + p.Cursor += len(b) return len(b), nil } -func (peeker *bytePeeker) ReadByte() (byte, error) { - buf, err := peeker.Peek(1 + peeker.cursor) +func (p *BytePeeker) ReadByte() (byte, error) { + buf, err := p.Peek(1 + p.Cursor) if err != nil { return 0x00, err } - b := buf[peeker.cursor] - peeker.cursor++ + b := buf[p.Cursor] + p.Cursor++ return b, nil } diff --git a/pkg/infrared/protocol/peeker_test.go b/pkg/infrared/protocol/peeker_test.go index aeffd542..05e82d08 100644 --- a/pkg/infrared/protocol/peeker_test.go +++ b/pkg/infrared/protocol/peeker_test.go @@ -1,35 +1,38 @@ -package protocol +package protocol_test import ( "bufio" "bytes" + "errors" "io" "testing" + + "github.com/haveachin/infrared/pkg/infrared/protocol" ) func TestBytePeeker_ReadByte(t *testing.T) { tt := []struct { - peeker bytePeeker + peeker protocol.BytePeeker data []byte expectedByte byte }{ { - peeker: bytePeeker{ - cursor: 0, + peeker: protocol.BytePeeker{ + Cursor: 0, }, data: []byte{0x00, 0x01, 0x02, 0x03}, expectedByte: 0x00, }, { - peeker: bytePeeker{ - cursor: 1, + peeker: protocol.BytePeeker{ + Cursor: 1, }, data: []byte{0x00, 0x01, 0x02, 0x03}, expectedByte: 0x01, }, { - peeker: bytePeeker{ - cursor: 3, + peeker: protocol.BytePeeker{ + Cursor: 3, }, data: []byte{0x00, 0x01, 0x02, 0x03}, expectedByte: 0x03, @@ -44,7 +47,7 @@ func TestBytePeeker_ReadByte(t *testing.T) { // Act b, err := tc.peeker.ReadByte() - if err != nil && err != io.EOF { + if err != nil && errors.Is(err, io.EOF) { t.Error(err) } @@ -61,30 +64,30 @@ func TestBytePeeker_ReadByte(t *testing.T) { func TestBytePeeker_Read(t *testing.T) { tt := []struct { - peeker bytePeeker + peeker protocol.BytePeeker data []byte expectedData []byte expectedN int }{ { - peeker: bytePeeker{ - cursor: 0, + peeker: protocol.BytePeeker{ + Cursor: 0, }, data: []byte{0x00, 0x01, 0x02, 0x03}, expectedData: []byte{0x00, 0x01, 0x02, 0x03}, expectedN: 4, }, { - peeker: bytePeeker{ - cursor: 1, + peeker: protocol.BytePeeker{ + Cursor: 1, }, data: []byte{0x00, 0x01, 0x02, 0x03}, expectedData: []byte{0x01, 0x02, 0x03}, expectedN: 3, }, { - peeker: bytePeeker{ - cursor: 3, + peeker: protocol.BytePeeker{ + Cursor: 3, }, data: []byte{0x00, 0x01, 0x02, 0x03}, expectedData: []byte{0x03}, @@ -101,7 +104,7 @@ func TestBytePeeker_Read(t *testing.T) { // Act n, err := tc.peeker.Read(resultData) - if err != nil && err != io.EOF { + if err != nil && errors.Is(err, io.EOF) { t.Error(err) } diff --git a/pkg/infrared/protocol/play/clientbound_disconnect.go b/pkg/infrared/protocol/play/clientbound_disconnect.go index 9cac3c68..210964b9 100644 --- a/pkg/infrared/protocol/play/clientbound_disconnect.go +++ b/pkg/infrared/protocol/play/clientbound_disconnect.go @@ -2,15 +2,15 @@ package play import "github.com/haveachin/infrared/pkg/infrared/protocol" -const IDClientBoundDisconnect int32 = 0x17 +const ClientBoundDisconnectID int32 = 0x17 type ClientBoundDisconnect struct { Reason protocol.Chat } -func (pk ClientBoundDisconnect) Marshal(packet *protocol.Packet) { - packet.Encode( - IDClientBoundDisconnect, +func (pk ClientBoundDisconnect) Marshal(packet *protocol.Packet) error { + return packet.Encode( + ClientBoundDisconnectID, pk.Reason, ) } diff --git a/pkg/infrared/protocol/status/clientbound_response.go b/pkg/infrared/protocol/status/clientbound_response.go index f1f45bfb..a3c223ce 100644 --- a/pkg/infrared/protocol/status/clientbound_response.go +++ b/pkg/infrared/protocol/status/clientbound_response.go @@ -3,23 +3,22 @@ package status import "github.com/haveachin/infrared/pkg/infrared/protocol" const ( - MaxSizeClientBoundResponse = 1 + 32767*4 - IDClientBoundResponse int32 = 0x00 + ClientBoundResponseID int32 = 0x00 ) type ClientBoundResponse struct { JSONResponse protocol.String } -func (pk ClientBoundResponse) Marshal(packet *protocol.Packet) { - packet.Encode( - IDClientBoundResponse, +func (pk ClientBoundResponse) Marshal(packet *protocol.Packet) error { + return packet.Encode( + ClientBoundResponseID, pk.JSONResponse, ) } func (pk *ClientBoundResponse) Unmarshal(packet protocol.Packet) error { - if packet.ID != IDClientBoundResponse { + if packet.ID != ClientBoundResponseID { return protocol.ErrInvalidPacketID } @@ -68,7 +67,7 @@ type DescriptionJSON struct { Text string `json:"text"` } -// FMLModInfoJSON is a part of the FML Server List Ping +// FMLModInfoJSON is a part of the FML Server List Ping. type FMLModInfoJSON struct { LoaderType string `json:"type"` ModList []FMLModJSON `json:"modList"` @@ -79,7 +78,7 @@ type FMLModJSON struct { Version string `json:"version"` } -// FML2ForgeDataJSON is a part of the FML2 Server List Ping +// FML2ForgeDataJSON is a part of the FML2 Server List Ping. type FML2ForgeDataJSON struct { Channels []FML2ChannelsJSON `json:"channels"` Mods []FML2ModJSON `json:"mods"` diff --git a/pkg/infrared/protocol/status/clientbound_response_test.go b/pkg/infrared/protocol/status/clientbound_response_test.go index de61947d..6f2d7b87 100644 --- a/pkg/infrared/protocol/status/clientbound_response_test.go +++ b/pkg/infrared/protocol/status/clientbound_response_test.go @@ -1,19 +1,20 @@ -package status +package status_test import ( "bytes" "testing" "github.com/haveachin/infrared/pkg/infrared/protocol" + "github.com/haveachin/infrared/pkg/infrared/protocol/status" ) func TestClientBoundResponse_Marshal(t *testing.T) { tt := []struct { - packet ClientBoundResponse + packet status.ClientBoundResponse marshaledPacket protocol.Packet }{ { - packet: ClientBoundResponse{ + packet: status.ClientBoundResponse{ JSONResponse: protocol.String(""), }, marshaledPacket: protocol.Packet{ @@ -22,7 +23,7 @@ func TestClientBoundResponse_Marshal(t *testing.T) { }, }, { - packet: ClientBoundResponse{ + packet: status.ClientBoundResponse{ JSONResponse: protocol.String("Hello, World!"), }, marshaledPacket: protocol.Packet{ @@ -34,9 +35,9 @@ func TestClientBoundResponse_Marshal(t *testing.T) { var pk protocol.Packet for _, tc := range tt { - tc.packet.Marshal(&pk) + _ = tc.packet.Marshal(&pk) - if pk.ID != IDClientBoundResponse { + if pk.ID != status.ClientBoundResponseID { t.Error("invalid packet id") } @@ -49,14 +50,14 @@ func TestClientBoundResponse_Marshal(t *testing.T) { func TestUnmarshalClientBoundResponse(t *testing.T) { tt := []struct { packet protocol.Packet - unmarshalledPacket ClientBoundResponse + unmarshalledPacket status.ClientBoundResponse }{ { packet: protocol.Packet{ ID: 0x00, Data: []byte{0x00}, }, - unmarshalledPacket: ClientBoundResponse{ + unmarshalledPacket: status.ClientBoundResponse{ JSONResponse: "", }, }, @@ -65,13 +66,13 @@ func TestUnmarshalClientBoundResponse(t *testing.T) { ID: 0x00, Data: []byte{0x0d, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x21}, }, - unmarshalledPacket: ClientBoundResponse{ + unmarshalledPacket: status.ClientBoundResponse{ JSONResponse: protocol.String("Hello, World!"), }, }, } - var actual ClientBoundResponse + var actual status.ClientBoundResponse for _, tc := range tt { if err := actual.Unmarshal(tc.packet); err != nil { t.Error(err) @@ -83,5 +84,4 @@ func TestUnmarshalClientBoundResponse(t *testing.T) { t.Errorf("got: %v, want: %v", actual, expected) } } - } diff --git a/pkg/infrared/protocol/status/serverbound_ping_request.go b/pkg/infrared/protocol/status/serverbound_ping_request.go deleted file mode 100644 index 7b86df3b..00000000 --- a/pkg/infrared/protocol/status/serverbound_ping_request.go +++ /dev/null @@ -1,4 +0,0 @@ -package status - -const MaxSizeServerBoundPingRequest = 1 + 8 -const MaxSizeClientBoundPingRequest = 1 + 8 diff --git a/pkg/infrared/protocol/status/serverbound_request.go b/pkg/infrared/protocol/status/serverbound_request.go index 7fa9f405..3b5eb315 100644 --- a/pkg/infrared/protocol/status/serverbound_request.go +++ b/pkg/infrared/protocol/status/serverbound_request.go @@ -2,12 +2,12 @@ package status import "github.com/haveachin/infrared/pkg/infrared/protocol" -const IDServerBoundRequest int32 = 0x00 +const ServerBoundRequestID int32 = 0x00 type ServerBoundRequest struct{} -func (pk ServerBoundRequest) Marshal(packet *protocol.Packet) { - packet.Encode( - IDServerBoundRequest, +func (pk ServerBoundRequest) Marshal(packet *protocol.Packet) error { + return packet.Encode( + ServerBoundRequestID, ) } diff --git a/pkg/infrared/protocol/status/serverbound_request_test.go b/pkg/infrared/protocol/status/serverbound_request_test.go index 8e9450aa..eb6851b8 100644 --- a/pkg/infrared/protocol/status/serverbound_request_test.go +++ b/pkg/infrared/protocol/status/serverbound_request_test.go @@ -1,18 +1,19 @@ -package status +package status_test import ( "testing" "github.com/haveachin/infrared/pkg/infrared/protocol" + "github.com/haveachin/infrared/pkg/infrared/protocol/status" ) func TestServerBoundRequest_Marshal(t *testing.T) { tt := []struct { - packet ServerBoundRequest + packet status.ServerBoundRequest marshaledPacket protocol.Packet }{ { - packet: ServerBoundRequest{}, + packet: status.ServerBoundRequest{}, marshaledPacket: protocol.Packet{ ID: 0x00, Data: []byte{}, @@ -22,9 +23,9 @@ func TestServerBoundRequest_Marshal(t *testing.T) { var pk protocol.Packet for _, tc := range tt { - tc.packet.Marshal(&pk) + _ = tc.packet.Marshal(&pk) - if pk.ID != IDServerBoundRequest { + if pk.ID != status.ServerBoundRequestID { t.Error("invalid packet id") } } diff --git a/pkg/infrared/protocol/types.go b/pkg/infrared/protocol/types.go index 2ab3621d..4c0eb6c7 100644 --- a/pkg/infrared/protocol/types.go +++ b/pkg/infrared/protocol/types.go @@ -7,7 +7,7 @@ import ( "github.com/google/uuid" ) -// A Field is both FieldEncoder and FieldDecoder +// A Field is both FieldEncoder and FieldDecoder. type Field interface { FieldEncoder FieldDecoder @@ -16,31 +16,31 @@ type Field interface { // A FieldEncoder can be encoded as minecraft protocol used. type FieldEncoder io.WriterTo -// A FieldDecoder can Decode from minecraft protocol +// A FieldDecoder can Decode from minecraft protocol. type FieldDecoder io.ReaderFrom type ( // Boolean of True is encoded as 0x01, false as 0x00. Boolean bool - // Byte is signed 8-bit integer, two's complement + // Byte is signed 8-bit integer, two's complement. Byte int8 - // UnsignedShort is unsigned 16-bit integer + // UnsignedShort is unsigned 16-bit integer. UnsignedShort uint16 - // Long is signed 64-bit integer, two's complement + // Long is signed 64-bit integer, two's complement. Long int64 - // String is sequence of Unicode scalar values + // String is sequence of Unicode scalar values. String string // Chat is encoded as a String with max length of 32767. Chat = String - // VarInt is variable-length data encoding a two's complement signed 32-bit integer + // VarInt is variable-length data encoding a two's complement signed 32-bit integer. VarInt int32 - // UUID encoded as an unsigned 128-bit integer + // UUID encoded as an unsigned 128-bit integer. UUID uuid.UUID - // ByteArray is []byte with prefix VarInt as length + // ByteArray is []byte with prefix VarInt as length. ByteArray []byte ) @@ -60,7 +60,7 @@ func (b Boolean) WriteTo(w io.Writer) (int64, error) { return int64(nn), err } -func (b *Boolean) ReadFrom(r io.Reader) (n int64, err error) { +func (b *Boolean) ReadFrom(r io.Reader) (int64, error) { n, v, err := readByte(r) if err != nil { return n, err @@ -80,14 +80,14 @@ func (s String) WriteTo(w io.Writer) (int64, error) { return n1 + int64(n2), err } -func (s *String) ReadFrom(r io.Reader) (n int64, err error) { +func (s *String) ReadFrom(r io.Reader) (int64, error) { var l VarInt // String length nn, err := l.ReadFrom(r) if err != nil { return nn, err } - n += nn + n := nn bs := make([]byte, l) if _, err := io.ReadFull(r, bs); err != nil { @@ -99,7 +99,7 @@ func (s *String) ReadFrom(r io.Reader) (n int64, err error) { return n, nil } -// readByte read one byte from io.Reader +// readByte read one byte from io.Reader. func readByte(r io.Reader) (int64, byte, error) { if r, ok := r.(io.ByteReader); ok { v, err := r.ReadByte() @@ -110,12 +110,12 @@ func readByte(r io.Reader) (int64, byte, error) { return int64(n), v[0], err } -func (b Byte) WriteTo(w io.Writer) (n int64, err error) { +func (b Byte) WriteTo(w io.Writer) (int64, error) { nn, err := w.Write([]byte{byte(b)}) return int64(nn), err } -func (b *Byte) ReadFrom(r io.Reader) (n int64, err error) { +func (b *Byte) ReadFrom(r io.Reader) (int64, error) { n, v, err := readByte(r) if err != nil { return n, err @@ -126,20 +126,21 @@ func (b *Byte) ReadFrom(r io.Reader) (n int64, err error) { func (us UnsignedShort) WriteTo(w io.Writer) (int64, error) { n := uint16(us) - nn, err := w.Write([]byte{byte(n >> 8), byte(n)}) + byteLen := uint16(8) + nn, err := w.Write([]byte{byte(n >> byteLen), byte(n)}) return int64(nn), err } -func (us *UnsignedShort) ReadFrom(r io.Reader) (n int64, err error) { +func (us *UnsignedShort) ReadFrom(r io.Reader) (int64, error) { var bs [2]byte - if nn, err := io.ReadFull(r, bs[:]); err != nil { + nn, err := io.ReadFull(r, bs[:]) + if err != nil { return int64(nn), err - } else { - n += int64(nn) } + n := int64(nn) *us = UnsignedShort(int16(bs[0])<<8 | int16(bs[1])) - return + return n, nil } func (l Long) WriteTo(w io.Writer) (int64, error) { @@ -151,24 +152,24 @@ func (l Long) WriteTo(w io.Writer) (int64, error) { return int64(nn), err } -func (l *Long) ReadFrom(r io.Reader) (n int64, err error) { +func (l *Long) ReadFrom(r io.Reader) (int64, error) { var bs [8]byte - if nn, err := io.ReadFull(r, bs[:]); err != nil { + nn, err := io.ReadFull(r, bs[:]) + if err != nil { return int64(nn), err - } else { - n += int64(nn) } + n := int64(nn) *l = Long(int64(bs[0])<<56 | int64(bs[1])<<48 | int64(bs[2])<<40 | int64(bs[3])<<32 | int64(bs[4])<<24 | int64(bs[5])<<16 | int64(bs[6])<<8 | int64(bs[7])) - return + return n, nil } -func (v VarInt) WriteTo(w io.Writer) (n int64, err error) { +func (v VarInt) WriteTo(w io.Writer) (int64, error) { var vi [MaxVarIntLen]byte - nn := v.WriteToBytes(vi[:]) - nn, err = w.Write(vi[:nn]) - return int64(nn), err + n := v.WriteToBytes(vi[:]) + n, err := w.Write(vi[:n]) + return int64(n), err } // WriteToBytes encodes the VarInt into buf and returns the number of bytes written. @@ -191,25 +192,24 @@ func (v VarInt) WriteToBytes(buf []byte) int { return i } -func (v *VarInt) ReadFrom(r io.Reader) (n int64, err error) { - var V uint32 - var num, n2 int64 +func (v *VarInt) ReadFrom(r io.Reader) (int64, error) { + var vi uint32 + var num, n int64 for sec := byte(0x80); sec&0x80 != 0; num++ { if num > MaxVarIntLen { - return n, errors.New("VarInt is too big") + return 0, errors.New("VarInt is too big") } - n2, sec, err = readByte(r) - n += n2 + var err error + n, sec, err = readByte(r) if err != nil { return n, err } - V |= uint32(sec&0x7F) << uint32(7*num) + vi |= uint32(sec&0x7F) << uint32(7*num) } - - *v = VarInt(V) - return + *v = VarInt(vi) + return n, nil } // Len returns the number of bytes required to encode the VarInt. @@ -230,7 +230,7 @@ func (v VarInt) Len() int { } } -func (b ByteArray) WriteTo(w io.Writer) (n int64, err error) { +func (b ByteArray) WriteTo(w io.Writer) (int64, error) { n1, err := VarInt(len(b)).WriteTo(w) if err != nil { return n1, err @@ -239,27 +239,27 @@ func (b ByteArray) WriteTo(w io.Writer) (n int64, err error) { return n1 + int64(n2), err } -func (b *ByteArray) ReadFrom(r io.Reader) (n int64, err error) { - var Len VarInt - n1, err := Len.ReadFrom(r) +func (b *ByteArray) ReadFrom(r io.Reader) (int64, error) { + var length VarInt + n1, err := length.ReadFrom(r) if err != nil { return n1, err } - if cap(*b) < int(Len) { - *b = make(ByteArray, Len) + if cap(*b) < int(length) { + *b = make(ByteArray, length) } else { - *b = (*b)[:Len] + *b = (*b)[:length] } n2, err := io.ReadFull(r, *b) return n1 + int64(n2), err } -func (u UUID) WriteTo(w io.Writer) (n int64, err error) { +func (u UUID) WriteTo(w io.Writer) (int64, error) { nn, err := w.Write(u[:]) return int64(nn), err } -func (u *UUID) ReadFrom(r io.Reader) (n int64, err error) { +func (u *UUID) ReadFrom(r io.Reader) (int64, error) { nn, err := io.ReadFull(r, (*u)[:]) return int64(nn), err } diff --git a/pkg/infrared/protocol/types_test.go b/pkg/infrared/protocol/types_test.go index 2e485d37..347efe89 100644 --- a/pkg/infrared/protocol/types_test.go +++ b/pkg/infrared/protocol/types_test.go @@ -1,13 +1,15 @@ -package protocol +package protocol_test import ( "bytes" "testing" + + "github.com/haveachin/infrared/pkg/infrared/protocol" ) -var VarInts = []VarInt{0, 1, 2, 127, 128, 255, 2147483647, -1, -2147483648} +var varInts = []protocol.VarInt{0, 1, 2, 127, 128, 255, 2147483647, -1, -2147483648} -var PackedVarInts = [][]byte{ +var packedVarInts = [][]byte{ {0x00}, {0x01}, {0x02}, @@ -21,33 +23,33 @@ var PackedVarInts = [][]byte{ func TestVarInt_WriteTo(t *testing.T) { var buf bytes.Buffer - for i, v := range VarInts { + for i, v := range varInts { buf.Reset() if n, err := v.WriteTo(&buf); err != nil { t.Fatalf("Write to bytes.Buffer should never fail: %v", err) } else if n != int64(buf.Len()) { t.Errorf("Number of byte returned by WriteTo should equal to buffer.Len()") } - if p := buf.Bytes(); !bytes.Equal(p, PackedVarInts[i]) { - t.Errorf("pack int %d should be \"% x\", get \"% x\"", v, PackedVarInts[i], p) + if p := buf.Bytes(); !bytes.Equal(p, packedVarInts[i]) { + t.Errorf("pack int %d should be \"% x\", get \"% x\"", v, packedVarInts[i], p) } } } func TestVarInt_ReadFrom(t *testing.T) { - for i, v := range PackedVarInts { - var vi VarInt + for i, v := range packedVarInts { + var vi protocol.VarInt if _, err := vi.ReadFrom(bytes.NewReader(v)); err != nil { t.Errorf("unpack \"% x\" error: %v", v, err) } - if vi != VarInts[i] { - t.Errorf("unpack \"% x\" should be %d, get %d", v, VarInts[i], vi) + if vi != varInts[i] { + t.Errorf("unpack \"% x\" should be %d, get %d", v, varInts[i], vi) } } } func TestVarInt_ReadFrom_tooLongData(t *testing.T) { - var vi VarInt + var vi protocol.VarInt data := []byte{0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01} if _, err := vi.ReadFrom(bytes.NewReader(data)); err != nil { t.Logf("unpack \"% x\" error: %v", data, err) @@ -57,16 +59,16 @@ func TestVarInt_ReadFrom_tooLongData(t *testing.T) { } func FuzzVarInt_Len(f *testing.F) { - for _, v := range VarInts { + for _, v := range varInts { f.Add(int32(v)) } var buf bytes.Buffer f.Fuzz(func(t *testing.T, v int32) { defer buf.Reset() - if _, err := VarInt(v).WriteTo(&buf); err != nil { + if _, err := protocol.VarInt(v).WriteTo(&buf); err != nil { t.Fatal(err) } - if a, b := buf.Len(), VarInt(v).Len(); a != b { + if a, b := buf.Len(), protocol.VarInt(v).Len(); a != b { t.Errorf("VarInt(%d) Length calculation error: calculated to be %d, actually %d", v, b, a) } }) @@ -75,7 +77,7 @@ func FuzzVarInt_Len(f *testing.F) { func TestUnsignedShort_ReadFrom(t *testing.T) { tt := []struct { name string - expected UnsignedShort + expected protocol.UnsignedShort bb []byte }{ { @@ -106,7 +108,7 @@ func TestUnsignedShort_ReadFrom(t *testing.T) { } for _, tc := range tt { - var actual UnsignedShort + var actual protocol.UnsignedShort buf := bytes.NewBuffer(tc.bb) t.Run(tc.name, func(t *testing.T) { if n, err := actual.ReadFrom(buf); err != nil { @@ -125,7 +127,7 @@ func TestUnsignedShort_ReadFrom(t *testing.T) { func TestUnsignedShort_WriteTo(t *testing.T) { tt := []struct { name string - us UnsignedShort + us protocol.UnsignedShort expected []byte }{ { diff --git a/pkg/infrared/protocol/versions.go b/pkg/infrared/protocol/versions.go index cd7e08bb..3a6f82f2 100644 --- a/pkg/infrared/protocol/versions.go +++ b/pkg/infrared/protocol/versions.go @@ -5,21 +5,21 @@ import "strconv" type Version int32 const ( - Version_1_18_2 Version = 758 - Version_1_19 Version = 759 - Version_1_19_3 Version = 761 - Version_1_20_2 Version = 764 + Version1_18_2 Version = 758 + Version1_19 Version = 759 + Version1_19_3 Version = 761 + Version1_20_2 Version = 764 ) func (v Version) Name() string { switch v { - case Version_1_18_2: + case Version1_18_2: return "1.18.2" - case Version_1_19: + case Version1_19: return "1.19" - case Version_1_19_3: + case Version1_19_3: return "1.19.3" - case Version_1_20_2: + case Version1_20_2: return "1.20.2" default: return strconv.Itoa(int(v)) diff --git a/pkg/infrared/rate_limiter.go b/pkg/infrared/rate_limiter.go new file mode 100644 index 00000000..e0402693 --- /dev/null +++ b/pkg/infrared/rate_limiter.go @@ -0,0 +1,245 @@ +package infrared + +import ( + "errors" + "math" + "net" + "strconv" + "strings" + "sync" + "time" + + "github.com/cespare/xxhash/v2" +) + +type RateLimiterConfig struct { + RequestLimit int `yaml:"requestLimit"` + WindowLength time.Duration `yaml:"windowLength"` +} + +func RateLimit(requestLimit int, windowLength time.Duration, options ...RateLimiterOption) Filterer { + return newRateLimiter(requestLimit, windowLength, options...).Filterer() +} + +func RateLimitByIP(requestLimit int, windowLength time.Duration) Filterer { + return RateLimit(requestLimit, windowLength, WithKeyFuncs(KeyByIP)) +} + +func KeyByIP(c net.Conn) string { + rAddr := c.RemoteAddr().String() + ip, _, err := net.SplitHostPort(rAddr) + if err != nil { + ip = rAddr + } + return canonicalizeIP(ip) +} + +func WithKeyFuncs(keyFuncs ...RateLimiterKeyFunc) RateLimiterOption { + return func(rl *rateLimiter) { + if len(keyFuncs) > 0 { + rl.keyFn = composedKeyFunc(keyFuncs...) + } + } +} + +func WithKeyByIP() RateLimiterOption { + return WithKeyFuncs(KeyByIP) +} + +func composedKeyFunc(keyFuncs ...RateLimiterKeyFunc) RateLimiterKeyFunc { + return func(c net.Conn) string { + var key strings.Builder + for i := 0; i < len(keyFuncs); i++ { + k := keyFuncs[i](c) + key.WriteString(k) + } + return key.String() + } +} + +type RateLimiterKeyFunc func(c net.Conn) string +type RateLimiterOption func(rl *rateLimiter) + +// canonicalizeIP returns a form of ip suitable for comparison to other IPs. +// For IPv4 addresses, this is simply the whole string. +// For IPv6 addresses, this is the /64 prefix. +// https://github.com/didip/tollbooth/blob/v6.1.2/libstring/libstring.go#L57-L102 +func canonicalizeIP(ip string) string { + isIPv6 := false + // This is how net.ParseIP decides if an address is IPv6 + // https://cs.opensource.google/go/go/+/refs/tags/go1.17.7:src/net/ip.go;l=704 + for i := 0; !isIPv6 && i < len(ip); i++ { + switch ip[i] { + case '.': + // IPv4 + return ip + case ':': + // IPv6 + isIPv6 = true + } + } + if !isIPv6 { + // Not an IP address at all + return ip + } + + // By default, the string representation of a net.IPNet (masked IP address) is just + // "full_address/mask_bits". But using that will result in different addresses with + // the same /64 prefix comparing differently. So we need to zero out the last 64 bits + // so that all IPs in the same prefix will be the same. + // + // Note: When 1.18 is the minimum Go version, this can be written more cleanly like: + // netip.PrefixFrom(netip.MustParseAddr(ipv6), 64).Masked().Addr().String() + // (With appropriate error checking.) + + ipv6 := net.ParseIP(ip) + if ipv6 == nil { + return ip + } + + const bytesToZero = (128 - 64) / 8 + for i := len(ipv6) - bytesToZero; i < len(ipv6); i++ { + ipv6[i] = 0 + } + + // Note that this doesn't have the "/64" suffix customary with a CIDR representation, + // but those three bytes add nothing for us. + return ipv6.String() +} + +func newRateLimiter(requestLimit int, windowLength time.Duration, options ...RateLimiterOption) *rateLimiter { + rl := &rateLimiter{ + requestLimit: requestLimit, + windowLength: windowLength, + limitCounter: localCounter{ + counters: make(map[uint64]*count), + windowLength: windowLength, + }, + } + + for _, opt := range options { + opt(rl) + } + + if rl.keyFn == nil { + rl.keyFn = func(c net.Conn) string { + return "*" + } + } + + if rl.onRequestLimit == nil { + rl.onRequestLimit = func(c net.Conn) { + c.Close() + } + } + + return rl +} + +type rateLimiter struct { + requestLimit int + windowLength time.Duration + keyFn RateLimiterKeyFunc + limitCounter localCounter + onRequestLimit func(c net.Conn) +} + +func (r *rateLimiter) Status(key string) (bool, float64) { + t := time.Now().UTC() + currentWindow := t.Truncate(r.windowLength) + previousWindow := currentWindow.Add(-r.windowLength) + + currCount, prevCount := r.limitCounter.Get(key, currentWindow, previousWindow) + + diff := t.Sub(currentWindow) + rate := float64(prevCount)*(float64(r.windowLength)-float64(diff))/float64(r.windowLength) + float64(currCount) + return rate > float64(r.requestLimit), rate +} + +var ErrRateLimitReached = errors.New("rate limit reached") + +func (r *rateLimiter) Filterer() Filterer { + return FilterFunc(func(c net.Conn) error { + key := r.keyFn(c) + currentWindow := time.Now().UTC().Truncate(r.windowLength) + + _, rate := r.Status(key) + nrate := int(math.Round(rate)) + + if nrate >= r.requestLimit { + r.onRequestLimit(c) + return ErrRateLimitReached + } + + r.limitCounter.Inc(key, currentWindow) + return nil + }) +} + +type localCounter struct { + counters map[uint64]*count + windowLength time.Duration + lastEvict time.Time + mu sync.Mutex +} + +type count struct { + value int + updatedAt time.Time +} + +func (c *localCounter) Inc(key string, currentWindow time.Time) { + c.evict() + + c.mu.Lock() + defer c.mu.Unlock() + + hkey := limitCounterKey(key, currentWindow) + + v, ok := c.counters[hkey] + if !ok { + v = &count{} + c.counters[hkey] = v + } + v.value++ + v.updatedAt = time.Now() +} + +func (c *localCounter) Get(key string, currentWindow, previousWindow time.Time) (int, int) { + c.mu.Lock() + defer c.mu.Unlock() + + curr, ok := c.counters[limitCounterKey(key, currentWindow)] + if !ok { + curr = &count{value: 0, updatedAt: time.Now()} + } + prev, ok := c.counters[limitCounterKey(key, previousWindow)] + if !ok { + prev = &count{value: 0, updatedAt: time.Now()} + } + + return curr.value, prev.value +} + +func (c *localCounter) evict() { + c.mu.Lock() + defer c.mu.Unlock() + + if time.Since(c.lastEvict) < c.windowLength { + return + } + c.lastEvict = time.Now() + + for k, v := range c.counters { + if time.Since(v.updatedAt) >= c.windowLength { + delete(c.counters, k) + } + } +} + +func limitCounterKey(key string, window time.Time) uint64 { + h := xxhash.New() + _, _ = h.WriteString(key) + _, _ = h.WriteString(strconv.FormatInt(window.Unix(), 10)) + return h.Sum64() +} diff --git a/pkg/infrared/server.go b/pkg/infrared/server.go index a8f3e0dd..b7d7aaa3 100644 --- a/pkg/infrared/server.go +++ b/pkg/infrared/server.go @@ -9,7 +9,7 @@ import ( "time" "github.com/IGLOU-EU/go-wildcard" - "github.com/cespare/xxhash" + "github.com/cespare/xxhash/v2" "github.com/haveachin/infrared/pkg/infrared/protocol" "github.com/haveachin/infrared/pkg/infrared/protocol/status" ) @@ -47,8 +47,7 @@ type ServerConfig struct { } type Server struct { - cfg ServerConfig - statusRespProv StatusResponseProvider + cfg ServerConfig } func NewServer(fns ...ServerConfigFunc) (*Server, error) { @@ -61,21 +60,12 @@ func NewServer(fns ...ServerConfigFunc) (*Server, error) { return nil, errors.New("no addresses") } - srv := &Server{ + return &Server{ cfg: cfg, - } - - srv.statusRespProv = &statusResponseProvider{ - server: srv, - cacheTTL: 30 * time.Second, - statusHash: make(map[protocol.Version]uint64), - statusResponseCache: make(map[uint64]*statusCacheEntry), - } - - return srv, nil + }, nil } -func (s Server) Dial() (*conn, error) { +func (s Server) Dial() (*Conn, error) { c, err := net.Dial("tcp", string(s.cfg.Addresses[0])) if err != nil { return nil, err @@ -90,48 +80,52 @@ type ServerRequest struct { Domain ServerDomain IsLogin bool ProtocolVersion protocol.Version - ReadPks [2]protocol.Packet - ResponseChan chan<- ServerRequestResponse + ReadPackets [2]protocol.Packet } -type ServerRequestResponse struct { - ServerConn *conn +type ServerResponse struct { + ServerConn *Conn StatusResponse protocol.Packet SendProxyProtocol bool - Err error } -type serverGateway struct { - Servers []*Server - responder ServerRequestResponder - requestChan <-chan ServerRequest +type ServerRequester interface { + RequestServer(ServerRequest) (ServerResponse, error) +} - servers map[ServerDomain]*Server +type ServerGateway struct { + responder ServerRequestResponder + servers map[ServerDomain]*Server } -func (sg *serverGateway) init() error { - if len(sg.Servers) == 0 { - return errors.New("server gateway: no servers to route to") +func NewServerGateway(servers []*Server, responder ServerRequestResponder) (*ServerGateway, error) { + if len(servers) == 0 { + return nil, errors.New("server gateway: no servers to route to") } - sg.servers = make(map[ServerDomain]*Server) - for _, srv := range sg.Servers { + srvs := make(map[ServerDomain]*Server) + for _, srv := range servers { for _, d := range srv.cfg.Domains { dStr := string(d) dStr = strings.ToLower(dStr) dmn := ServerDomain(dStr) - sg.servers[dmn] = srv + srvs[dmn] = srv } } - if sg.responder == nil { - sg.responder = DialServerRequestResponder{} + if responder == nil { + responder = DialServerResponder{ + respProvs: make(map[*Server]StatusResponseProvider), + } } - return nil + return &ServerGateway{ + servers: srvs, + responder: responder, + }, nil } -func (sg *serverGateway) findServer(domain ServerDomain) *Server { +func (sg *ServerGateway) findServer(domain ServerDomain) *Server { dm := string(domain) dm = strings.ToLower(dm) for d, srv := range sg.servers { @@ -142,49 +136,63 @@ func (sg *serverGateway) findServer(domain ServerDomain) *Server { return nil } -func (sg *serverGateway) listenAndServe() error { - if err := sg.init(); err != nil { - return err +func (sg *ServerGateway) RequestServer(req ServerRequest) (ServerResponse, error) { + srv := sg.findServer(req.Domain) + if srv == nil { + return ServerResponse{}, errors.New("server not found") } - for req := range sg.requestChan { - srv := sg.findServer(req.Domain) - if srv == nil { - req.ResponseChan <- ServerRequestResponse{ - Err: errors.New("server not found"), - } - continue - } - - go sg.responder.RespondeToServerRequest(req, srv) - } - - return nil + return sg.responder.RespondeToServerRequest(req, srv) } type ServerRequestResponder interface { - RespondeToServerRequest(ServerRequest, *Server) + RespondeToServerRequest(ServerRequest, *Server) (ServerResponse, error) } -type DialServerRequestResponder struct{} +type DialServerResponder struct { + respProvs map[*Server]StatusResponseProvider +} -func (r DialServerRequestResponder) RespondeToServerRequest(req ServerRequest, srv *Server) { +func (r DialServerResponder) RespondeToServerRequest(req ServerRequest, srv *Server) (ServerResponse, error) { if req.IsLogin { - rc, err := srv.Dial() + return r.respondeToLoginRequest(req, srv) + } + + return r.respondeToStatusRequest(req, srv) +} + +func (r DialServerResponder) respondeToLoginRequest(_ ServerRequest, srv *Server) (ServerResponse, error) { + rc, err := srv.Dial() + if err != nil { + return ServerResponse{}, err + } - req.ResponseChan <- ServerRequestResponse{ - ServerConn: rc, - Err: err, - SendProxyProtocol: srv.cfg.SendProxyProtocol, + return ServerResponse{ + ServerConn: rc, + SendProxyProtocol: srv.cfg.SendProxyProtocol, + }, nil +} + +func (r DialServerResponder) respondeToStatusRequest(req ServerRequest, srv *Server) (ServerResponse, error) { + respProv, ok := r.respProvs[srv] + if !ok { + respProv = &statusResponseProvider{ + server: srv, + cacheTTL: 30 * time.Second, + statusHash: make(map[protocol.Version]uint64), + statusResponseCache: make(map[uint64]*statusCacheEntry), } - return + r.respProvs[srv] = respProv } - _, pk, err := srv.statusRespProv.StatusResponse(req.ProtocolVersion, req.ReadPks) - req.ResponseChan <- ServerRequestResponse{ - StatusResponse: pk, - Err: err, + _, pk, err := respProv.StatusResponse(req.ProtocolVersion, req.ReadPackets) + if err != nil { + return ServerResponse{}, err } + + return ServerResponse{ + StatusResponse: pk, + }, nil } type StatusResponseProvider interface { @@ -210,7 +218,9 @@ type statusResponseProvider struct { statusResponseCache map[uint64]*statusCacheEntry } -func (s *statusResponseProvider) requestNewStatusResponseJSON(readPks [2]protocol.Packet) (status.ResponseJSON, protocol.Packet, error) { +func (s *statusResponseProvider) requestNewStatusResponseJSON( + readPks [2]protocol.Packet, +) (status.ResponseJSON, protocol.Packet, error) { rc, err := s.server.Dial() if err != nil { return status.ResponseJSON{}, protocol.Packet{}, err @@ -239,7 +249,10 @@ func (s *statusResponseProvider) requestNewStatusResponseJSON(readPks [2]protoco return respJSON, pk, nil } -func (s *statusResponseProvider) StatusResponse(protVer protocol.Version, readPks [2]protocol.Packet) (status.ResponseJSON, protocol.Packet, error) { +func (s *statusResponseProvider) StatusResponse( + protVer protocol.Version, + readPks [2]protocol.Packet, +) (status.ResponseJSON, protocol.Packet, error) { if s.cacheTTL <= 0 { return s.requestNewStatusResponseJSON(readPks) } @@ -259,7 +272,10 @@ func (s *statusResponseProvider) StatusResponse(protVer protocol.Version, readPk return entry.responseJSON, entry.responsePk, nil } -func (s *statusResponseProvider) cacheResponse(protVer protocol.Version, readPks [2]protocol.Packet) (status.ResponseJSON, protocol.Packet, error) { +func (s *statusResponseProvider) cacheResponse( + protVer protocol.Version, + readPks [2]protocol.Packet, +) (status.ResponseJSON, protocol.Packet, error) { newStatusResp, pk, err := s.requestNewStatusResponseJSON(readPks) if err != nil { return status.ResponseJSON{}, protocol.Packet{}, err diff --git a/tools/dos/main.go b/tools/dos/main.go index a229ce3f..30866eca 100644 --- a/tools/dos/main.go +++ b/tools/dos/main.go @@ -22,23 +22,23 @@ var ( func initPayload() { var pk protocol.Packet - handshaking.ServerBoundHandshake{ + _ = handshaking.ServerBoundHandshake{ ProtocolVersion: 758, ServerAddress: "localhost", ServerPort: 25565, NextState: handshaking.StateStatusServerBoundHandshake, }.Marshal(&pk) var buf bytes.Buffer - pk.WriteTo(&buf) + _, _ = pk.WriteTo(&buf) handshakePayload = buf.Bytes() - login.ServerBoundLoginStart{ + _ = login.ServerBoundLoginStart{ Name: "Test", HasSignature: false, HasPlayerUUID: false, - }.Marshal(&pk, protocol.Version_1_19) + }.Marshal(&pk, protocol.Version1_19) buf.Reset() - pk.WriteTo(&buf) + _, _ = pk.WriteTo(&buf) loginStartPayload = buf.Bytes() log.Println(len(handshakePayload) + len(loginStartPayload)) @@ -53,14 +53,12 @@ func initFlags() { flag.Parse() } -func init() { +func main() { runtime.GOMAXPROCS(4) initFlags() initPayload() -} -func main() { targetAddr := "localhost:25565" if len(os.Args) < 2 { @@ -70,11 +68,11 @@ func main() { targetAddr = os.Args[1] } - c, err := net.Dial("tcp", targetAddr) + conn, err := net.Dial("tcp", targetAddr) if err != nil { log.Fatal(err) } - c.Close() + _ = conn.Close() for i := 0; ; i++ { if i > 0 && i%10 == 0 { @@ -88,20 +86,22 @@ func main() { } if sendProxyProtocol { - writeProxyProtocolHeader(randomAddr(), c) + if err = writeProxyProtocolHeader(randomAddr(), c); err != nil { + log.Printf("Write proxy protocol: %s", err) + } } - c.Write(handshakePayload) - c.Write(loginStartPayload) - c.Close() + _, _ = c.Write(handshakePayload) + _, _ = c.Write(loginStartPayload) + _ = c.Close() }() - //time.Sleep(time.Millisecond * 10) + // time.Sleep(time.Millisecond * 10) } } func randomAddr() net.Addr { addrBytes := make([]byte, 6) - rand.Read(addrBytes) + _, _ = rand.Read(addrBytes) return &net.TCPAddr{ IP: net.IPv4(addrBytes[0], addrBytes[1], addrBytes[2], addrBytes[3]), @@ -111,7 +111,11 @@ func randomAddr() net.Addr { func writeProxyProtocolHeader(addr net.Addr, rc net.Conn) error { tp := proxyproto.TCPv4 - addrTCP := addr.(*net.TCPAddr) + addrTCP, ok := addr.(*net.TCPAddr) + if !ok { + panic("not a tcp connection") + } + if addrTCP.IP.To4() == nil { tp = proxyproto.TCPv6 } diff --git a/tools/malpk/main.go b/tools/malpk/main.go new file mode 100644 index 00000000..08d8bf6c --- /dev/null +++ b/tools/malpk/main.go @@ -0,0 +1,51 @@ +package main + +import ( + "bytes" + "log" + "net" + "sync" + + "github.com/haveachin/infrared/pkg/infrared/protocol" + "github.com/haveachin/infrared/pkg/infrared/protocol/handshaking" +) + +var payload []byte + +func initPayload() { + buf := new(bytes.Buffer) + _, _ = protocol.VarInt(0x200000).WriteTo(buf) + _, _ = protocol.VarInt(handshaking.ServerBoundHandshakeID).WriteTo(buf) + _, _ = protocol.VarInt(protocol.Version1_20_2.ProtocolNumber()).WriteTo(buf) + _, _ = protocol.String("localhost").WriteTo(buf) + _, _ = protocol.UnsignedShort(25565).WriteTo(buf) + _, _ = protocol.Byte(2).WriteTo(buf) + payload = buf.Bytes() +} + +func main() { + initPayload() + + n := 100000 + + wg := sync.WaitGroup{} + wg.Add(n) + + for i := 0; i < n; i++ { + go func() { + c, err := net.Dial("tcp", "localhost:25565") + if err != nil { + log.Println(err) + return + } + + if _, err = c.Write(payload); err != nil { + log.Println(err) + } + _ = c.Close() + wg.Done() + }() + } + + wg.Wait() +}