/* Add a SAP announce */ static int announce_SAPAnnounceAdd( sap_handler_t *p_sap, session_descriptor_t *p_session, announce_method_t *p_method ) { int i; char *psz_type = "application/sdp"; int i_header_size; char *psz_head; vlc_bool_t b_found = VLC_FALSE; sap_session_t *p_sap_session; mtime_t i_hash; vlc_mutex_lock( &p_sap->object_lock ); /* If needed, build the SDP */ if( !p_session->psz_sdp ) { if ( SDPGenerate( p_sap, p_session ) != VLC_SUCCESS ) { vlc_mutex_unlock( &p_sap->object_lock ); return VLC_EGENERIC; } } if( !p_method->psz_address ) { if( p_method->i_ip_version == 6 ) { char sz_scope; if( p_method->psz_ipv6_scope != NULL ) { sz_scope = *p_method->psz_ipv6_scope; } else { sz_scope = DEFAULT_IPV6_SCOPE; } p_method->psz_address = (char*)malloc( 30*sizeof(char )); sprintf( p_method->psz_address, "%s%c%s", SAP_IPV6_ADDR_1, sz_scope, SAP_IPV6_ADDR_2 ); } else { /* IPv4 */ p_method->psz_address = (char*)malloc( 15*sizeof(char) ); snprintf(p_method->psz_address, 15, SAP_IPV4_ADDR ); } } msg_Dbg( p_sap, "using SAP address: %s",p_method->psz_address); /* XXX: Check for dupes */ p_sap_session = (sap_session_t*)malloc(sizeof(sap_session_t)); p_sap_session->psz_sdp = strdup( p_session->psz_sdp ); p_sap_session->i_last = 0; /* Add the address to the buffer */ for( i = 0; i< p_sap->i_addresses; i++) { if( !strcmp( p_method->psz_address, p_sap->pp_addresses[i]->psz_address ) ) { p_sap_session->p_address = p_sap->pp_addresses[i]; b_found = VLC_TRUE; break; } } if( b_found == VLC_FALSE ) { sap_address_t *p_address = (sap_address_t *) malloc( sizeof(sap_address_t) ); if( !p_address ) { msg_Err( p_sap, "out of memory" ); return VLC_ENOMEM; } p_address->psz_address = strdup( p_method->psz_address ); p_address->i_ip_version = p_method->i_ip_version; p_address->i_port = 9875; p_address->i_wfd = net_OpenUDP( p_sap, "", 0, p_address->psz_address, p_address->i_port ); if( p_sap->b_control == VLC_TRUE ) { p_address->i_rfd = net_OpenUDP( p_sap, p_method->psz_address, p_address->i_port, "", 0 ); p_address->i_buff = 0; p_address->b_enabled = VLC_TRUE; p_address->b_ready = VLC_FALSE; p_address->i_limit = 10000; /* 10000 bps */ p_address->t1 = 0; } else { p_address->b_enabled = VLC_TRUE; p_address->b_ready = VLC_TRUE; p_address->i_interval = config_GetInt( p_sap,"sap-interval"); } if( p_address->i_wfd == -1 || (p_address->i_rfd == -1 && p_sap->b_control ) ) { msg_Warn( p_sap, "disabling address" ); p_address->b_enabled = VLC_FALSE; } INSERT_ELEM( p_sap->pp_addresses, p_sap->i_addresses, p_sap->i_addresses, p_address ); p_sap_session->p_address = p_address; } /* Build the SAP Headers */ i_header_size = ( p_method->i_ip_version == 6 ? 20 : 8 ) + strlen( psz_type ) + 1; psz_head = (char *) malloc( i_header_size * sizeof( char ) ); if( ! psz_head ) { msg_Err( p_sap, "out of memory" ); return VLC_ENOMEM; } psz_head[0] = 0x20; /* Means SAPv1, IPv4, not encrypted, not compressed */ psz_head[1] = 0x00; /* No authentification length */ i_hash = mdate(); psz_head[2] = (i_hash & 0xFF00) >> 8; /* Msg id hash */ psz_head[3] = (i_hash & 0xFF); /* Msg id hash 2 */ if( p_method->i_ip_version == 6 ) { /* in_addr_t ip_server = inet_addr( ip ); */ psz_head[0] |= 0x10; /* Set IPv6 */ psz_head[4] = 0x01; /* Source IP FIXME: we should get the real address */ psz_head[5] = 0x02; /* idem */ psz_head[6] = 0x03; /* idem */ psz_head[7] = 0x04; /* idem */ psz_head[8] = 0x01; /* Source IP FIXME: we should get the real address */ psz_head[9] = 0x02; /* idem */ psz_head[10] = 0x03; /* idem */ psz_head[11] = 0x04; /* idem */ psz_head[12] = 0x01; /* Source IP FIXME: we should get the real address */ psz_head[13] = 0x02; /* idem */ psz_head[14] = 0x03; /* idem */ psz_head[15] = 0x04; /* idem */ psz_head[16] = 0x01; /* Source IP FIXME: we should get the real address */ psz_head[17] = 0x02; /* idem */ psz_head[18] = 0x03; /* idem */ psz_head[19] = 0x04; /* idem */ strncpy( psz_head + 20, psz_type, 15 ); } else { /* in_addr_t ip_server = inet_addr( ip) */ /* Source IP FIXME: we should get the real address */ psz_head[4] = 0x01; /* ip_server */ psz_head[5] = 0x02; /* ip_server>>8 */ psz_head[6] = 0x03; /* ip_server>>16 */ psz_head[7] = 0x04; /* ip_server>>24 */ strncpy( psz_head + 8, psz_type, 15 ); } psz_head[ i_header_size-1 ] = '\0'; p_sap_session->i_length = i_header_size + strlen( p_sap_session->psz_sdp); p_sap_session->psz_data = (char *)malloc( sizeof(char)* p_sap_session->i_length ); /* Build the final message */ memcpy( p_sap_session->psz_data, psz_head, i_header_size ); memcpy( p_sap_session->psz_data+i_header_size, p_sap_session->psz_sdp, strlen( p_sap_session->psz_sdp) ); free( psz_head ); /* Enqueue the announce */ INSERT_ELEM( p_sap->pp_sessions, p_sap->i_sessions, p_sap->i_sessions, p_sap_session ); msg_Dbg( p_sap,"Addresses: %i Sessions: %i", p_sap->i_addresses,p_sap->i_sessions); /* Remember the SAP session for later deletion */ p_session->p_sap = p_sap_session; vlc_mutex_unlock( &p_sap->object_lock ); return VLC_SUCCESS; }
/** RTSP requests handler * @param id selected track for non-aggregate URLs, * NULL for aggregate URLs */ static int RtspHandler( rtsp_stream_t *rtsp, rtsp_stream_id_t *id, httpd_client_t *cl, httpd_message_t *answer, const httpd_message_t *query ) { sout_stream_t *p_stream = rtsp->owner; char psz_sesbuf[17]; const char *psz_session = NULL, *psz; char control[sizeof("rtsp://[]:12345") + NI_MAXNUMERICHOST + strlen( rtsp->psz_path )]; time_t now; time (&now); if( answer == NULL || query == NULL || cl == NULL ) return VLC_SUCCESS; else { /* Build self-referential control URL */ char ip[NI_MAXNUMERICHOST], *ptr; httpd_ServerIP( cl, ip ); ptr = strchr( ip, '%' ); if( ptr != NULL ) *ptr = '\0'; if( strchr( ip, ':' ) != NULL ) sprintf( control, "rtsp://[%s]:%u%s", ip, rtsp->port, rtsp->psz_path ); else sprintf( control, "rtsp://%s:%u%s", ip, rtsp->port, rtsp->psz_path ); } /* */ answer->i_proto = HTTPD_PROTO_RTSP; answer->i_version= 0; answer->i_type = HTTPD_MSG_ANSWER; answer->i_body = 0; answer->p_body = NULL; httpd_MsgAdd( answer, "Server", "%s", PACKAGE_STRING ); /* Date: is always allowed, and sometimes mandatory with RTSP/2.0. */ struct tm ut; if (gmtime_r (&now, &ut) != NULL) { /* RFC1123 format, GMT is mandatory */ static const char wdays[7][4] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; static const char mons[12][4] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; httpd_MsgAdd (answer, "Date", "%s, %02u %s %04u %02u:%02u:%02u GMT", wdays[ut.tm_wday], ut.tm_mday, mons[ut.tm_mon], 1900 + ut.tm_year, ut.tm_hour, ut.tm_min, ut.tm_sec); } if( query->i_proto != HTTPD_PROTO_RTSP ) { answer->i_status = 505; } else if( httpd_MsgGet( query, "Require" ) != NULL ) { answer->i_status = 551; httpd_MsgAdd( answer, "Unsupported", "%s", httpd_MsgGet( query, "Require" ) ); } else switch( query->i_type ) { case HTTPD_MSG_DESCRIBE: { /* Aggregate-only */ if( id != NULL ) { answer->i_status = 460; break; } answer->i_status = 200; httpd_MsgAdd( answer, "Content-Type", "%s", "application/sdp" ); httpd_MsgAdd( answer, "Content-Base", "%s", control ); answer->p_body = (uint8_t *)SDPGenerate( rtsp->owner, control ); if( answer->p_body != NULL ) answer->i_body = strlen( (char *)answer->p_body ); else answer->i_status = 500; break; } case HTTPD_MSG_SETUP: /* Non-aggregate-only */ if( id == NULL ) { answer->i_status = 459; break; } psz_session = httpd_MsgGet( query, "Session" ); answer->i_status = 461; for( const char *tpt = httpd_MsgGet( query, "Transport" ); tpt != NULL; tpt = transport_next( tpt ) ) { bool b_multicast = true, b_unsupp = false; unsigned loport = 5004, hiport = 5005; /* from RFC3551 */ /* Check transport protocol. */ /* Currently, we only support RTP/AVP over UDP */ if( strncmp( tpt, "RTP/AVP", 7 ) ) continue; tpt += 7; if( strncmp( tpt, "/UDP", 4 ) == 0 ) tpt += 4; if( strchr( ";,", *tpt ) == NULL ) continue; /* Parse transport options */ for( const char *opt = parameter_next( tpt ); opt != NULL; opt = parameter_next( opt ) ) { if( strncmp( opt, "multicast", 9 ) == 0) b_multicast = true; else if( strncmp( opt, "unicast", 7 ) == 0 ) b_multicast = false; else if( sscanf( opt, "client_port=%u-%u", &loport, &hiport ) == 2 ) ; else if( strncmp( opt, "mode=", 5 ) == 0 ) { if( strncasecmp( opt + 5, "play", 4 ) && strncasecmp( opt + 5, "\"PLAY\"", 6 ) ) { /* Not playing?! */ b_unsupp = true; break; } } else if( strncmp( opt,"destination=", 12 ) == 0 ) { answer->i_status = 403; b_unsupp = true; } else { /* * Every other option is unsupported: * * "source" and "append" are invalid (server-only); * "ssrc" also (as clarified per RFC2326bis). * * For multicast, "port", "layers", "ttl" are set by the * stream output configuration. * * For unicast, we want to decide "server_port" values. * * "interleaved" is not implemented. */ b_unsupp = true; break; } } if( b_unsupp ) continue; if( b_multicast ) { const char *dst = id->dst; if( dst == NULL ) continue; if( psz_session == NULL ) { /* Create a dummy session ID */ snprintf( psz_sesbuf, sizeof( psz_sesbuf ), "%d", rand() ); psz_session = psz_sesbuf; } answer->i_status = 200; httpd_MsgAdd( answer, "Transport", "RTP/AVP/UDP;destination=%s;port=%u-%u;" "ttl=%d;mode=play", dst, id->loport, id->hiport, ( id->ttl > 0 ) ? id->ttl : 1 ); } else { char ip[NI_MAXNUMERICHOST], src[NI_MAXNUMERICHOST]; rtsp_session_t *ses = NULL; rtsp_strack_t track = { id->sout_id, -1, false }; int sport; if( httpd_ClientIP( cl, ip ) == NULL ) { answer->i_status = 500; continue; } track.fd = net_ConnectDgram( p_stream, ip, loport, -1, IPPROTO_UDP ); if( track.fd == -1 ) { msg_Err( p_stream, "cannot create RTP socket for %s port %u", ip, loport ); answer->i_status = 500; continue; } net_GetSockAddress( track.fd, src, &sport ); vlc_mutex_lock( &rtsp->lock ); if( psz_session == NULL ) { ses = RtspClientNew( rtsp ); snprintf( psz_sesbuf, sizeof( psz_sesbuf ), "%"PRIx64, ses->id ); psz_session = psz_sesbuf; } else { /* FIXME: we probably need to remove an access out, * if there is already one for the same ID */ ses = RtspClientGet( rtsp, psz_session ); if( ses == NULL ) { answer->i_status = 454; vlc_mutex_unlock( &rtsp->lock ); continue; } } INSERT_ELEM( ses->trackv, ses->trackc, ses->trackc, track ); vlc_mutex_unlock( &rtsp->lock ); httpd_ServerIP( cl, ip ); if( strcmp( src, ip ) ) { /* Specify source IP if it is different from the RTSP * control connection server address */ char *ptr = strchr( src, '%' ); if( ptr != NULL ) *ptr = '\0'; /* remove scope ID */ httpd_MsgAdd( answer, "Transport", "RTP/AVP/UDP;unicast;source=%s;" "client_port=%u-%u;server_port=%u-%u;" "ssrc=%08X;mode=play", src, loport, loport + 1, sport, sport + 1, id->ssrc ); } else { httpd_MsgAdd( answer, "Transport", "RTP/AVP/UDP;unicast;" "client_port=%u-%u;server_port=%u-%u;" "ssrc=%08X;mode=play", loport, loport + 1, sport, sport + 1, id->ssrc ); } answer->i_status = 200; } break; } break; case HTTPD_MSG_PLAY: { rtsp_session_t *ses; answer->i_status = 200; psz_session = httpd_MsgGet( query, "Session" ); const char *range = httpd_MsgGet (query, "Range"); if (range && strncmp (range, "npt=", 4)) { answer->i_status = 501; break; } vlc_mutex_lock( &rtsp->lock ); ses = RtspClientGet( rtsp, psz_session ); if( ses != NULL ) { /* FIXME: we really need to limit the number of tracks... */ char info[ses->trackc * ( strlen( control ) + sizeof("url=/trackID=123;seq=65535, ") ) + 1]; size_t infolen = 0; for( int i = 0; i < ses->trackc; i++ ) { rtsp_strack_t *tr = ses->trackv + i; if( ( id == NULL ) || ( tr->id == id->sout_id ) ) { if( !tr->playing ) { tr->playing = true; rtp_add_sink( tr->id, tr->fd, false ); } infolen += sprintf( info + infolen, "url=%s/trackID=%u;seq=%u, ", control, rtp_get_num( tr->id ), rtp_get_seq( tr->id ) ); } } if( infolen > 0 ) { info[infolen - 2] = '\0'; /* remove trailing ", " */ httpd_MsgAdd( answer, "RTP-Info", "%s", info ); } } vlc_mutex_unlock( &rtsp->lock ); if( httpd_MsgGet( query, "Scale" ) != NULL ) httpd_MsgAdd( answer, "Scale", "1." ); break; } case HTTPD_MSG_PAUSE: answer->i_status = 405; httpd_MsgAdd( answer, "Allow", "%s, TEARDOWN, PLAY, GET_PARAMETER", ( id != NULL ) ? "SETUP" : "DESCRIBE" ); break; case HTTPD_MSG_GETPARAMETER: if( query->i_body > 0 ) { answer->i_status = 451; break; } psz_session = httpd_MsgGet( query, "Session" ); answer->i_status = 200; break; case HTTPD_MSG_TEARDOWN: { rtsp_session_t *ses; answer->i_status = 200; psz_session = httpd_MsgGet( query, "Session" ); vlc_mutex_lock( &rtsp->lock ); ses = RtspClientGet( rtsp, psz_session ); if( ses != NULL ) { if( id == NULL ) /* Delete the entire session */ RtspClientDel( rtsp, ses ); else /* Delete one track from the session */ for( int i = 0; i < ses->trackc; i++ ) { if( ses->trackv[i].id == id->sout_id ) { rtp_del_sink( id->sout_id, ses->trackv[i].fd ); REMOVE_ELEM( ses->trackv, ses->trackc, i ); } } } vlc_mutex_unlock( &rtsp->lock ); break; } default: return VLC_EGENERIC; } if( psz_session ) httpd_MsgAdd( answer, "Session", "%s"/*;timeout=5*/, psz_session ); httpd_MsgAdd( answer, "Content-Length", "%d", answer->i_body ); httpd_MsgAdd( answer, "Cache-Control", "no-cache" ); psz = httpd_MsgGet( query, "Cseq" ); if( psz != NULL ) httpd_MsgAdd( answer, "Cseq", "%s", psz ); psz = httpd_MsgGet( query, "Timestamp" ); if( psz != NULL ) httpd_MsgAdd( answer, "Timestamp", "%s", psz ); return VLC_SUCCESS; }