google / ExoPlayer

This project is deprecated and stale. The latest ExoPlayer code is available in https://github.com/androidx/media
https://developer.android.com/media/media3/exoplayer
Apache License 2.0
21.74k stars 6.03k forks source link

RTSP Basic Authorization #8941

Closed Pitel closed 3 years ago

Pitel commented 3 years ago

Use case description

I'm trying to connect to RTSP stream with URI: rtsp://admin:heslo123@1.2.3.4:554/Streaming/Channels/101?transportmode=unicast&profile=Profile_1. It doesn't work. I get the following error:

2021-05-14 15:39:05.812 21279-21443 E/ExoPlayerImplInternal: Playback error
      com.google.android.exoplayer2.ExoPlaybackException: Source error
        at com.google.android.exoplayer2.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:580)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:223)
        at android.os.HandlerThread.run(HandlerThread.java:67)
     Caused by: com.google.android.exoplayer2.source.rtsp.RtspMediaSource$RtspPlaybackException: DESCRIBE 401
        at com.google.android.exoplayer2.source.rtsp.RtspMediaSource$SessionInfoListenerImpl.onSessionTimelineRequestFailed(RtspMediaSource.java:219)
        at com.google.android.exoplayer2.source.rtsp.RtspClient$MessageListener.dispatchRtspError(RtspClient.java:529)
        at com.google.android.exoplayer2.source.rtsp.RtspClient$MessageListener.onRtspMessageReceived(RtspClient.java:366)
        at com.google.android.exoplayer2.source.rtsp.RtspMessageChannel$Receiver.lambda$handleRtspMessage$0$RtspMessageChannel$Receiver(RtspMessageChannel.java:291)
        at com.google.android.exoplayer2.source.rtsp.-$$Lambda$RtspMessageChannel$Receiver$HitD0FATwe-gLFkoSjlAUnzETfA.run(Unknown Source:4)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:223) 
        at android.os.HandlerThread.run(HandlerThread.java:67) 
     Caused by: com.google.android.exoplayer2.source.rtsp.RtspMediaSource$RtspPlaybackException: DESCRIBE 401
        at com.google.android.exoplayer2.source.rtsp.RtspClient$MessageListener.onRtspMessageReceived(RtspClient.java:368)
        at com.google.android.exoplayer2.source.rtsp.RtspMessageChannel$Receiver.lambda$handleRtspMessage$0$RtspMessageChannel$Receiver(RtspMessageChannel.java:291) 
        at com.google.android.exoplayer2.source.rtsp.-$$Lambda$RtspMessageChannel$Receiver$HitD0FATwe-gLFkoSjlAUnzETfA.run(Unknown Source:4) 
        at android.os.Handler.handleCallback(Handler.java:938) 
        at android.os.Handler.dispatchMessage(Handler.java:99) 
        at android.os.Looper.loop(Looper.java:223) 
        at android.os.HandlerThread.run(HandlerThread.java:67) 

Indeed, when I debuigged the requests and responses, the Authorization: Basic header is missing. And I couldn't find any way to provide custom headers.

Proposed solution

Automaticaly generate the header based on the URI.

Alternatives considered

Provide a way to add custom headers.

lcf87 commented 3 years ago

Hello Pitel,

Thanks for your interest in RTSP, right now ExoPlayer does not support RTSP auth, but it is something that we have planned. Please stay tuned.

If you are looking to implement auth yourself, take a look at RtspClient.MessageListener. You can customize RtspRequest's header section with RtspHeaders.Builder

claincly commented 3 years ago

Hi Pitel,

Wondered if you could provide a sample source to help us test?

Pitel commented 3 years ago

Hi, sorry, unfortunately I can't. Our testing camera is on the intranet and I'm not aware of any public cams required for testing.

But I can show you the requests (but it should be just the same header as HTTP uses) and reposnses and help you test it.

claincly commented 3 years ago

The requests are useful too, thanks!

You can send us an email at dev.exoplayer@gmail.com using a subject in the format "Issue #8941", if you find it uncomfortable sharing that on Github.

