1 /*****************************************************************************
2 * satip.c: SAT>IP input module
3 *****************************************************************************
4 * Copyright © 2016 VLC authors and VideoLAN
5 * Copyright © 2016 jusst technologies GmbH
6 * Copyright © 2016 Videolabs SAS
7 * Copyright © 2016 Julian Scheel
9 * Authors: Julian Scheel <julian@jusst.de>
11 * This program is free software; you can redistribute it and/or modify it
12 * under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation; either version 2.1 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU Lesser General Public License for more details.
21 * You should have received a copy of the GNU Lesser General Public License
22 * along with this program; if not, write to the Free Software Foundation,
23 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24 *****************************************************************************/
34 #include <vlc_common.h>
35 #include <vlc_plugin.h>
36 #include <vlc_access.h>
37 #include <vlc_network.h>
38 #include <vlc_block.h>
41 #include <vlc_interrupt.h>
50 #define RTSP_DEFAULT_PORT 554
51 #define RTSP_RECEIVE_BUFFER 2048
52 #define RTP_HEADER_SIZE 12
54 #define KEEPALIVE_INTERVAL 60
55 #define KEEPALIVE_MARGIN 5
57 static int satip_open(vlc_object_t
*);
58 static void satip_close(vlc_object_t
*);
60 #define BUFFER_TEXT N_("Receive buffer")
61 #define BUFFER_LONGTEXT N_("UDP receive buffer size (bytes)")
63 #define MULTICAST_TEXT N_("Request multicast stream")
64 #define MULTICAST_LONGTEXT N_("Request server to send stream as multicast")
66 #define SATIP_HOST_TEXT N_("Host")
69 set_shortname("satip")
70 set_description( N_("SAT>IP Receiver Plugin") )
71 set_capability("access", 201)
72 set_callbacks(satip_open
, satip_close
)
73 set_category(CAT_INPUT
)
74 set_subcategory(SUBCAT_INPUT_ACCESS
)
75 add_integer("satip-buffer", 0x400000, BUFFER_TEXT
, BUFFER_LONGTEXT
, true)
76 add_bool("satip-multicast", false, MULTICAST_TEXT
, MULTICAST_LONGTEXT
, true)
77 add_string("satip-host", "", SATIP_HOST_TEXT
, SATIP_HOST_TEXT
, true)
79 add_shortcut("rtsp", "satip")
94 #define UDP_ADDRESS_LEN 16
95 typedef struct access_sys_t
{
100 int keepalive_interval
;
102 char udp_address
[UDP_ADDRESS_LEN
];
109 enum rtsp_state state
;
115 uint16_t last_seq_nr
;
120 static void parse_session(char *request_line
, char *session
, unsigned max
, int *timeout
) {
124 tok
= strtok_r(request_line
, ";", &state
);
127 strncpy(session
, tok
, __MIN(strlen(tok
), max
- 1));
129 while ((tok
= strtok_r(NULL
, ";", &state
)) != NULL
) {
130 if (strncmp(tok
, "timeout=", 8) == 0) {
131 *timeout
= atoi(tok
+ 8);
133 *timeout
-= KEEPALIVE_MARGIN
;
134 else if (*timeout
> 0)
140 static int parse_port(char *str
, uint16_t *port
)
143 if (p
< 0 || p
> UINT16_MAX
)
151 static int parse_transport(access_t
*access
, char *request_line
) {
152 access_sys_t
*sys
= access
->p_sys
;
157 tok
= strtok_r(request_line
, ";", &state
);
158 if (tok
== NULL
|| strncmp(tok
, "RTP/AVP", 7) != 0)
161 tok
= strtok_r(NULL
, ";", &state
);
162 if (tok
== NULL
|| strncmp(tok
, "multicast", 9) != 0)
165 while ((tok
= strtok_r(NULL
, ";", &state
)) != NULL
) {
166 if (strncmp(tok
, "destination=", 12) == 0) {
167 strncpy(sys
->udp_address
, tok
+ 12, __MIN(strlen(tok
+ 12), UDP_ADDRESS_LEN
- 1));
168 } else if (strncmp(tok
, "port=", 5) == 0) {
172 memset(port
, 0x00, 6);
173 strncpy(port
, tok
+ 5, __MIN(strlen(tok
+ 5), 5));
174 if ((end
= strstr(port
, "-")) != NULL
)
176 err
= parse_port(port
, &sys
->udp_port
);
186 * Semi-interruptible net_Gets replacement.
187 * If an interruption is occuring it will fallback to non-interruptible read
188 * with a given timeout before it returns.
190 * interrupted: Informs the caller whether an interrupt occured or not
192 static char *net_readln_timeout(vlc_object_t
*obj
, int fd
, int timeout
, bool *interrupted
)
195 size_t size
= 0, len
= 0;
202 if (unlikely(size
>= (1 << 16)))
205 goto error
; /* put sane buffer size limit */
208 char *newbuf
= realloc(buf
, size
+ 1024);
209 if (unlikely(newbuf
== NULL
))
218 val
= vlc_recv_i11e(fd
, buf
+ len
, size
- len
, MSG_PEEK
);
219 if (val
<= 0 && errno
== EINTR
) {
228 struct pollfd pfd
= {
234 while((ret
= poll(&pfd
, 1, timeout
)) < 0);
238 val
= recv(fd
, buf
+ len
, size
- len
, MSG_PEEK
);
243 char *end
= memchr(buf
+ len
, '\n', val
);
245 val
= (end
+ 1) - (buf
+ len
);
246 if (recv(fd
, buf
+ len
, val
, 0) != val
)
255 if (len
> 0 && buf
[--len
] == '\r')
259 msg_Err(obj
, "read error: %s", vlc_strerror_c(errno
));
264 #define skip_whitespace(x) while(*x == ' ') x++
265 static enum rtsp_result
rtsp_handle(access_t
*access
, bool *interrupted
) {
266 access_sys_t
*sys
= access
->p_sys
;
269 bool have_header
= false;
270 size_t content_length
= 0;
275 while (!have_header
) {
276 in
= net_readln_timeout((vlc_object_t
*)access
, sys
->tcp_sock
, 5000,
281 if (strncmp(in
, "RTSP/1.0 ", 9) == 0) {
282 rtsp_result
= atoi(in
+ 9);
283 } else if (strncmp(in
, "Content-Base:", 13) == 0) {
284 free(sys
->content_base
);
287 skip_whitespace(val
);
289 sys
->content_base
= strdup(val
);
290 } else if (strncmp(in
, "Content-Length:", 15) == 0) {
292 skip_whitespace(val
);
294 content_length
= atoi(val
);
295 } else if (strncmp("Session:", in
, 8) == 0) {
297 skip_whitespace(val
);
299 parse_session(val
, sys
->session_id
, 64, &sys
->keepalive_interval
);
300 } else if (strncmp("Transport:", in
, 10) == 0) {
302 skip_whitespace(val
);
304 if (parse_transport(access
, val
) != 0) {
305 rtsp_result
= VLC_EGENERIC
;
308 } else if (strncmp("com.ses.streamID:", in
, 17) == 0) {
310 skip_whitespace(val
);
312 sys
->stream_id
= atoi(val
);
313 } else if (in
[0] == '\0') {
320 /* Discard further content */
321 while (content_length
> 0 &&
322 (read
= net_Read(access
, sys
->tcp_sock
, buffer
, __MIN(sizeof(buffer
), content_length
))))
323 content_length
-= read
;
329 static void satip_cleanup_blocks(void *data
)
331 block_t
**input_blocks
= data
;
333 for (size_t i
= 0; i
< VLEN
; i
++)
334 if (input_blocks
[i
] != NULL
)
335 block_Release(input_blocks
[i
]);
339 static int check_rtp_seq(access_t
*access
, block_t
*block
)
341 access_sys_t
*sys
= access
->p_sys
;
342 uint16_t seq_nr
= block
->p_buffer
[2] << 8 | block
->p_buffer
[3];
344 if (seq_nr
== sys
->last_seq_nr
) {
345 msg_Warn(access
, "Received duplicate packet (seq_nr=%"PRIu16
")", seq_nr
);
347 } else if (seq_nr
< (uint16_t)(sys
->last_seq_nr
+ 1)) {
348 msg_Warn(access
, "Received out of order packet (seq_nr=%"PRIu16
" < %"PRIu16
")",
349 seq_nr
, sys
->last_seq_nr
);
351 } else if (++sys
->last_seq_nr
> 1 && seq_nr
> sys
->last_seq_nr
) {
352 msg_Warn(access
, "Gap in seq_nr (%"PRIu16
" > %"PRIu16
"), probably lost a packet",
353 seq_nr
, sys
->last_seq_nr
);
356 sys
->last_seq_nr
= seq_nr
;
360 static void satip_teardown(void *data
) {
361 access_t
*access
= data
;
362 access_sys_t
*sys
= access
->p_sys
;
365 if (sys
->tcp_sock
> 0) {
366 if (sys
->session_id
[0] > 0) {
367 char discard_buf
[32];
368 struct pollfd pfd
= {
374 ssize_t len
= asprintf(&msg
, "TEARDOWN %s RTSP/1.0\r\n"
376 "Session: %s\r\n\r\n",
377 sys
->control
, sys
->cseq
++, sys
->session_id
);
381 /* make socket non-blocking, to avoid blocking when output buffer
382 * has not enough space */
384 fcntl(sys
->tcp_sock
, F_SETFL
, fcntl(sys
->tcp_sock
, F_GETFL
) | O_NONBLOCK
);
386 ioctlsocket(sys
->tcp_sock
, FIONBIO
, &(unsigned long){ 1 });
389 for (unsigned sent
= 0; sent
< len
;) {
390 ret
= poll(&pfd
, 1, 5000);
392 msg_Err(access
, "Timed out sending RTSP teardown\n");
397 ret
= send(sys
->tcp_sock
, msg
+ sent
, len
, MSG_NOSIGNAL
);
399 msg_Err(access
, "Failed to send RTSP teardown: %d\n", ret
);
407 if (rtsp_handle(access
, NULL
) != RTSP_RESULT_OK
) {
408 msg_Err(access
, "Failed to teardown RTSP session");
412 /* Some SATIP servers send a few empty extra bytes after TEARDOWN.
413 * Try to read them, to avoid a TCP socket reset */
414 while ((len
= recv(sys
->tcp_sock
, discard_buf
, sizeof(discard_buf
), 0) > 0));
416 /* Extra sleep for compatibility with some satip servers, that
417 * can't handle new sessions right after teardown */
423 #define RECV_TIMEOUT 2 * 1000 * 1000
424 static void *satip_thread(void *data
) {
425 access_t
*access
= data
;
426 access_sys_t
*sys
= access
->p_sys
;
427 int sock
= sys
->udp_sock
;
428 mtime_t last_recv
= mdate();
430 mtime_t next_keepalive
= mdate() + sys
->keepalive_interval
* 1000 * 1000;
432 struct mmsghdr msgs
[VLEN
];
433 struct iovec iovecs
[VLEN
];
434 block_t
*input_blocks
[VLEN
];
437 for (size_t i
= 0; i
< VLEN
; i
++) {
438 memset(&msgs
[i
], 0, sizeof (msgs
[i
]));
439 msgs
[i
].msg_hdr
.msg_iov
= &iovecs
[i
];
440 msgs
[i
].msg_hdr
.msg_iovlen
= 1;
441 iovecs
[i
].iov_base
= NULL
;
442 iovecs
[i
].iov_len
= RTSP_RECEIVE_BUFFER
;
443 input_blocks
[i
] = NULL
;
452 while (last_recv
> mdate() - RECV_TIMEOUT
) {
454 for (size_t i
= 0; i
< VLEN
; i
++) {
455 if (input_blocks
[i
] != NULL
)
458 input_blocks
[i
] = block_Alloc(RTSP_RECEIVE_BUFFER
);
459 if (unlikely(input_blocks
[i
] == NULL
))
462 iovecs
[i
].iov_base
= input_blocks
[i
]->p_buffer
;
465 vlc_cleanup_push(satip_cleanup_blocks
, input_blocks
);
466 retval
= recvmmsg(sock
, msgs
, VLEN
, MSG_WAITFORONE
, NULL
);
472 for (int i
= 0; i
< retval
; ++i
) {
473 block_t
*block
= input_blocks
[i
];
475 len
= msgs
[i
].msg_len
;
476 if (check_rtp_seq(access
, block
))
479 block
->p_buffer
+= RTP_HEADER_SIZE
;
480 block
->i_buffer
= len
- RTP_HEADER_SIZE
;
481 block_FifoPut(sys
->fifo
, block
);
482 input_blocks
[i
] = NULL
;
485 if (poll(&ufd
, 1, 20) == -1)
488 block_t
*block
= block_Alloc(RTSP_RECEIVE_BUFFER
);
490 msg_Err(access
, "Failed to allocate memory for input buffer");
494 block_cleanup_push(block
);
495 len
= recv(sock
, block
->p_buffer
, RTSP_RECEIVE_BUFFER
, 0);
498 if (len
< RTP_HEADER_SIZE
) {
499 block_Release(block
);
503 if (check_rtp_seq(access
, block
)) {
504 block_Release(block
);
508 block
->p_buffer
+= RTP_HEADER_SIZE
;
509 block
->i_buffer
= len
- RTP_HEADER_SIZE
;
510 block_FifoPut(sys
->fifo
, block
);
513 if (sys
->keepalive_interval
> 0 && mdate() > next_keepalive
) {
514 net_Printf(access
, sys
->tcp_sock
,
515 "OPTIONS %s RTSP/1.0\r\n"
517 "Session: %s\r\n\r\n",
518 sys
->control
, sys
->cseq
++, sys
->session_id
);
519 if (rtsp_handle(access
, NULL
) != RTSP_RESULT_OK
)
520 msg_Warn(access
, "Failed to keepalive RTSP session");
522 next_keepalive
= mdate() + sys
->keepalive_interval
* 1000 * 1000;
527 satip_cleanup_blocks(input_blocks
);
529 msg_Dbg(access
, "timed out waiting for data...");
530 vlc_fifo_Lock(sys
->fifo
);
532 vlc_fifo_Signal(sys
->fifo
);
533 vlc_fifo_Unlock(sys
->fifo
);
538 static block_t
* satip_block(access_t
*access
, bool *restrict eof
) {
539 access_sys_t
*sys
= access
->p_sys
;
542 vlc_fifo_Lock(sys
->fifo
);
544 while (vlc_fifo_IsEmpty(sys
->fifo
)) {
547 vlc_fifo_Wait(sys
->fifo
);
550 if ((block
= vlc_fifo_DequeueUnlocked(sys
->fifo
)) == NULL
)
553 vlc_fifo_Unlock(sys
->fifo
);
558 static int satip_control(access_t
*access
, int i_query
, va_list args
) {
564 case STREAM_CAN_CONTROL_PACE
:
565 case STREAM_CAN_SEEK
:
566 case STREAM_CAN_PAUSE
:
567 pb_bool
= va_arg(args
, bool *);
571 case STREAM_GET_PTS_DELAY
:
572 pi_64
= va_arg(args
, int64_t *);
573 *pi_64
= INT64_C(1000) * var_InheritInteger(access
, "live-caching");
583 /* Bind two adjacent free ports, of which the first one is even (for RTP data)
584 * and the second is odd (RTCP). This is a requirement of the satip
586 static int satip_bind_ports(access_t
*access
)
588 access_sys_t
*sys
= access
->p_sys
;
591 vlc_rand_bytes(&rnd
, 1);
592 sys
->udp_port
= 9000 + (rnd
* 2); /* randomly chosen, even start point */
593 while (sys
->udp_sock
< 0) {
594 sys
->udp_sock
= net_OpenDgram(access
, "0.0.0.0", sys
->udp_port
, NULL
,
596 if (sys
->udp_sock
< 0) {
597 if (sys
->udp_port
== 65534)
604 sys
->rtcp_sock
= net_OpenDgram(access
, "0.0.0.0", sys
->udp_port
+ 1, NULL
,
606 if (sys
->rtcp_sock
< 0) {
607 close(sys
->udp_sock
);
613 if (sys
->udp_sock
< 0) {
614 msg_Err(access
, "Could not open two adjacent ports for RTP and RTCP data");
621 static int satip_open(vlc_object_t
*obj
)
623 access_t
*access
= (access_t
*)obj
;
627 bool multicast
= var_InheritBool(access
, "satip-multicast");
629 access
->p_sys
= sys
= calloc(1, sizeof(*sys
));
633 msg_Dbg(access
, "try to open '%s'", access
->psz_url
);
635 char *psz_host
= var_InheritString(access
, "satip-host");
640 /* convert url to lowercase, some famous m3u playlists for satip contain
641 * uppercase parameters while most (all?) satip servers do only understand
642 * parameters matching lowercase spelling as defined in the specification
644 char *psz_lower_url
= strdup(access
->psz_url
);
645 if (psz_lower_url
== NULL
)
648 for (unsigned i
= 0; i
< strlen(psz_lower_url
); i
++)
649 psz_lower_url
[i
] = tolower(psz_lower_url
[i
]);
651 vlc_UrlParse(&url
, psz_lower_url
);
653 url
.i_port
= RTSP_DEFAULT_PORT
;
654 if (psz_host
== NULL
&& url
.psz_host
)
655 psz_host
= strdup(url
.psz_host
);
656 if (psz_host
== NULL
)
659 if (url
.psz_host
== NULL
|| url
.psz_host
[0] == '\0')
661 msg_Dbg(access
, "malformed URL: %s", psz_lower_url
);
665 msg_Dbg(access
, "connect to host '%s'", psz_host
);
666 sys
->tcp_sock
= net_ConnectTCP(access
, psz_host
, url
.i_port
);
667 if (sys
->tcp_sock
< 0) {
668 msg_Err(access
, "Failed to connect to RTSP server %s:%d",
669 psz_host
, url
.i_port
);
672 setsockopt (sys
->tcp_sock
, SOL_SOCKET
, SO_KEEPALIVE
, &(int){ 1 }, sizeof (int));
674 if (asprintf(&sys
->content_base
, "rtsp://%s:%d/", psz_host
,
676 sys
->content_base
= NULL
;
680 sys
->last_seq_nr
= 0;
681 sys
->keepalive_interval
= (KEEPALIVE_INTERVAL
- KEEPALIVE_MARGIN
);
683 vlc_url_t setup_url
= url
;
685 // substitute "sat.ip" if present with an the host IP that was fetched during device discovery
686 if( !strncasecmp( setup_url
.psz_host
, "sat.ip", 6 ) ) {
687 setup_url
.psz_host
= psz_host
;
690 // reverse the satip protocol trick, as SAT>IP believes to be RTSP
691 if( setup_url
.psz_protocol
== NULL
||
692 strncasecmp( setup_url
.psz_protocol
, "satip", 5 ) == 0 )
694 setup_url
.psz_protocol
= (char *)"rtsp";
697 char *psz_setup_url
= vlc_uri_compose(&setup_url
);
698 if( psz_setup_url
== NULL
)
702 net_Printf(access
, sys
->tcp_sock
,
703 "SETUP %s RTSP/1.0\r\n"
705 "Transport: RTP/AVP;multicast\r\n\r\n",
706 psz_setup_url
, sys
->cseq
++);
708 /* open UDP socket to acquire a free port to use */
709 if (satip_bind_ports(access
)) {
714 net_Printf(access
, sys
->tcp_sock
,
715 "SETUP %s RTSP/1.0\r\n"
717 "Transport: RTP/AVP;unicast;client_port=%d-%d\r\n\r\n",
718 psz_setup_url
, sys
->cseq
++, sys
->udp_port
, sys
->udp_port
+ 1);
722 bool interrupted
= false;
723 if (rtsp_handle(access
, &interrupted
) != RTSP_RESULT_OK
) {
724 msg_Err(access
, "Failed to setup RTSP session");
728 if (asprintf(&sys
->control
, "%sstream=%d", sys
->content_base
, sys
->stream_id
) < 0) {
734 msg_Warn(access
, "SETUP was interrupted, abort startup");
738 /* Extra sleep for compatibility with some satip servers, that
739 * can't handle PLAY right after SETUP */
740 if (vlc_msleep_i11e(50000) < 0)
743 /* Open UDP socket for reading if not done */
745 sys
->udp_sock
= net_OpenDgram(access
, sys
->udp_address
, sys
->udp_port
, "", sys
->udp_port
, IPPROTO_UDP
);
746 if (sys
->udp_sock
< 0) {
747 msg_Err(access
, "Failed to open UDP socket for listening.");
751 sys
->rtcp_sock
= net_OpenDgram(access
, sys
->udp_address
, sys
->udp_port
+ 1, "", sys
->udp_port
+ 1, IPPROTO_UDP
);
752 if (sys
->rtcp_sock
< 0) {
753 msg_Err(access
, "Failed to open RTCP socket for listening.");
758 net_Printf(access
, sys
->tcp_sock
,
759 "PLAY %s RTSP/1.0\r\n"
761 "Session: %s\r\n\r\n",
762 sys
->control
, sys
->cseq
++, sys
->session_id
);
764 if (rtsp_handle(access
, NULL
) != RTSP_RESULT_OK
) {
765 msg_Err(access
, "Failed to play RTSP session");
769 sys
->fifo
= block_FifoNew();
771 msg_Err(access
, "Failed to allocate block fifo.");
774 sys
->fifo_size
= var_InheritInteger(access
, "satip-buffer");
776 if (vlc_clone(&sys
->thread
, satip_thread
, access
, VLC_THREAD_PRIORITY_INPUT
)) {
777 msg_Err(access
, "Failed to create worker thread.");
781 access
->pf_control
= satip_control
;
782 access
->pf_block
= satip_block
;
793 satip_teardown(access
);
796 block_FifoRelease(sys
->fifo
);
797 if (sys
->udp_sock
>= 0)
798 net_Close(sys
->udp_sock
);
799 if (sys
->rtcp_sock
>= 0)
800 net_Close(sys
->rtcp_sock
);
801 if (sys
->tcp_sock
>= 0)
802 net_Close(sys
->tcp_sock
);
804 free(sys
->content_base
);
810 static void satip_close(vlc_object_t
*obj
)
812 access_t
*access
= (access_t
*)obj
;
813 access_sys_t
*sys
= access
->p_sys
;
815 vlc_cancel(sys
->thread
);
816 vlc_join(sys
->thread
, NULL
);
818 satip_teardown(access
);
820 block_FifoRelease(sys
->fifo
);
821 net_Close(sys
->udp_sock
);
822 net_Close(sys
->rtcp_sock
);
823 net_Close(sys
->tcp_sock
);
824 free(sys
->content_base
);