Files
cameradar/internal/scan/masscan/scanner.go
T
Lawrence Arryl Lopez 8531c006d4 feat: support http tunneled rtsp (#419)
* enhancement: supporting http tunneled rtsp

* refactor: simplify HTTP tunnel support per review feedback

- Extract streamCandidate() for nmap port classification
- Add isCommonHTTPPort() for masscan and nmap fallback
- Move URL building to Stream.String() and Stream.URL()
- Pass Stream directly to attack methods instead of individual args
- Add TLS config for HTTPS tunnel support
- Make auth detection non-fatal for tunneled streams
- Rename HTTPTunnel to UseHTTPTunnel

* - Testing the auth workflow for the tunneled streams is not blocking the rest of the pipeline since I changed the return values to Auth unknown and nil
- added extra ports in the test according to suggestions

* fixing some lint errors

* removing the unused buildrtspurl

* delayed the urlstream call for clarity

removed error messages

refactored the test that used the deprecated buildTRSPurl to use stream.URL and stream.String() methods

* extracting iscommonHTTP port to pkg/ports (package ports)

switching on u.scheme to create proper schemes for http and https

* refactor: replace HTTP tunnel bool with scheme-based detection; enable TLS only for HTTPS-tunneled streams

* chore: simnplify InferTunnelScheme and newRTSPClient

* fix: remove rendundant check in streamCandidate

* fix: typo in parseScheme

* tests: coverage for new schemes

* fix: use RTSP and not RTSPS for HTTPS URLs

* fix: tunneled RTSP scheme handling and auth detection fallback

* ui: render empty credentials as none in summary and TUI

* chore: ignore duplicate-string warning for none literal

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* fix: rtsps probe headers

---------

Co-authored-by: Brendan Le Glaunec <brendan@glaulabs.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-03-18 06:41:20 +01:00

114 lines
2.8 KiB
Go

package masscan
import (
"context"
"fmt"
"net/netip"
"strings"
"github.com/Ullaakut/cameradar/v6"
"github.com/Ullaakut/cameradar/v6/pkg/ports"
masscanlib "github.com/Ullaakut/masscan"
)
// Reporter reports scan progress and debug information.
type Reporter interface {
Debug(step cameradar.Step, message string)
Progress(step cameradar.Step, message string)
}
// Runner is something that can run a masscan scan.
type Runner interface {
Run(ctx context.Context) (*masscanlib.Run, error)
}
// Scanner scans targets and ports for RTSP streams.
type Scanner struct {
runner Runner
reporter Reporter
}
// New returns a Scanner configured with the provided targets and ports.
func New(targets, ports []string, reporter Reporter) (*Scanner, error) {
runner, err := masscanlib.NewScanner(
masscanlib.WithTargets(targets...),
masscanlib.WithPorts(ports...),
masscanlib.WithOpenOnly(),
)
if err != nil {
return nil, fmt.Errorf("creating masscan scanner: %w", err)
}
return &Scanner{
runner: runner,
reporter: reporter,
}, nil
}
// Scan discovers RTSP streams on the configured targets and ports.
func (s *Scanner) Scan(ctx context.Context) ([]cameradar.Stream, error) {
return runScan(ctx, s.runner, s.reporter)
}
func runScan(ctx context.Context, runner Runner, reporter Reporter) ([]cameradar.Stream, error) {
results, err := runner.Run(ctx)
if err != nil {
return nil, fmt.Errorf("scanning network: %w", err)
}
for _, warning := range results.Warnings() {
reporter.Debug(cameradar.StepScan, "masscan warning: "+warning)
}
var streams []cameradar.Stream
for _, host := range results.Hosts {
address := strings.TrimSpace(host.Address)
if address == "" {
reporter.Progress(cameradar.StepScan, "Skipping host with empty address")
continue
}
addr, err := netip.ParseAddr(address)
if err != nil {
reporter.Progress(cameradar.StepScan, fmt.Sprintf("Skipping invalid address %q: %v", host.Address, err))
continue
}
for _, port := range host.Ports {
if port.Status != "open" {
continue
}
if port.Number <= 0 || port.Number > 65535 {
reporter.Progress(cameradar.StepScan, fmt.Sprintf("Skipping invalid port %d on %s", port.Number, host.Address))
continue
}
scheme := ports.InferTunnelScheme(uint16(port.Number), "")
streams = append(streams, cameradar.Stream{
Address: addr,
Port: uint16(port.Number),
Scheme: scheme,
})
}
}
reporter.Progress(cameradar.StepScan, fmt.Sprintf("Found %d RTSP streams", len(streams)))
updateSummary(reporter, streams)
return streams, nil
}
type summaryUpdater interface {
UpdateSummary(streams []cameradar.Stream)
}
func updateSummary(reporter Reporter, streams []cameradar.Stream) {
updater, ok := reporter.(summaryUpdater)
if !ok {
return
}
updater.UpdateSummary(streams)
}