Pitel commented 3 years ago

I guess it's alright sharing it there. It's just a testing camera on the intranet with dummy password.

After a bit of debugging, I find you probably has 2 options:


The first one is to simply use the URI with username and password in it, like this (notice the admin:heslo123@ part):

DESCRIBE rtsp://admin:heslo123@10.2.33.226:554/Streaming/Channels/101?transportmode=unicast&profile=Profile_1 RTSP/1.0
CSeq: 2
User-Agent: ExoPlayerLib/2.10.4 (Media Player for Android)
Accept: application/sdp

RTSP/1.0 200 OK
CSeq: 2
Content-Base: rtsp://admin:heslo123@10.2.33.226:554/Streaming/Channels/101/
Content-Type: application/sdp
Content-Length: 901

<< SDP IS HERE >>

This is what I see when I'm using ExoPlayer from #3854.


Another option is what ffmpeg is doing. It sends the plain request, the camera responds with 401 and some WWW-Authenticate headers which should be used in the following repeated request:

DESCRIBE rtsp://10.2.33.226:554/Streaming/Channels/101?transportmode=unicast&profile=Profile_1 RTSP/1.0
Accept: application/sdp
CSeq: 2
User-Agent: libmpv

RTSP/1.0 401 Unauthorized
CSeq: 2
WWW-Authenticate: Digest realm="2857be52f47f", nonce="f4cba07ad14b5bf181ac77c5a92ba65f", stale="FALSE"
WWW-Authenticate: Basic realm="2857be52f47f"
Date:  Tue, Apr 25 1972 20:27:07 GMT

DESCRIBE rtsp://10.2.33.226:554/Streaming/Channels/101?transportmode=unicast&profile=Profile_1 RTSP/1.0
Accept: application/sdp
CSeq: 3
User-Agent: libmpv
Authorization: Digest username="admin", realm="2857be52f47f", nonce="f4cba07ad14b5bf181ac77c5a92ba65f", uri="rtsp://10.2.33.226:554/Streaming/Channels/101?transportmode=unicast&profile=Profile_1", response="50e0c1696fbccefe96b3a8d02379fad9"    

RTSP/1.0 200 OK
CSeq: 3
Content-Type: application/sdp
Content-Base: rtsp://10.2.33.226:554/Streaming/Channels/101/
Content-Length: 856

<< SDP IS HERE >>

I'm not the expert here, but I suppose the second option is more secure. However, when testing it, I found out that our camera responds 200 OK disregarding the nonce parameter :shrug:.

EduardAblekimov commented 3 years ago

Hey @claincly. I've seen your commit on dev-v2 on basic and digest auth. So, I tried it with my camera that is located on my local network. It just streams an h.264 stream on rtsp. It correctly parses the WWW-Authenticate header and then sends DESCRIBE signal again, but then it just stucks.

After some debugging I found out that it stucks in RtspMessageChannel.handleRtspMessageLine. It just iterates through the InputStream trying to find \r\n (CRLF) until it hits EOF. Therefore there is no video stream.

Now, I am not sure if it's "Test Ready" or not, but if there is anything i can provide, please tell.

I also attached logs below

opening message channel on socket Socket[address=/192.168.1.114,port=554,localPort=43808]

OPTIONS rtsp://192.168.1.114:554/videoMain RTSP/1.0
cseq: 0
user-agent: ExoPlayerLib/2.14.0

RTSP/1.0 200 OK
Allow: OPTIONS, DESCRIBE, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER, TEARDOWN
CSeq: 0
Content-Length: 0

DESCRIBE rtsp://192.168.1.114:554/videoMain RTSP/1.0
cseq: 1
user-agent: ExoPlayerLib/2.14.0

RTSP/1.0 401 Unauthorized
WWW-Authenticate: Digest realm="MyRealm",nonce="90189dc995f6644d"
CSeq: 1
Content-Length: 0

