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>
This commit is contained in:
Lawrence Arryl Lopez
2026-03-18 01:41:20 -04:00
committed by GitHub
parent 14dcb74e89
commit 8531c006d4
15 changed files with 626 additions and 136 deletions
+11 -5
View File
@@ -88,11 +88,9 @@ func formatStream(stream cameradar.Stream) string {
builder.WriteString(" Routes: not found\n")
}
if stream.CredentialsFound {
if stream.CredentialsFound || stream.AuthenticationType == cameradar.AuthNone {
builder.WriteString(" Credentials: ")
builder.WriteString(stream.Username)
builder.WriteString(":")
builder.WriteString(stream.Password)
builder.WriteString(formatCredentials(stream))
builder.WriteString("\n")
} else {
builder.WriteString(" Credentials: not found\n")
@@ -105,7 +103,7 @@ func formatStream(stream cameradar.Stream) string {
builder.WriteString("no\n")
}
if stream.RouteFound && stream.CredentialsFound {
if stream.RouteFound && (stream.CredentialsFound || stream.AuthenticationType == cameradar.AuthNone) {
builder.WriteString(" RTSP URL: ")
builder.WriteString(formatRTSPURL(stream))
builder.WriteString("\n")
@@ -133,6 +131,14 @@ func formatAdminPanelURL(stream cameradar.Stream) string {
return fmt.Sprintf("http://%s/", stream.Address.String())
}
func formatCredentials(stream cameradar.Stream) string {
if stream.Username == "" && stream.Password == "" {
return "none"
}
return stream.Username + ":" + stream.Password
}
func authTypeLabel(auth cameradar.AuthType) string {
switch auth {
case cameradar.AuthNone:
+24 -1
View File
@@ -73,18 +73,41 @@ func TestFormatSummary(t *testing.T) {
"Authentication: digest",
"Routes: stream1, stream2",
"Credentials: user:pass",
"Credentials: none",
"RTSP URL: rtsp://user:pass@10.0.0.1:8554/stream1",
"Admin panel: http://10.0.0.1/",
"Admin panel: http://10.0.0.2/",
},
wantNotContains: []string{
"RTSP URL: rtsp://10.0.0.2",
"Error:",
},
orderedPairs: [][2]string{
{"• 10.0.0.1:8554", "• 10.0.0.2:554"},
},
},
{
name: "empty discovered credentials render as none",
streams: []cameradar.Stream{
{
Address: netip.MustParseAddr("10.0.0.4"),
Port: 554,
Available: true,
RouteFound: true,
Routes: []string{"stream"},
CredentialsFound: true,
AuthenticationType: cameradar.AuthNone,
},
},
wantContains: []string{
"Accessible streams: 1",
"Credentials: none",
"RTSP URL: rtsp://10.0.0.4:554/stream",
},
wantNotContains: []string{
"Credentials: :",
"rtsp://:@10.0.0.4:554/stream",
},
},
}
for _, test := range tests {
+1 -1
View File
@@ -542,7 +542,7 @@ func buildSummaryRows(streams []cameradar.Stream, visibility summaryVisibilitySt
credentials := emptyEntry
if visibility.showCredentials && stream.CredentialsFound {
credentials = fmt.Sprintf("%s:%s", stream.Username, stream.Password)
credentials = formatCredentials(stream)
}
available := emptyEntry