Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 82d063ca8b | |||
| 561c19581c | |||
| d7366c970b | |||
| beb59543dc | |||
| e4ec21e135 | |||
| f715379a44 | |||
| 8d216370d3 | |||
| f3eb09812b | |||
| 75e9b8dc50 | |||
| d70d774be6 | |||
| 62ab02acf0 | |||
| d3a51c18c0 | |||
| 4535a38ad8 | |||
| 9195485b99 | |||
| 4cc5808883 | |||
| 2d96b9ed8d | |||
| 58e81a7c02 | |||
| a3ab13cbf5 | |||
| 2bb748e78f | |||
| 5c30baa11e | |||
| 5912ed5283 | |||
| 6871ee1c09 | |||
| 4953b16a08 | |||
| 2865295911 | |||
| 5bb2684d9e | |||
| 2c548c6b68 | |||
| f89af3bfd0 | |||
| f66c0f6d94 | |||
| c2c9a6e546 | |||
| 6d52cf0259 | |||
| 1700227483 | |||
| b335f98330 | |||
| 2e8343526e | |||
| 0f26f25cb9 | |||
| 21a35a8b48 | |||
| 0065db672c | |||
| ac8a77e539 | |||
| 8956d5bc53 | |||
| 40f41c3028 | |||
| 5bb789333d | |||
| 9457911e4a | |||
| aa8c6fbd90 |
@@ -0,0 +1,2 @@
|
|||||||
|
*.go @Ullaakut @whiteboxsolutions @nblair2
|
||||||
|
*.md @Ullaakut @whiteboxsolutions @nblair2
|
||||||
@@ -2,10 +2,8 @@
|
|||||||
|
|
||||||
<!-- A brief description of the change being made with this pull request. -->
|
<!-- A brief description of the change being made with this pull request. -->
|
||||||
|
|
||||||
<!--
|
Fixes #
|
||||||
Fixes [#XXX](https://github.com/Ulaakut/cameradar/issues/XXX)
|
|
||||||
-->
|
|
||||||
|
|
||||||
## How did I test it?
|
## How did I test it?
|
||||||
|
|
||||||
<!-- A brief description the steps taken to test this pull request. -->
|
<!-- A brief description of the steps taken to test this pull request. -->
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ jobs:
|
|||||||
if: steps.gomod.outputs.cache-hit != 'true'
|
if: steps.gomod.outputs.cache-hit != 'true'
|
||||||
|
|
||||||
- name: Run GoReleaser
|
- name: Run GoReleaser
|
||||||
uses: goreleaser/goreleaser-action@v6
|
uses: goreleaser/goreleaser-action@v7
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GORELEASER_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GORELEASER_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -28,13 +28,13 @@ jobs:
|
|||||||
if: steps.install-go.outputs.cache-hit != 'true'
|
if: steps.install-go.outputs.cache-hit != 'true'
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
- name: Log in to Docker Hub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v4
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Run GoReleaser
|
- name: Run GoReleaser
|
||||||
uses: goreleaser/goreleaser-action@v6
|
uses: goreleaser/goreleaser-action@v7
|
||||||
env:
|
env:
|
||||||
GORELEASER_CURRENT_TAG: ${{ github.ref_name }}
|
GORELEASER_CURRENT_TAG: ${{ github.ref_name }}
|
||||||
DOCKER_REPOSITORY: ullaakut/cameradar
|
DOCKER_REPOSITORY: ullaakut/cameradar
|
||||||
|
|||||||
+69
-1
@@ -14,6 +14,49 @@ Clone the repo and install dependencies using Go modules.
|
|||||||
go mod download
|
go mod download
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Test against fake targets
|
||||||
|
|
||||||
|
Use the following options when you want reproducible local testing.
|
||||||
|
|
||||||
|
#### Testing discovery behavior
|
||||||
|
|
||||||
|
Use `scanme.nmap.org` to validate discovery-related behavior.
|
||||||
|
|
||||||
|
- `scanme.nmap.org` does not expose RTSP or RTSPS ports.
|
||||||
|
- Target its open ports (for example `22`, `80`, `9929`, `31337`) to test discovery flow, reporting, and scan handling.
|
||||||
|
|
||||||
|
Example command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cameradar -t scanme.nmap.org -p 22
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Testing RTSP and attack behavior
|
||||||
|
|
||||||
|
Use [RTSPAllTheThings](https://github.com/Ullaakut/RTSPAllTheThings) to test RTSP-specific logic and camera attack flows.
|
||||||
|
|
||||||
|
- It supports both basic and digest authentication.
|
||||||
|
- It behaves like a standards-compliant RTSP camera.
|
||||||
|
|
||||||
|
> [!CAUTION]
|
||||||
|
> It is no longer maintained and has limited camera emulation coverage.
|
||||||
|
|
||||||
|
Example command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --net=host -p 8554:8554 -e RTSP_USERNAME=admin -e RTSP_PASSWORD=12345 -e RTSP_PORT=8554 -e RTSP_AUTHENTICATION_METHOD=digest ullaakut/rtspatt
|
||||||
|
```
|
||||||
|
|
||||||
|
Many real cameras slightly diverge from strict RTSP behavior. For example, some devices allow `DESCRIBE` without authentication, or return `403` and `404` in an order that differs from strict expectations.
|
||||||
|
Unfortunately, RTSPATT cannot reproduce those behaviors.
|
||||||
|
|
||||||
|
#### Prefer real cameras when possible
|
||||||
|
|
||||||
|
The most reliable testing method is running against real cameras and real network conditions.
|
||||||
|
|
||||||
|
> [!CAUTION]
|
||||||
|
> Scan only authorized targets and networks.
|
||||||
|
|
||||||
## Run tests
|
## Run tests
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -22,13 +65,37 @@ make test
|
|||||||
|
|
||||||
## Formatting and linting
|
## Formatting and linting
|
||||||
|
|
||||||
Run `gofmt` on changed files.
|
|
||||||
Keep code idiomatic and consistent with existing style.
|
Keep code idiomatic and consistent with existing style.
|
||||||
|
By default, follow the [Uber Go Style Guide](https://github.com/uber-go/guide) and the guidelines from [Effective Go](https://go.dev/doc/effective_go).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make fmt
|
make fmt
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Dependency for linting
|
||||||
|
|
||||||
|
* golangci-lint
|
||||||
|
* see current version defined in `.github/workflows/test.yaml` at `jobs.tests.steps.["Run linter"]`
|
||||||
|
* configured in `.golangci.yml`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make lint
|
||||||
|
```
|
||||||
|
|
||||||
|
## Commit messages and PR titles
|
||||||
|
|
||||||
|
Use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) for commit messages and pull request titles.
|
||||||
|
|
||||||
|
- Use the format: `type: subject`
|
||||||
|
- Write the subject in imperative mood: `add`, `update`, `remove`, `fix`, `refactor`
|
||||||
|
- Do not use gerunds in subjects: avoid `adding`, `updating`, `removing`
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
- `feat: add RTSP timeout flag`
|
||||||
|
- `fix: remove duplicate progress line`
|
||||||
|
- `docs: update commit message guidelines`
|
||||||
|
|
||||||
## Reporting issues
|
## Reporting issues
|
||||||
|
|
||||||
Use the issue template in [.github/ISSUE_TEMPLATE.md](.github/ISSUE_TEMPLATE.md).
|
Use the issue template in [.github/ISSUE_TEMPLATE.md](.github/ISSUE_TEMPLATE.md).
|
||||||
@@ -43,3 +110,4 @@ Only scan authorized targets.
|
|||||||
4. Add or update tests when possible.
|
4. Add or update tests when possible.
|
||||||
5. Ensure `make test` passes.
|
5. Ensure `make test` passes.
|
||||||
6. Try to bring as much test coverage as possible with your changes.
|
6. Try to bring as much test coverage as possible with your changes.
|
||||||
|
7. Use a Conventional Commit-style PR title with an imperative subject.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="#license">
|
<a href="#license">
|
||||||
<img src="https://img.shields.io/badge/license-Apache-blue.svg?style=flat" />
|
<img src="https://img.shields.io/badge/license-MIT-blue.svg?style=flat" />
|
||||||
</a>
|
</a>
|
||||||
<a href="https://hub.docker.com/r/ullaakut/cameradar/">
|
<a href="https://hub.docker.com/r/ullaakut/cameradar/">
|
||||||
<img src="https://img.shields.io/docker/pulls/ullaakut/cameradar.svg?style=flat" />
|
<img src="https://img.shields.io/docker/pulls/ullaakut/cameradar.svg?style=flat" />
|
||||||
@@ -47,9 +47,8 @@ Cameradar scans RTSP endpoints on authorized targets, and uses dictionary attack
|
|||||||
- [Security and responsible use](#security-and-responsible-use)
|
- [Security and responsible use](#security-and-responsible-use)
|
||||||
- [Output](#output)
|
- [Output](#output)
|
||||||
- [Check camera access](#check-camera-access)
|
- [Check camera access](#check-camera-access)
|
||||||
- [Command-line options](#command-line-options)
|
- [Command-line options and environment variables](#command-line-options-and-environment-variables)
|
||||||
- [Input file format](#input-file-format)
|
- [Input file format](#input-file-format)
|
||||||
- [Environment variables](#environment-variables)
|
|
||||||
- [Build and contribute](#build-and-contribute)
|
- [Build and contribute](#build-and-contribute)
|
||||||
- [Frequently asked questions](#frequently-asked-questions)
|
- [Frequently asked questions](#frequently-asked-questions)
|
||||||
- [Examples](#examples)
|
- [Examples](#examples)
|
||||||
@@ -75,7 +74,7 @@ docker run --rm -t --net=host ullaakut/cameradar --targets 192.168.100.0/24
|
|||||||
|
|
||||||
This scans ports 554, 5554, and 8554 on the target subnet.
|
This scans ports 554, 5554, and 8554 on the target subnet.
|
||||||
It attempts to enumerate RTSP streams.
|
It attempts to enumerate RTSP streams.
|
||||||
For all options, see [command-line options](#command-line-options).
|
For all options, see [Configuration reference](https://github.com/Ullaakut/cameradar/wiki/Configuration-Reference).
|
||||||
|
|
||||||
- Targets can be CIDRs, IPs, IP ranges or a hostname.
|
- Targets can be CIDRs, IPs, IP ranges or a hostname.
|
||||||
- Subnet: `172.16.100.0/24`
|
- Subnet: `172.16.100.0/24`
|
||||||
@@ -107,7 +106,7 @@ Use this option if Docker is not available or if you want a local build.
|
|||||||
1. `go install github.com/Ullaakut/cameradar/v6/cmd/cameradar@latest`
|
1. `go install github.com/Ullaakut/cameradar/v6/cmd/cameradar@latest`
|
||||||
|
|
||||||
The `cameradar` binary is now in your `$GOPATH/bin`.
|
The `cameradar` binary is now in your `$GOPATH/bin`.
|
||||||
For available flags, see [command-line options](#command-line-options).
|
For available flags, see [Configuration reference](https://github.com/Ullaakut/cameradar/wiki/Configuration-Reference).
|
||||||
|
|
||||||
## Install on Android (Termux)
|
## Install on Android (Termux)
|
||||||
|
|
||||||
@@ -273,117 +272,11 @@ localhost
|
|||||||
When you use `--skip-scan`, Cameradar expands each entry into explicit IP
|
When you use `--skip-scan`, Cameradar expands each entry into explicit IP
|
||||||
addresses before building the target list.
|
addresses before building the target list.
|
||||||
|
|
||||||
## Options
|
## Command-line options and environment variables
|
||||||
|
|
||||||
### `TARGETS` / `--targets` / `-t`
|
The complete CLI and environment variable reference is maintained in [Configuration reference](https://github.com/Ullaakut/cameradar/wiki/Configuration-Reference).
|
||||||
|
|
||||||
This variable is required.
|
This includes all supported flags, defaults, accepted values, and env var mapping.
|
||||||
It specifies the target that Cameradar scans and attempts to access.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
* `172.16.100.0/24`
|
|
||||||
* `192.168.1.1`
|
|
||||||
* `localhost`
|
|
||||||
* `192.168.1.140-255`
|
|
||||||
* `192.168.2-3.0-255`
|
|
||||||
|
|
||||||
### `PORTS` / `--ports` / `-p`
|
|
||||||
|
|
||||||
This variable is optional and allows you to specify the ports to scan.
|
|
||||||
|
|
||||||
Default value: `554,5554,8554`
|
|
||||||
|
|
||||||
Change these only if you are sure cameras stream over different ports.
|
|
||||||
Most cameras use these defaults.
|
|
||||||
|
|
||||||
### `CUSTOM_ROUTES` / `--custom-routes` / `-r`
|
|
||||||
|
|
||||||
This option is optional.
|
|
||||||
It replaces the default routes dictionary used for the dictionary attack.
|
|
||||||
|
|
||||||
If unset, Cameradar uses the built-in routes dictionary.
|
|
||||||
|
|
||||||
### `CUSTOM_CREDENTIALS` / `--custom-credentials` / `-c`
|
|
||||||
|
|
||||||
This option is optional.
|
|
||||||
It replaces the default credentials dictionary used for the dictionary attack.
|
|
||||||
|
|
||||||
If unset, Cameradar uses the built-in credentials dictionary.
|
|
||||||
|
|
||||||
### `SCANNER` / `--scanner`
|
|
||||||
|
|
||||||
This optional variable sets the discovery backend.
|
|
||||||
|
|
||||||
* `nmap` includes service discovery and is generally more reliable when you want
|
|
||||||
to specifically identify RTSP services.
|
|
||||||
* `masscan` is generally more efficient for large-scale discovery, but it does
|
|
||||||
not identify services and therefore can be less specific for RTSP.
|
|
||||||
|
|
||||||
Supported values: `nmap`, `masscan`
|
|
||||||
|
|
||||||
Default value: `nmap`
|
|
||||||
|
|
||||||
### `SCAN_SPEED` / `--scan-speed` / `-s`
|
|
||||||
|
|
||||||
This optional variable sets nmap discovery presets for speed or accuracy.
|
|
||||||
Lower it on slow networks and raise it on fast networks.
|
|
||||||
See [nmap timing templates](https://nmap.org/book/man-performance.html).
|
|
||||||
|
|
||||||
This option is ignored when `--scanner masscan` is used.
|
|
||||||
|
|
||||||
Default value: `4`
|
|
||||||
|
|
||||||
### `SKIP_SCAN` / `--skip-scan`
|
|
||||||
|
|
||||||
This optional flag skips network discovery and assumes every target and port
|
|
||||||
pair is an RTSP stream.
|
|
||||||
|
|
||||||
Use it when you already know the RTSP endpoints or when discovery is blocked.
|
|
||||||
For best results, specify only RTSP ports.
|
|
||||||
|
|
||||||
Default value: `false`
|
|
||||||
|
|
||||||
### `ATTACK_INTERVAL` / `--attack-interval` / `-I`
|
|
||||||
|
|
||||||
This optional variable sets a delay between attacks.
|
|
||||||
Increase it for networks that may block brute-force attempts.
|
|
||||||
Default: no delay.
|
|
||||||
|
|
||||||
Default value: `0ms`
|
|
||||||
|
|
||||||
### `TIMEOUT` / `--timeout` / `-T`
|
|
||||||
|
|
||||||
This optional variable sets the timeout for requests sent to the cameras.
|
|
||||||
Increase it for slow networks and decrease it for fast networks.
|
|
||||||
|
|
||||||
Default value: `2000ms`
|
|
||||||
|
|
||||||
### `DEBUG` / `--debug` / `-d`
|
|
||||||
|
|
||||||
This optional variable enables more verbose output.
|
|
||||||
|
|
||||||
It outputs discovery results (`nmap` or `masscan`), cURL requests, and more.
|
|
||||||
|
|
||||||
Default: `false`
|
|
||||||
|
|
||||||
### `UI` / `--ui`
|
|
||||||
|
|
||||||
This option selects the UI mode.
|
|
||||||
|
|
||||||
* `auto` selects `tui` if your terminal is interactive, `plain` otherwise
|
|
||||||
* `tui` shows a fullscreen interface with a progress bar and shows the results in a table
|
|
||||||
* `plain` logs the steps taken by cameradar as plain text and is meant to be used by non-interactive terminals
|
|
||||||
|
|
||||||
Supported values: `auto`, `tui`, `plain`
|
|
||||||
|
|
||||||
Default: `auto`
|
|
||||||
|
|
||||||
### `OUTPUT` / `--output`
|
|
||||||
|
|
||||||
This optional variable writes an M3U playlist of the discovered streams to the given file path.
|
|
||||||
|
|
||||||
Example: `/tmp/cameradar.m3u`
|
|
||||||
|
|
||||||
## Build and contribute
|
## Build and contribute
|
||||||
|
|
||||||
@@ -403,41 +296,7 @@ The `cameradar` binary is now in `$GOPATH/bin/cameradar`.
|
|||||||
|
|
||||||
## Frequently asked questions
|
## Frequently asked questions
|
||||||
|
|
||||||
> Cameradar does not detect any camera!
|
See [Troubleshooting & FAQ](https://github.com/Ullaakut/cameradar/wiki/Troubleshooting-%26-FAQ)
|
||||||
|
|
||||||
This usually means the cameras are not streaming over RTSP.
|
|
||||||
It can also mean the targets are not in your scan range.
|
|
||||||
CCTV cameras are often on private subnets.
|
|
||||||
Use `-t` to set the correct targets.
|
|
||||||
If you still see no results, open an issue with device details.
|
|
||||||
|
|
||||||
> Cameradar detects my cameras, but does not manage to access them!
|
|
||||||
|
|
||||||
The camera configuration may have changed, so defaults do not match.
|
|
||||||
Cameradar uses defaults unless you provide custom dictionaries.
|
|
||||||
Add your credentials and routes, then follow the [configuration](#configuration) section.
|
|
||||||
|
|
||||||
> What happened to the C++ version?
|
|
||||||
|
|
||||||
The 1.1.4 tag contains the legacy C++ implementation.
|
|
||||||
It is slower and less stable than the Go version, so it is not recommended to use.
|
|
||||||
|
|
||||||
> I want to scan my local network or my own machine, and it does not work! What's going on?
|
|
||||||
|
|
||||||
Use `--net=host` when running the Docker image, or use the installed binary.
|
|
||||||
|
|
||||||
> I don't have a camera, but I'd like to try Cameradar!
|
|
||||||
|
|
||||||
Run the following container, then run Cameradar against it:
|
|
||||||
|
|
||||||
`docker run -p 8554:8554 -e RTSP_USERNAME=admin -e RTSP_PASSWORD=12345 -e RTSP_PORT=8554 ullaakut/rtspatt`
|
|
||||||
|
|
||||||
Cameradar should discover the `admin` / `12345` credentials.
|
|
||||||
You can try other default credentials listed in the dictionaries.
|
|
||||||
|
|
||||||
> What authentication types does Cameradar support?
|
|
||||||
|
|
||||||
Cameradar supports both basic and digest authentication.
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
@@ -449,7 +308,7 @@ Cameradar supports both basic and digest authentication.
|
|||||||
|
|
||||||
`docker run --rm -t --net=host -v /tmp:/tmp ullaakut/cameradar --targets /tmp/test.txt --ports 8554`
|
`docker run --rm -t --net=host -v /tmp:/tmp ullaakut/cameradar --targets /tmp/test.txt --ports 8554`
|
||||||
|
|
||||||
> Running cameradar on a subnetwork with custom dictionaries, on ports 554, 5554 and 8554
|
> Running cameradar on a subnetwork with custom dictionaries, on ports 554, 5554, 8554, 322, and 8322
|
||||||
|
|
||||||
`docker run --rm -t --net=host -v /tmp:/tmp ullaakut/cameradar --targets 192.168.0.0/24 --custom-credentials "/tmp/dictionaries/credentials.json" --custom-routes "/tmp/dictionaries/routes" --ports 554,5554,8554`
|
`docker run --rm -t --net=host -v /tmp:/tmp ullaakut/cameradar --targets 192.168.0.0/24 --custom-credentials "/tmp/dictionaries/credentials.json" --custom-routes "/tmp/dictionaries/routes" --ports 554,5554,8554`
|
||||||
|
|
||||||
|
|||||||
+10
-17
@@ -38,18 +38,17 @@ var (
|
|||||||
|
|
||||||
var flags = cmd.Flags{
|
var flags = cmd.Flags{
|
||||||
&cli.StringSliceFlag{
|
&cli.StringSliceFlag{
|
||||||
Name: flagTargets,
|
Name: flagTargets,
|
||||||
Usage: "The targets on which to scan for open RTSP streams in a network range format",
|
Usage: "The targets on which to scan for open RTSP streams in a network range format",
|
||||||
Aliases: []string{"t"},
|
Aliases: []string{"t"},
|
||||||
Sources: cli.EnvVars(strcase.ToSNAKE(flagTargets)),
|
Sources: cli.EnvVars(strcase.ToSNAKE(flagTargets)),
|
||||||
Required: true,
|
|
||||||
},
|
},
|
||||||
&cli.StringSliceFlag{
|
&cli.StringSliceFlag{
|
||||||
Name: flagPorts,
|
Name: flagPorts,
|
||||||
Usage: "The ports on which to search for RTSP streams",
|
Usage: "The ports on which to search for RTSP streams",
|
||||||
Aliases: []string{"p"},
|
Aliases: []string{"p"},
|
||||||
Sources: cli.EnvVars(strcase.ToSNAKE(flagPorts)),
|
Sources: cli.EnvVars(strcase.ToSNAKE(flagPorts)),
|
||||||
Value: []string{"554", "5554", "8554", "http"},
|
Value: []string{"554", "5554", "8554", "http", "322", "8322"},
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: flagCustomRoutes,
|
Name: flagCustomRoutes,
|
||||||
@@ -128,19 +127,13 @@ func realMain() (code int) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
scanCommand := &cli.Command{
|
|
||||||
Name: "scan",
|
|
||||||
Usage: "Scan targets for RTSP streams",
|
|
||||||
Flags: flags,
|
|
||||||
Action: runCameradar,
|
|
||||||
}
|
|
||||||
|
|
||||||
app := &cli.Command{
|
app := &cli.Command{
|
||||||
Name: "Cameradar",
|
Name: "Cameradar",
|
||||||
Version: version,
|
Version: version,
|
||||||
DefaultCommand: scanCommand.Name,
|
Usage: "Scan targets for RTSP streams",
|
||||||
|
Flags: flags,
|
||||||
|
Action: runCameradar,
|
||||||
Commands: []*cli.Command{
|
Commands: []*cli.Command{
|
||||||
scanCommand,
|
|
||||||
{
|
{
|
||||||
Name: "version",
|
Name: "version",
|
||||||
Usage: "Print version information",
|
Usage: "Print version information",
|
||||||
|
|||||||
@@ -5,30 +5,33 @@ go 1.25.3
|
|||||||
require (
|
require (
|
||||||
github.com/Ullaakut/masscan v1.0.0
|
github.com/Ullaakut/masscan v1.0.0
|
||||||
github.com/Ullaakut/nmap/v4 v4.0.0
|
github.com/Ullaakut/nmap/v4 v4.0.0
|
||||||
github.com/bluenviron/gortsplib/v5 v5.3.0
|
github.com/bluenviron/gortsplib/v5 v5.4.0
|
||||||
github.com/charmbracelet/bubbles v0.21.0
|
github.com/charmbracelet/bubbles v1.0.0
|
||||||
github.com/charmbracelet/bubbletea v1.3.10
|
github.com/charmbracelet/bubbletea v1.3.10
|
||||||
github.com/charmbracelet/lipgloss v1.1.0
|
github.com/charmbracelet/lipgloss v1.1.0
|
||||||
github.com/ettle/strcase v0.2.0
|
github.com/ettle/strcase v0.2.0
|
||||||
github.com/hamba/cmd/v3 v3.1.0
|
github.com/hamba/cmd/v3 v3.1.0
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/urfave/cli/v3 v3.4.1
|
github.com/urfave/cli/v3 v3.7.0
|
||||||
golang.org/x/term v0.39.0
|
golang.org/x/term v0.40.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/VictoriaMetrics/metrics v1.40.1 // indirect
|
github.com/VictoriaMetrics/metrics v1.40.1 // indirect
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/bluenviron/mediacommon/v2 v2.7.0 // indirect
|
github.com/bluenviron/mediacommon/v2 v2.8.1 // indirect
|
||||||
github.com/cactus/go-statsd-client/v5 v5.1.0 // indirect
|
github.com/cactus/go-statsd-client/v5 v5.1.0 // indirect
|
||||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
github.com/charmbracelet/colorprofile v0.4.1 // indirect
|
||||||
github.com/charmbracelet/harmonica v0.2.0 // indirect
|
github.com/charmbracelet/harmonica v0.2.0 // indirect
|
||||||
github.com/charmbracelet/x/ansi v0.10.1 // indirect
|
github.com/charmbracelet/x/ansi v0.11.6 // indirect
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
|
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
|
||||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
github.com/charmbracelet/x/term v0.2.2 // indirect
|
||||||
|
github.com/clipperhouse/displaywidth v0.9.0 // indirect
|
||||||
|
github.com/clipperhouse/stringish v0.1.1 // indirect
|
||||||
|
github.com/clipperhouse/uax29/v2 v2.5.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||||
github.com/go-logr/logr v1.4.3 // indirect
|
github.com/go-logr/logr v1.4.3 // indirect
|
||||||
@@ -42,10 +45,10 @@ require (
|
|||||||
github.com/hamba/logger/v2 v2.9.0 // indirect
|
github.com/hamba/logger/v2 v2.9.0 // indirect
|
||||||
github.com/hamba/statter/v2 v2.8.0 // indirect
|
github.com/hamba/statter/v2 v2.8.0 // indirect
|
||||||
github.com/klauspost/compress v1.18.0 // indirect
|
github.com/klauspost/compress v1.18.0 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
github.com/mattn/go-runewidth v0.0.19 // indirect
|
||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||||
github.com/muesli/termenv v0.16.0 // indirect
|
github.com/muesli/termenv v0.16.0 // indirect
|
||||||
@@ -54,8 +57,8 @@ require (
|
|||||||
github.com/pion/logging v0.2.4 // indirect
|
github.com/pion/logging v0.2.4 // indirect
|
||||||
github.com/pion/randutil v0.1.0 // indirect
|
github.com/pion/randutil v0.1.0 // indirect
|
||||||
github.com/pion/rtcp v1.2.16 // indirect
|
github.com/pion/rtcp v1.2.16 // indirect
|
||||||
github.com/pion/rtp v1.10.0 // indirect
|
github.com/pion/rtp v1.10.1 // indirect
|
||||||
github.com/pion/sdp/v3 v3.0.17 // indirect
|
github.com/pion/sdp/v3 v3.0.18 // indirect
|
||||||
github.com/pion/srtp/v3 v3.0.10 // indirect
|
github.com/pion/srtp/v3 v3.0.10 // indirect
|
||||||
github.com/pion/transport/v4 v4.0.1 // indirect
|
github.com/pion/transport/v4 v4.0.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
@@ -68,19 +71,19 @@ require (
|
|||||||
github.com/valyala/histogram v1.2.0 // indirect
|
github.com/valyala/histogram v1.2.0 // indirect
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||||
go.opentelemetry.io/otel v1.38.0 // indirect
|
go.opentelemetry.io/otel v1.40.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/zipkin v1.38.0 // indirect
|
go.opentelemetry.io/otel/exporters/zipkin v1.38.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.38.0 // indirect
|
go.opentelemetry.io/otel/metric v1.40.0 // indirect
|
||||||
go.opentelemetry.io/otel/sdk v1.38.0 // indirect
|
go.opentelemetry.io/otel/sdk v1.40.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.38.0 // indirect
|
go.opentelemetry.io/otel/trace v1.40.0 // indirect
|
||||||
go.opentelemetry.io/proto/otlp v1.8.0 // indirect
|
go.opentelemetry.io/proto/otlp v1.8.0 // indirect
|
||||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||||
golang.org/x/net v0.49.0 // indirect
|
golang.org/x/net v0.51.0 // indirect
|
||||||
golang.org/x/sys v0.40.0 // indirect
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
golang.org/x/text v0.33.0 // indirect
|
golang.org/x/text v0.34.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 // indirect
|
||||||
google.golang.org/grpc v1.75.1 // indirect
|
google.golang.org/grpc v1.75.1 // indirect
|
||||||
|
|||||||
@@ -14,14 +14,14 @@ github.com/VictoriaMetrics/metrics v1.40.1 h1:FrF5uJRpIVj9fayWcn8xgiI+FYsKGMslzP
|
|||||||
github.com/VictoriaMetrics/metrics v1.40.1/go.mod h1:XE4uudAAIRaJE614Tl5HMrtoEU6+GDZO4QTnNSsZRuA=
|
github.com/VictoriaMetrics/metrics v1.40.1/go.mod h1:XE4uudAAIRaJE614Tl5HMrtoEU6+GDZO4QTnNSsZRuA=
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||||
github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
|
github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=
|
||||||
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
|
github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/bluenviron/gortsplib/v5 v5.3.0 h1:uVuCjYTiSnru9ZNF9+DdNjtQgL1Mv6TKlXjdcz/U5ic=
|
github.com/bluenviron/gortsplib/v5 v5.4.0 h1:xi9G4NU67+5uNxGZzJP87SwyaWKr+rUAzbIkOE2SQBo=
|
||||||
github.com/bluenviron/gortsplib/v5 v5.3.0/go.mod h1:0005vOF5SUy6uKqOD+vp11nDYi1y3AzM+ood9DBzCbM=
|
github.com/bluenviron/gortsplib/v5 v5.4.0/go.mod h1:+vGoi2RqF8LA7ktls7nC0JIF3DmOHwj0448kdQGYBEQ=
|
||||||
github.com/bluenviron/mediacommon/v2 v2.7.0 h1:XPj8UQu8iZuytwaeiQvqyDrBmo7VdV2+/ND5zPdgbCY=
|
github.com/bluenviron/mediacommon/v2 v2.8.1 h1:UfR+AxqpL9fl5+KeT5BGklBfWgKS0OaSA7LsL8eVYS8=
|
||||||
github.com/bluenviron/mediacommon/v2 v2.7.0/go.mod h1:5V15TiOfeaNVmZPVuOqAwqQSWyvMV86/dijDKu5q9Zs=
|
github.com/bluenviron/mediacommon/v2 v2.8.1/go.mod h1:4AsE74EnTxkHeUs1VMER31fivU0jufZUAepaKFRV1lM=
|
||||||
github.com/cactus/go-statsd-client/v5 v5.1.0 h1:sbbdfIl9PgisjEoXzvXI1lwUKWElngsjJKaZeC021P4=
|
github.com/cactus/go-statsd-client/v5 v5.1.0 h1:sbbdfIl9PgisjEoXzvXI1lwUKWElngsjJKaZeC021P4=
|
||||||
github.com/cactus/go-statsd-client/v5 v5.1.0/go.mod h1:COEvJ1E+/E2L4q6QE5CkjWPi4eeDw9maJBMIuMPBZbY=
|
github.com/cactus/go-statsd-client/v5 v5.1.0/go.mod h1:COEvJ1E+/E2L4q6QE5CkjWPi4eeDw9maJBMIuMPBZbY=
|
||||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||||
@@ -30,24 +30,30 @@ github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1x
|
|||||||
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs=
|
github.com/charmbracelet/bubbles v1.0.0 h1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5fXonfvc=
|
||||||
github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
|
github.com/charmbracelet/bubbles v1.0.0/go.mod h1:9d/Zd5GdnauMI5ivUIVisuEm3ave1XwXtD1ckyV6r3E=
|
||||||
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
|
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
|
||||||
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
|
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
|
||||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
|
github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk=
|
||||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
|
github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk=
|
||||||
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
|
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
|
||||||
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
|
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
|
||||||
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
||||||
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
||||||
github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ=
|
github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=
|
||||||
github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
|
github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
|
github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q=
|
||||||
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
|
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
|
||||||
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
|
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
|
||||||
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
|
||||||
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
|
||||||
|
github.com/clipperhouse/displaywidth v0.9.0 h1:Qb4KOhYwRiN3viMv1v/3cTBlz3AcAZX3+y9OLhMtAtA=
|
||||||
|
github.com/clipperhouse/displaywidth v0.9.0/go.mod h1:aCAAqTlh4GIVkhQnJpbL0T/WfcrJXHcj8C0yjYcjOZA=
|
||||||
|
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
|
||||||
|
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
|
||||||
|
github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U=
|
||||||
|
github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
|
||||||
github.com/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes=
|
github.com/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes=
|
||||||
github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY=
|
github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY=
|
||||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||||
@@ -112,8 +118,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||||
@@ -122,8 +128,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
|
|||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
|
||||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||||
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
|
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
|
||||||
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||||
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
|
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
|
||||||
@@ -154,10 +160,10 @@ github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
|||||||
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
||||||
github.com/pion/rtcp v1.2.16 h1:fk1B1dNW4hsI78XUCljZJlC4kZOPk67mNRuQ0fcEkSo=
|
github.com/pion/rtcp v1.2.16 h1:fk1B1dNW4hsI78XUCljZJlC4kZOPk67mNRuQ0fcEkSo=
|
||||||
github.com/pion/rtcp v1.2.16/go.mod h1:/as7VKfYbs5NIb4h6muQ35kQF/J0ZVNz2Z3xKoCBYOo=
|
github.com/pion/rtcp v1.2.16/go.mod h1:/as7VKfYbs5NIb4h6muQ35kQF/J0ZVNz2Z3xKoCBYOo=
|
||||||
github.com/pion/rtp v1.10.0 h1:XN/xca4ho6ZEcijpdF2VGFbwuHUfiIMf3ew8eAAE43w=
|
github.com/pion/rtp v1.10.1 h1:xP1prZcCTUuhO2c83XtxyOHJteISg6o8iPsE2acaMtA=
|
||||||
github.com/pion/rtp v1.10.0/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM=
|
github.com/pion/rtp v1.10.1/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM=
|
||||||
github.com/pion/sdp/v3 v3.0.17 h1:9SfLAW/fF1XC8yRqQ3iWGzxkySxup4k4V7yN8Fs8nuo=
|
github.com/pion/sdp/v3 v3.0.18 h1:l0bAXazKHpepazVdp+tPYnrsy9dfh7ZbT8DxesH5ZnI=
|
||||||
github.com/pion/sdp/v3 v3.0.17/go.mod h1:9tyKzznud3qiweZcD86kS0ff1pGYB3VX+Bcsmkx6IXo=
|
github.com/pion/sdp/v3 v3.0.18/go.mod h1:ZREGo6A9ZygQ9XkqAj5xYCQtQpif0i6Pa81HOiAdqQ8=
|
||||||
github.com/pion/srtp/v3 v3.0.10 h1:tFirkpBb3XccP5VEXLi50GqXhv5SKPxqrdlhDCJlZrQ=
|
github.com/pion/srtp/v3 v3.0.10 h1:tFirkpBb3XccP5VEXLi50GqXhv5SKPxqrdlhDCJlZrQ=
|
||||||
github.com/pion/srtp/v3 v3.0.10/go.mod h1:3mOTIB0cq9qlbn59V4ozvv9ClW/BSEbRp4cY0VtaR7M=
|
github.com/pion/srtp/v3 v3.0.10/go.mod h1:3mOTIB0cq9qlbn59V4ozvv9ClW/BSEbRp4cY0VtaR7M=
|
||||||
github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o=
|
github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o=
|
||||||
@@ -176,7 +182,6 @@ github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9Z
|
|||||||
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||||
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
|
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
|
||||||
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
|
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
@@ -197,8 +202,8 @@ github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFA
|
|||||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||||
github.com/urfave/cli/v3 v3.4.1 h1:1M9UOCy5bLmGnuu1yn3t3CB4rG79Rtoxuv1sPhnm6qM=
|
github.com/urfave/cli/v3 v3.7.0 h1:AGSnbUyjtLiM+WJUb4dzXKldl/gL+F8OwmRDtVr6g2U=
|
||||||
github.com/urfave/cli/v3 v3.4.1/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo=
|
github.com/urfave/cli/v3 v3.7.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
|
||||||
github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8=
|
github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8=
|
||||||
github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ=
|
github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ=
|
||||||
github.com/valyala/histogram v1.2.0 h1:wyYGAZZt3CpwUiIb9AU/Zbllg1llXyrtApRS815OLoQ=
|
github.com/valyala/histogram v1.2.0 h1:wyYGAZZt3CpwUiIb9AU/Zbllg1llXyrtApRS815OLoQ=
|
||||||
@@ -211,8 +216,8 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ
|
|||||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
|
||||||
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
|
||||||
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54=
|
||||||
@@ -221,40 +226,40 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F
|
|||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
|
||||||
go.opentelemetry.io/otel/exporters/zipkin v1.38.0 h1:0rJ2TmzpHDG+Ib9gPmu3J3cE0zXirumQcKS4wCoZUa0=
|
go.opentelemetry.io/otel/exporters/zipkin v1.38.0 h1:0rJ2TmzpHDG+Ib9gPmu3J3cE0zXirumQcKS4wCoZUa0=
|
||||||
go.opentelemetry.io/otel/exporters/zipkin v1.38.0/go.mod h1:Su/nq/K5zRjDKKC3Il0xbViE3juWgG3JDoqLumFx5G0=
|
go.opentelemetry.io/otel/exporters/zipkin v1.38.0/go.mod h1:Su/nq/K5zRjDKKC3Il0xbViE3juWgG3JDoqLumFx5G0=
|
||||||
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
|
||||||
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
|
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
|
||||||
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
|
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
|
||||||
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
|
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
|
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
|
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
|
||||||
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
|
||||||
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
|
||||||
go.opentelemetry.io/proto/otlp v1.8.0 h1:fRAZQDcAFHySxpJ1TwlA1cJ4tvcrw7nXl9xWWC8N5CE=
|
go.opentelemetry.io/proto/otlp v1.8.0 h1:fRAZQDcAFHySxpJ1TwlA1cJ4tvcrw7nXl9xWWC8N5CE=
|
||||||
go.opentelemetry.io/proto/otlp v1.8.0/go.mod h1:tIeYOeNBU4cvmPqpaji1P+KbB4Oloai8wN4rWzRrFF0=
|
go.opentelemetry.io/proto/otlp v1.8.0/go.mod h1:tIeYOeNBU4cvmPqpaji1P+KbB4Oloai8wN4rWzRrFF0=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||||
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||||
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||||
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
|
||||||
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
|
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
|
||||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
|
||||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
|
||||||
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
|
||||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||||
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
||||||
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
||||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 h1:d8Nakh1G+ur7+P3GcMjpRDEkoLUcLW2iU92XVqR+XMQ=
|
google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 h1:d8Nakh1G+ur7+P3GcMjpRDEkoLUcLW2iU92XVqR+XMQ=
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ func (a Attacker) attackCredentialsForStream(ctx context.Context, target camerad
|
|||||||
msg := fmt.Sprintf("credential attempt failed for %s:%d (%s:%s): %v", target.Address.String(), target.Port, username, password, err)
|
msg := fmt.Sprintf("credential attempt failed for %s:%d (%s:%s): %v", target.Address.String(), target.Port, username, password, err)
|
||||||
a.reporter.Debug(cameradar.StepAttackCredentials, msg)
|
a.reporter.Debug(cameradar.StepAttackCredentials, msg)
|
||||||
|
|
||||||
return target, nil
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if ok {
|
if ok {
|
||||||
@@ -253,7 +253,7 @@ func (a Attacker) attackRoutesForStream(ctx context.Context, target cameradar.St
|
|||||||
if emitProgress {
|
if emitProgress {
|
||||||
a.reporter.Progress(cameradar.StepAttackRoutes, cameradar.ProgressTickMessage())
|
a.reporter.Progress(cameradar.StepAttackRoutes, cameradar.ProgressTickMessage())
|
||||||
}
|
}
|
||||||
ok, err := a.routeAttack(target, dummyRoute)
|
ok, err := a.routeAttack(ctx, &target, dummyRoute)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.reporter.Debug(cameradar.StepAttackRoutes, fmt.Sprintf("route probe failed for %s:%d: %v", target.Address.String(), target.Port, err))
|
a.reporter.Debug(cameradar.StepAttackRoutes, fmt.Sprintf("route probe failed for %s:%d: %v", target.Address.String(), target.Port, err))
|
||||||
return target, nil
|
return target, nil
|
||||||
@@ -275,7 +275,7 @@ func (a Attacker) attackRoutesForStream(ctx context.Context, target cameradar.St
|
|||||||
if emitProgress {
|
if emitProgress {
|
||||||
a.reporter.Progress(cameradar.StepAttackRoutes, cameradar.ProgressTickMessage())
|
a.reporter.Progress(cameradar.StepAttackRoutes, cameradar.ProgressTickMessage())
|
||||||
}
|
}
|
||||||
ok, err := a.routeAttack(target, route)
|
ok, err := a.routeAttack(ctx, &target, route)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.reporter.Debug(cameradar.StepAttackRoutes, fmt.Sprintf("route attempt failed for %s:%d (%s): %v", target.Address.String(), target.Port, route, err))
|
a.reporter.Debug(cameradar.StepAttackRoutes, fmt.Sprintf("route attempt failed for %s:%d (%s): %v", target.Address.String(), target.Port, route, err))
|
||||||
return target, nil
|
return target, nil
|
||||||
@@ -290,18 +290,30 @@ func (a Attacker) attackRoutesForStream(ctx context.Context, target cameradar.St
|
|||||||
return target, nil
|
return target, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a Attacker) routeAttack(stream cameradar.Stream, route string) (bool, error) {
|
func (a Attacker) routeAttack(ctx context.Context, stream *cameradar.Stream, route string) (bool, error) {
|
||||||
u, urlStr, err := buildRTSPURL(stream, route, stream.Username, stream.Password)
|
u, urlStr, err := buildRTSPURL(*stream, route, stream.Username, stream.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("building rtsp url: %w", err)
|
return false, fmt.Errorf("building rtsp url: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
code, err := a.describeStatus(u)
|
code, headers, err := a.probeDescribeHeaders(ctx, u, urlStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("performing describe request at %q: %w", urlStr, err)
|
return false, fmt.Errorf("performing describe request at %q: %w", urlStr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
a.reporter.Debug(cameradar.StepAttackRoutes, fmt.Sprintf("DESCRIBE %s RTSP/1.0 > %d", urlStr, code))
|
a.reporter.Debug(cameradar.StepAttackRoutes, fmt.Sprintf("DESCRIBE %s RTSP/1.0 > %d", urlStr, code))
|
||||||
|
|
||||||
|
if code == base.StatusMovedPermanently {
|
||||||
|
a.handleRedirect(stream, headers)
|
||||||
|
u, urlStr, err = buildRTSPURL(*stream, route, stream.Username, stream.Password)
|
||||||
|
if err == nil {
|
||||||
|
code, _, err = a.probeDescribeHeaders(ctx, u, urlStr)
|
||||||
|
if err == nil {
|
||||||
|
a.reporter.Debug(cameradar.StepAttackRoutes, fmt.Sprintf("DESCRIBE %s RTSP/1.0 (redirect followed) > %d", urlStr, code))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
access := code == base.StatusOK || code == base.StatusUnauthorized || code == base.StatusForbidden
|
access := code == base.StatusOK || code == base.StatusUnauthorized || code == base.StatusForbidden
|
||||||
return access, nil
|
return access, nil
|
||||||
}
|
}
|
||||||
@@ -314,10 +326,12 @@ func (a Attacker) credAttack(stream cameradar.Stream, username, password string)
|
|||||||
|
|
||||||
code, err := a.describeStatus(u)
|
code, err := a.describeStatus(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
a.reporter.Debug(cameradar.StepAttackCredentials, fmt.Sprintf("Error testing %s:%s -> %v", username, password, err))
|
||||||
return false, fmt.Errorf("performing describe request at %q: %w", urlStr, err)
|
return false, fmt.Errorf("performing describe request at %q: %w", urlStr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
a.reporter.Debug(cameradar.StepAttackCredentials, fmt.Sprintf("DESCRIBE %s RTSP/1.0 > %d", urlStr, code))
|
a.reporter.Debug(cameradar.StepAttackCredentials, fmt.Sprintf("DESCRIBE %s RTSP/1.0 > %d", urlStr, code))
|
||||||
|
|
||||||
return code == base.StatusOK || code == base.StatusNotFound, nil
|
return code == base.StatusOK || code == base.StatusNotFound, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+58
-2
@@ -3,9 +3,11 @@ package attack
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"net/textproto"
|
"net/textproto"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -19,10 +21,16 @@ import (
|
|||||||
"github.com/bluenviron/gortsplib/v5/pkg/liberrors"
|
"github.com/bluenviron/gortsplib/v5/pkg/liberrors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
rtsp = "rtsp"
|
||||||
|
rtsps = "rtsps"
|
||||||
|
)
|
||||||
|
|
||||||
func (a Attacker) newRTSPClient(u *base.URL) (*gortsplib.Client, error) {
|
func (a Attacker) newRTSPClient(u *base.URL) (*gortsplib.Client, error) {
|
||||||
client := &gortsplib.Client{
|
client := &gortsplib.Client{
|
||||||
ReadTimeout: a.timeout,
|
ReadTimeout: a.timeout,
|
||||||
WriteTimeout: a.timeout,
|
WriteTimeout: a.timeout,
|
||||||
|
TLSConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
}
|
}
|
||||||
client.Scheme = u.Scheme
|
client.Scheme = u.Scheme
|
||||||
client.Host = u.Host
|
client.Host = u.Host
|
||||||
@@ -63,7 +71,16 @@ func (a Attacker) describeStatus(u *base.URL) (base.StatusCode, error) {
|
|||||||
// which is exactly what we need in order to detect authentication methods.
|
// which is exactly what we need in order to detect authentication methods.
|
||||||
func (a Attacker) probeDescribeHeaders(ctx context.Context, u *base.URL, urlStr string) (base.StatusCode, base.Header, error) {
|
func (a Attacker) probeDescribeHeaders(ctx context.Context, u *base.URL, urlStr string) (base.StatusCode, base.Header, error) {
|
||||||
dialer := &net.Dialer{Timeout: a.timeout}
|
dialer := &net.Dialer{Timeout: a.timeout}
|
||||||
conn, err := dialer.DialContext(ctx, "tcp", u.Host)
|
var conn net.Conn
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if u.Scheme == rtsps {
|
||||||
|
tlsDialer := &tls.Dialer{NetDialer: dialer, Config: &tls.Config{InsecureSkipVerify: true}}
|
||||||
|
conn, err = tlsDialer.DialContext(ctx, "tcp", u.Host)
|
||||||
|
} else {
|
||||||
|
conn, err = dialer.DialContext(ctx, "tcp", u.Host)
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
@@ -117,6 +134,40 @@ func (a Attacker) probeDescribeHeaders(ctx context.Context, u *base.URL, urlStr
|
|||||||
return base.StatusCode(code), headers, nil
|
return base.StatusCode(code), headers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a Attacker) handleRedirect(stream *cameradar.Stream, resHeaders base.Header) {
|
||||||
|
locations := headerValues(resHeaders, "Location")
|
||||||
|
if len(locations) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
location, err := url.Parse(locations[0])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch location.Scheme {
|
||||||
|
case rtsps:
|
||||||
|
stream.Secure = true
|
||||||
|
case rtsp:
|
||||||
|
stream.Secure = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if location.Hostname() != "" {
|
||||||
|
addr, err := netip.ParseAddr(location.Hostname())
|
||||||
|
if err == nil {
|
||||||
|
stream.Address = addr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if location.Port() != "" {
|
||||||
|
port, err := strconv.Atoi(location.Port())
|
||||||
|
if err == nil {
|
||||||
|
if port >= 0 && port <= 65535 {
|
||||||
|
stream.Port = uint16(port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func authTypeFromHeaders(values base.HeaderValue) cameradar.AuthType {
|
func authTypeFromHeaders(values base.HeaderValue) cameradar.AuthType {
|
||||||
if len(values) == 0 {
|
if len(values) == 0 {
|
||||||
return cameradar.AuthUnknown
|
return cameradar.AuthUnknown
|
||||||
@@ -168,8 +219,13 @@ func buildRTSPURL(stream cameradar.Stream, route, username, password string) (*b
|
|||||||
host := net.JoinHostPort(stream.Address.String(), strconv.Itoa(int(stream.Port)))
|
host := net.JoinHostPort(stream.Address.String(), strconv.Itoa(int(stream.Port)))
|
||||||
path := "/" + strings.TrimLeft(strings.TrimSpace(route), "/") // Ensure path starts with a single "/"
|
path := "/" + strings.TrimLeft(strings.TrimSpace(route), "/") // Ensure path starts with a single "/"
|
||||||
|
|
||||||
|
scheme := rtsp
|
||||||
|
if stream.Secure {
|
||||||
|
scheme = rtsps
|
||||||
|
}
|
||||||
|
|
||||||
u := &url.URL{
|
u := &url.URL{
|
||||||
Scheme: "rtsp",
|
Scheme: scheme,
|
||||||
Host: host,
|
Host: host,
|
||||||
Path: path,
|
Path: path,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ func TestBuildRTSPURL(t *testing.T) {
|
|||||||
route string
|
route string
|
||||||
username string
|
username string
|
||||||
password string
|
password string
|
||||||
|
secure bool
|
||||||
wantURL string
|
wantURL string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
@@ -74,11 +75,27 @@ func TestBuildRTSPURL(t *testing.T) {
|
|||||||
username: "user",
|
username: "user",
|
||||||
wantURL: "rtsp://user:@192.168.0.10:554/stream",
|
wantURL: "rtsp://user:@192.168.0.10:554/stream",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "secure rtsps scheme without credentials",
|
||||||
|
route: "stream",
|
||||||
|
secure: true,
|
||||||
|
wantURL: "rtsps://192.168.0.10:554/stream",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "secure rtsps scheme with credentials",
|
||||||
|
route: "stream",
|
||||||
|
username: "admin",
|
||||||
|
password: "password",
|
||||||
|
secure: true,
|
||||||
|
wantURL: "rtsps://admin:password@192.168.0.10:554/stream",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
_, gotURL, err := buildRTSPURL(stream, test.route, test.username, test.password)
|
s := stream
|
||||||
|
s.Secure = test.secure
|
||||||
|
_, gotURL, err := buildRTSPURL(s, test.route, test.username, test.password)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, test.wantURL, gotURL)
|
require.Equal(t, test.wantURL, gotURL)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -119,5 +119,10 @@ func formatRTSPURL(stream cameradar.Stream) string {
|
|||||||
credentials = stream.Username + ":" + stream.Password + "@"
|
credentials = stream.Username + ":" + stream.Password + "@"
|
||||||
}
|
}
|
||||||
|
|
||||||
return "rtsp://" + credentials + stream.Address.String() + ":" + strconv.FormatUint(uint64(stream.Port), 10) + path
|
scheme := "rtsp"
|
||||||
|
if stream.Secure {
|
||||||
|
scheme = "rtsps"
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s://%s%s:%s%s", scheme, credentials, stream.Address.String(), strconv.FormatUint(uint64(stream.Port), 10), path)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,9 +83,12 @@ func runScan(ctx context.Context, runner Runner, reporter Reporter) ([]cameradar
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isSecure := port.Number == 322 || port.Number == 8322
|
||||||
|
|
||||||
streams = append(streams, cameradar.Stream{
|
streams = append(streams, cameradar.Stream{
|
||||||
Address: addr,
|
Address: addr,
|
||||||
Port: uint16(port.Number),
|
Port: uint16(port.Number),
|
||||||
|
Secure: isSecure,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,15 +53,16 @@ func TestRunScan(t *testing.T) {
|
|||||||
name: "collects streams from multiple hosts",
|
name: "collects streams from multiple hosts",
|
||||||
result: &masscanlib.Run{
|
result: &masscanlib.Run{
|
||||||
Hosts: []masscanlib.Host{
|
Hosts: []masscanlib.Host{
|
||||||
{Address: "192.0.2.10", Ports: []masscanlib.Port{{Number: 8554, Status: "open"}}},
|
{Address: "192.0.2.10", Ports: []masscanlib.Port{{Number: 8554, Status: "open"}, {Number: 8322, Status: "open"}}},
|
||||||
{Address: "198.51.100.9", Ports: []masscanlib.Port{{Number: 554, Status: "open"}}},
|
{Address: "198.51.100.9", Ports: []masscanlib.Port{{Number: 554, Status: "open"}}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wantStreams: []cameradar.Stream{
|
wantStreams: []cameradar.Stream{
|
||||||
{Address: netip.MustParseAddr("192.0.2.10"), Port: 8554},
|
{Address: netip.MustParseAddr("192.0.2.10"), Port: 8554, Secure: false},
|
||||||
{Address: netip.MustParseAddr("198.51.100.9"), Port: 554},
|
{Address: netip.MustParseAddr("192.0.2.10"), Port: 8322, Secure: true},
|
||||||
|
{Address: netip.MustParseAddr("198.51.100.9"), Port: 554, Secure: false},
|
||||||
},
|
},
|
||||||
wantProgress: []string{"Found 2 RTSP streams"},
|
wantProgress: []string{"Found 3 RTSP streams"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "returns error when scan fails",
|
name: "returns error when scan fails",
|
||||||
|
|||||||
@@ -71,6 +71,8 @@ func runScan(ctx context.Context, nmap Runner, reporter Reporter) ([]cameradar.S
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isSecure := strings.Contains(port.Service.Name, "rtsps") || strings.Contains(port.Service.Name, "ssl") || port.ID == 322 || port.ID == 8322
|
||||||
|
|
||||||
for _, address := range host.Addresses {
|
for _, address := range host.Addresses {
|
||||||
addr, err := netip.ParseAddr(address.Addr)
|
addr, err := netip.ParseAddr(address.Addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -82,6 +84,7 @@ func runScan(ctx context.Context, nmap Runner, reporter Reporter) ([]cameradar.S
|
|||||||
Device: port.Service.Product,
|
Device: port.Service.Product,
|
||||||
Address: addr,
|
Address: addr,
|
||||||
Port: port.ID,
|
Port: port.ID,
|
||||||
|
Secure: isSecure,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ func TestScanner_Scan(t *testing.T) {
|
|||||||
Addresses: []nmaplib.Address{{Addr: "192.0.2.10"}, {Addr: "192.0.2.11"}},
|
Addresses: []nmaplib.Address{{Addr: "192.0.2.10"}, {Addr: "192.0.2.11"}},
|
||||||
Ports: []nmaplib.Port{
|
Ports: []nmaplib.Port{
|
||||||
openPort(8554, "rtsp-alt", "Model A"),
|
openPort(8554, "rtsp-alt", "Model A"),
|
||||||
|
openPort(322, "rtsps", "Model C"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nmaplib.Host{
|
nmaplib.Host{
|
||||||
@@ -68,19 +69,34 @@ func TestScanner_Scan(t *testing.T) {
|
|||||||
Device: "Model A",
|
Device: "Model A",
|
||||||
Address: netip.MustParseAddr("192.0.2.10"),
|
Address: netip.MustParseAddr("192.0.2.10"),
|
||||||
Port: 8554,
|
Port: 8554,
|
||||||
|
Secure: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Device: "Model A",
|
Device: "Model A",
|
||||||
Address: netip.MustParseAddr("192.0.2.11"),
|
Address: netip.MustParseAddr("192.0.2.11"),
|
||||||
Port: 8554,
|
Port: 8554,
|
||||||
|
Secure: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Device: "Model C",
|
||||||
|
Address: netip.MustParseAddr("192.0.2.10"),
|
||||||
|
Port: 322,
|
||||||
|
Secure: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Device: "Model C",
|
||||||
|
Address: netip.MustParseAddr("192.0.2.11"),
|
||||||
|
Port: 322,
|
||||||
|
Secure: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Device: "Model B",
|
Device: "Model B",
|
||||||
Address: netip.MustParseAddr("198.51.100.9"),
|
Address: netip.MustParseAddr("198.51.100.9"),
|
||||||
Port: 554,
|
Port: 554,
|
||||||
|
Secure: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wantProgress: "Found 3 RTSP streams",
|
wantProgress: "Found 5 RTSP streams",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "returns error when scan fails",
|
name: "returns error when scan fails",
|
||||||
|
|||||||
@@ -51,9 +51,11 @@ func buildStreamsFromTargets(ctx context.Context, targets, ports []string) ([]ca
|
|||||||
streams := make([]cameradar.Stream, 0, len(resolvedTargets)*len(resolvedPorts))
|
streams := make([]cameradar.Stream, 0, len(resolvedTargets)*len(resolvedPorts))
|
||||||
for _, addr := range resolvedTargets {
|
for _, addr := range resolvedTargets {
|
||||||
for _, port := range resolvedPorts {
|
for _, port := range resolvedPorts {
|
||||||
|
isSecure := port == 322 || port == 8322
|
||||||
streams = append(streams, cameradar.Stream{
|
streams = append(streams, cameradar.Stream{
|
||||||
Address: addr,
|
Address: addr,
|
||||||
Port: port,
|
Port: port,
|
||||||
|
Secure: isSecure,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ func TestNew_ExpandsTargetsAndPorts(t *testing.T) {
|
|||||||
"192.0.2.15",
|
"192.0.2.15",
|
||||||
"192.0.2.10-11",
|
"192.0.2.10-11",
|
||||||
}
|
}
|
||||||
ports := []string{"554", "8554-8555"}
|
ports := []string{"554", "322", "8554-8555"}
|
||||||
|
|
||||||
scanner := skip.New(targets, ports)
|
scanner := skip.New(targets, ports)
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ func TestNew_ExpandsTargetsAndPorts(t *testing.T) {
|
|||||||
netip.MustParseAddr("192.0.2.11"),
|
netip.MustParseAddr("192.0.2.11"),
|
||||||
netip.MustParseAddr("192.0.2.15"),
|
netip.MustParseAddr("192.0.2.15"),
|
||||||
}
|
}
|
||||||
portsExpected := []uint16{554, 8554, 8555}
|
portsExpected := []uint16{554, 322, 8554, 8555}
|
||||||
|
|
||||||
var want []string
|
var want []string
|
||||||
for _, addr := range addrs {
|
for _, addr := range addrs {
|
||||||
@@ -44,6 +44,13 @@ func TestNew_ExpandsTargetsAndPorts(t *testing.T) {
|
|||||||
var got []string
|
var got []string
|
||||||
for _, stream := range streams {
|
for _, stream := range streams {
|
||||||
got = append(got, stream.Address.String()+":"+strconv.Itoa(int(stream.Port)))
|
got = append(got, stream.Address.String()+":"+strconv.Itoa(int(stream.Port)))
|
||||||
|
|
||||||
|
if stream.Port == 322 || stream.Port == 8322 {
|
||||||
|
assert.True(t, stream.Secure)
|
||||||
|
} else {
|
||||||
|
assert.False(t, stream.Secure)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.ElementsMatch(t, want, got)
|
assert.ElementsMatch(t, want, got)
|
||||||
|
|||||||
@@ -126,7 +126,12 @@ func formatRTSPURL(stream cameradar.Stream) string {
|
|||||||
credentials = stream.Username + ":" + stream.Password + "@"
|
credentials = stream.Username + ":" + stream.Password + "@"
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("rtsp://%s%s:%d%s", credentials, stream.Address.String(), stream.Port, path)
|
scheme := "rtsp"
|
||||||
|
if stream.Secure {
|
||||||
|
scheme = "rtsps"
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s://%s%s:%d%s", scheme, credentials, stream.Address.String(), stream.Port, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatAdminPanelURL(stream cameradar.Stream) string {
|
func formatAdminPanelURL(stream cameradar.Stream) string {
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ func TestFormatSummary(t *testing.T) {
|
|||||||
Device: "Model A",
|
Device: "Model A",
|
||||||
Address: netip.MustParseAddr("10.0.0.1"),
|
Address: netip.MustParseAddr("10.0.0.1"),
|
||||||
Port: 8554,
|
Port: 8554,
|
||||||
|
Secure: true,
|
||||||
Available: true,
|
Available: true,
|
||||||
Routes: []string{"stream1", "stream2"},
|
Routes: []string{"stream1", "stream2"},
|
||||||
RouteFound: true,
|
RouteFound: true,
|
||||||
@@ -73,7 +74,7 @@ func TestFormatSummary(t *testing.T) {
|
|||||||
"Authentication: digest",
|
"Authentication: digest",
|
||||||
"Routes: stream1, stream2",
|
"Routes: stream1, stream2",
|
||||||
"Credentials: user:pass",
|
"Credentials: user:pass",
|
||||||
"RTSP URL: rtsp://user:pass@10.0.0.1:8554/stream1",
|
"RTSP URL: rtsps://user:pass@10.0.0.1:8554/stream1",
|
||||||
"Admin panel: http://10.0.0.1/",
|
"Admin panel: http://10.0.0.1/",
|
||||||
"Admin panel: http://10.0.0.2/",
|
"Admin panel: http://10.0.0.2/",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ type Stream struct {
|
|||||||
Address netip.Addr `json:"address" validate:"required"`
|
Address netip.Addr `json:"address" validate:"required"`
|
||||||
Port uint16 `json:"port" validate:"required"`
|
Port uint16 `json:"port" validate:"required"`
|
||||||
|
|
||||||
|
Secure bool `json:"secure"`
|
||||||
|
|
||||||
CredentialsFound bool `json:"credentials_found"`
|
CredentialsFound bool `json:"credentials_found"`
|
||||||
RouteFound bool `json:"route_found"`
|
RouteFound bool `json:"route_found"`
|
||||||
Available bool `json:"available"`
|
Available bool `json:"available"`
|
||||||
|
|||||||
Reference in New Issue
Block a user