DESCRIBE rtsp://192.168.1.114:554/videoMain RTSP/1.0
cseq: 2
user-agent: ExoPlayerLib/2.14.0
authorization: Digest username="admin", realm="MyRealm", nonce="90189dc995f6644d", uri="rtsp://192.168.1.114:554/videoMain", response="4bc524332a8cc8248cb4dec75b554ebb"

RTSP/1.0 200 OK
Content-Base: rtsp://192.168.1.114:554/videoMain/
Content-Type: application/sdp
CSeq: 2
Content-Length: 303
claincly commented 3 years ago

@k1llrogg Is the log complete?

On a rough look, the DESCRIBE response did not carry the actual SDP media description. I'm not sure if that's because how you logged the message.

Where did you insert the log lines? Is it where I mentioned in the pull request?

EduardAblekimov commented 3 years ago

@claincly I did it in the RtspMessageChannel.addLine(byte[]). More specifically after String line = new String(lineBytes, /* offset= */ 0, /* length= */ lineBytes.length - 2, CHARSET);

Here i just log the lines.

I can also attach a kind of "sequence of actions":

  1. Digest extravaganza, we sent the second DESCRIBE request with the correct authorization header.
  2. We start to parse response in the RtspMessageChannel.addLine(byte[]). We parsed status code, then content-base, content-type, cseq, content-length.
  3. Next line is empty line, therefore we set state = STATE_READING_RTSP_BODY; and return messageLines = null
  4. In RtspMessageChannel.handleRtspMessage(byte) messageLines is still null, because there is also body which is not parsed.
  5. And then it stucks in RtspMessageChannel.handleRtspMessageLine, trying to find \r\n.
claincly commented 3 years ago

Thanks for the detailed comment. Can you try printing out the received bytes in handleRtspMessageLine(), after every dataInputStream.readByte()? My suspicion is your camera (RTSP server) did not insert a CRLF (\r\n) at the end of the message body, although it should (not necessarily a must).

EduardAblekimov commented 3 years ago

I am pretty confident that it doesn't have CRLF at the end.

But for additional information, I logged the bytes I am receiving. There are 700 bytes, but I will include last 10 of them. 114 111 108 58 118 105 100 101 111 10. There is LF, but no CR.

Probably have to use dataInputStream.read() instead and then also check for -1 as EOF signal besides checking for CRLF

EduardAblekimov commented 3 years ago

I advanced a little bit, changed DESCRIBE parser so it splits by LF instead of CRLF, but now it crashes on SessionDescription.Builder.build(), because there is no session name. Now, I am not particularly good with RTSP, so I can't tell if this parameter is really mandatory, but just for the sake of testing, I am sending you the DESCRIBE lines I am receiving.

v=0
o=- 3831122695 3831122695 IN IP4 192.168.1.114
c=IN IP4 192.168.1.114
t=0 0
a=tool:*confidential*
a=recvonly
a=type:broadcast
a=charset:UTF-8
a=control:*
m=video 0 RTP/AVP 96
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1;sprop-parameter-sets=Z00AKpY1QPAET8s3AQEBAg==,aO48gA==
a=control:video
claincly commented 3 years ago

So there are two things:

  1. According to the byte you posted, the last three chars are 'e', 'o', and a LF '\n' (that match the DESCRIBE response you posted in the second comment), indeed the receiver will freeze if it's constantly looking for CRLF. I'll see what we can do on our side.

  2. The SDP (RFC2327) requires a session name (under a s= tag). I can see that the tool attribute in the DESCRIBE is confidential, I am assuming you have control over the server, I would suggest tweak the server a bit to include a session name.

claincly commented 3 years ago

Another thing that pops to my mind,

You said in one of the comments that you received over 700 bytes, but the Content-Length header you posted is only 303 bytes long. Are they the same media?

EduardAblekimov commented 3 years ago

Another thing that pops to my mind,

You said in one of the comments that you received over 700 bytes, but the Content-Length header you posted is only 303 bytes long. Are they the same media?

I was wrong, it equals to content length.

So there are two things:

  1. The SDP (RFC2327) requires a session name (under a s= tag). I can see that the tool attribute in the DESCRIBE is confidential, I am assuming you have control over the server, I would suggest tweak the server a bit to include a session name.

I don't have an exact access to the server, since my server is my client's camera, but i notified my client about it. meanwhile, I will probably drop the session name or assign some arbitrary value

EduardAblekimov commented 3 years ago

@claincly There is one more thing. As you can see in my SDP, there is no profile-level-id in fmtp attribute, which is strictly required in exoplayer. Referring to RFC6184:

If no profile-level-id is present, the Baseline profile, without additional constraints at Level 1, MUST be inferred.

But even better solution would be to parse it from sprop-parameter-sets. If you decode my sprop-parameter-sets to hexa, you will get 67 4d 00 2a 96 35 40 f0 04 4f cb 37 01 01 01 02, where 4d 00 2a is actually profile-level-id

claincly commented 3 years ago

@k1llrogg Thanks for pointing out. Could you post another issue on the profile-level-id though, it is out of this issue's scope.

As for the CRLF line terminator thing, we are working on a solution.

ishaq1994 commented 3 years ago

Use case description

I'm trying to connect to RTSP stream with URI: rtsp://admin:heslo123@1.2.3.4:554/Streaming/Channels/101?transportmode=unicast&profile=Profile_1. It doesn't work. I get the following error:

2021-05-14 15:39:05.812 21279-21443 E/ExoPlayerImplInternal: Playback error
      com.google.android.exoplayer2.ExoPlaybackException: Source error
        at com.google.android.exoplayer2.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:580)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:223)
        at android.os.HandlerThread.run(HandlerThread.java:67)
     Caused by: com.google.android.exoplayer2.source.rtsp.RtspMediaSource$RtspPlaybackException: DESCRIBE 401
        at com.google.android.exoplayer2.source.rtsp.RtspMediaSource$SessionInfoListenerImpl.onSessionTimelineRequestFailed(RtspMediaSource.java:219)
        at com.google.android.exoplayer2.source.rtsp.RtspClient$MessageListener.dispatchRtspError(RtspClient.java:529)
        at com.google.android.exoplayer2.source.rtsp.RtspClient$MessageListener.onRtspMessageReceived(RtspClient.java:366)
        at com.google.android.exoplayer2.source.rtsp.RtspMessageChannel$Receiver.lambda$handleRtspMessage$0$RtspMessageChannel$Receiver(RtspMessageChannel.java:291)
        at com.google.android.exoplayer2.source.rtsp.-$$Lambda$RtspMessageChannel$Receiver$HitD0FATwe-gLFkoSjlAUnzETfA.run(Unknown Source:4)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:223) 
        at android.os.HandlerThread.run(HandlerThread.java:67) 
     Caused by: com.google.android.exoplayer2.source.rtsp.RtspMediaSource$RtspPlaybackException: DESCRIBE 401
        at com.google.android.exoplayer2.source.rtsp.RtspClient$MessageListener.onRtspMessageReceived(RtspClient.java:368)
        at com.google.android.exoplayer2.source.rtsp.RtspMessageChannel$Receiver.lambda$handleRtspMessage$0$RtspMessageChannel$Receiver(RtspMessageChannel.java:291) 
        at com.google.android.exoplayer2.source.rtsp.-$$Lambda$RtspMessageChannel$Receiver$HitD0FATwe-gLFkoSjlAUnzETfA.run(Unknown Source:4) 
        at android.os.Handler.handleCallback(Handler.java:938) 
        at android.os.Handler.dispatchMessage(Handler.java:99) 
        at android.os.Looper.loop(Looper.java:223) 
        at android.os.HandlerThread.run(HandlerThread.java:67) 

Indeed, when I debuigged the requests and responses, the Authorization: Basic header is missing. And I couldn't find any way to provide custom headers.

Proposed solution

Automaticaly generate the header based on the URI.

Alternatives considered

Provide a way to add custom headers.

Hi , I 'm also facing the same issue , so what is the solution . please help !