| /* GStreamer |
| * Copyright (C) 2011 Axis Communications <dev-gstreamer@axis.com> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
| * Boston, MA 02111-1307, USA. |
| */ |
| |
| /** |
| * SECTION:element-curlsink |
| * @short_description: sink that uploads data to a server using libcurl |
| * @see_also: |
| * |
| * This is a network sink that uses libcurl as a client to upload data to |
| * a server (e.g. a HTTP/FTP server). |
| * |
| * <refsect2> |
| * <title>Example launch line (upload a JPEG file to an HTTP server)</title> |
| * |[ |
| * gst-launch filesrc filesrc location=image.jpg ! jpegparse ! curlsink \ |
| * file-name=image.jpg \ |
| * location=http://192.168.0.1:8080/cgi-bin/patupload.cgi/ \ |
| * user=test passwd=test \ |
| * content-type=image/jpeg \ |
| * use-content-length=false |
| * ]| |
| * </refsect2> |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include <curl/curl.h> |
| #include <string.h> |
| #include <stdio.h> |
| |
| #include <sys/socket.h> |
| #include <sys/types.h> |
| #include <netinet/in.h> |
| #include <unistd.h> |
| #include <netinet/ip.h> |
| #include <netinet/tcp.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| |
| #include "gstcurlsink.h" |
| |
| #include "gst/glib-compat-private.h" |
| |
| /* Default values */ |
| #define GST_CAT_DEFAULT gst_curl_sink_debug |
| #define DEFAULT_URL "localhost:5555" |
| #define DEFAULT_TIMEOUT 30 |
| #define DEFAULT_PROXY_PORT 3128 |
| #define DEFAULT_QOS_DSCP 0 |
| #define DEFAULT_ACCEPT_SELF_SIGNED FALSE |
| #define DEFAULT_USE_CONTENT_LENGTH FALSE |
| |
| #define DSCP_MIN 0 |
| #define DSCP_MAX 63 |
| #define RESPONSE_100_CONTINUE 100 |
| #define RESPONSE_CONNECT_PROXY 200 |
| |
| /* Plugin specific settings */ |
| static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", |
| GST_PAD_SINK, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS_ANY); |
| |
| GST_DEBUG_CATEGORY_STATIC (gst_curl_sink_debug); |
| |
| enum |
| { |
| PROP_0, |
| PROP_LOCATION, |
| PROP_USER_NAME, |
| PROP_USER_PASSWD, |
| PROP_PROXY, |
| PROP_PROXY_PORT, |
| PROP_PROXY_USER_NAME, |
| PROP_PROXY_USER_PASSWD, |
| PROP_FILE_NAME, |
| PROP_TIMEOUT, |
| PROP_QOS_DSCP, |
| PROP_ACCEPT_SELF_SIGNED, |
| PROP_USE_CONTENT_LENGTH, |
| PROP_CONTENT_TYPE |
| }; |
| static gboolean proxy_auth = FALSE; |
| static gboolean proxy_conn_established = FALSE; |
| |
| /* Object class function declarations */ |
| static void gst_curl_sink_finalize (GObject * gobject); |
| static void gst_curl_sink_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec); |
| static void gst_curl_sink_get_property (GObject * object, guint prop_id, |
| GValue * value, GParamSpec * pspec); |
| |
| /* BaseSink class function declarations */ |
| static GstFlowReturn gst_curl_sink_render (GstBaseSink * bsink, |
| GstBuffer * buf); |
| static gboolean gst_curl_sink_event (GstBaseSink * bsink, GstEvent * event); |
| static gboolean gst_curl_sink_start (GstBaseSink * bsink); |
| static gboolean gst_curl_sink_stop (GstBaseSink * bsink); |
| static gboolean gst_curl_sink_unlock (GstBaseSink * bsink); |
| static gboolean gst_curl_sink_unlock_stop (GstBaseSink * bsink); |
| |
| /* private functions */ |
| static gboolean gst_curl_sink_transfer_setup_unlocked (GstCurlSink * sink); |
| static gboolean gst_curl_sink_transfer_set_options_unlocked (GstCurlSink |
| * sink); |
| static gboolean gst_curl_sink_transfer_start_unlocked (GstCurlSink * sink); |
| static void gst_curl_sink_transfer_cleanup (GstCurlSink * sink); |
| static size_t gst_curl_sink_transfer_read_cb (void *ptr, size_t size, |
| size_t nmemb, void *stream); |
| static size_t gst_curl_sink_transfer_write_cb (void *ptr, size_t size, |
| size_t nmemb, void *stream); |
| static GstFlowReturn gst_curl_sink_handle_transfer (GstCurlSink * sink); |
| static int gst_curl_sink_transfer_socket_cb (void *clientp, |
| curl_socket_t curlfd, curlsocktype purpose); |
| static gpointer gst_curl_sink_transfer_thread_func (gpointer data); |
| static CURLcode gst_curl_sink_transfer_check (GstCurlSink * sink); |
| static gint gst_curl_sink_setup_dscp_unlocked (GstCurlSink * sink); |
| |
| static gboolean gst_curl_sink_wait_for_data_unlocked (GstCurlSink * sink); |
| static void gst_curl_sink_new_file_notify_unlocked (GstCurlSink * sink); |
| static void gst_curl_sink_transfer_thread_notify_unlocked (GstCurlSink * sink); |
| static void gst_curl_sink_transfer_thread_close_unlocked (GstCurlSink * sink); |
| static void gst_curl_sink_wait_for_transfer_thread_to_send_unlocked (GstCurlSink |
| * sink); |
| static void gst_curl_sink_data_sent_notify_unlocked (GstCurlSink * sink); |
| |
| static void |
| _do_init (GType type) |
| { |
| GST_DEBUG_CATEGORY_INIT (gst_curl_sink_debug, "curlsink", 0, |
| "curl sink element"); |
| } |
| |
| GST_BOILERPLATE_FULL (GstCurlSink, gst_curl_sink, GstBaseSink, |
| GST_TYPE_BASE_SINK, _do_init); |
| |
| static void |
| gst_curl_sink_base_init (gpointer g_class) |
| { |
| GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); |
| |
| gst_element_class_add_pad_template (element_class, |
| gst_static_pad_template_get (&sinktemplate)); |
| gst_element_class_set_details_simple (element_class, |
| "Curl sink", |
| "Sink/Network", |
| "Upload data over the network to a server using libcurl", |
| "Patricia Muscalu <patricia@axis.com>"); |
| } |
| |
| static void |
| gst_curl_sink_class_init (GstCurlSinkClass * klass) |
| { |
| GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
| GstBaseSinkClass *gstbasesink_class = (GstBaseSinkClass *) klass; |
| |
| GST_DEBUG_OBJECT (klass, "class_init"); |
| |
| gstbasesink_class->event = GST_DEBUG_FUNCPTR (gst_curl_sink_event); |
| gstbasesink_class->render = GST_DEBUG_FUNCPTR (gst_curl_sink_render); |
| gstbasesink_class->start = GST_DEBUG_FUNCPTR (gst_curl_sink_start); |
| gstbasesink_class->stop = GST_DEBUG_FUNCPTR (gst_curl_sink_stop); |
| gstbasesink_class->unlock = GST_DEBUG_FUNCPTR (gst_curl_sink_unlock); |
| gstbasesink_class->unlock_stop = |
| GST_DEBUG_FUNCPTR (gst_curl_sink_unlock_stop); |
| gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_curl_sink_finalize); |
| |
| gobject_class->set_property = gst_curl_sink_set_property; |
| gobject_class->get_property = gst_curl_sink_get_property; |
| |
| /* FIXME: check against souphttpsrc and use same names for same properties */ |
| g_object_class_install_property (gobject_class, PROP_LOCATION, |
| g_param_spec_string ("location", "Location", |
| "URI location to write to", NULL, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_USER_NAME, |
| g_param_spec_string ("user", "User name", |
| "User name to use for server authentication", NULL, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_USER_PASSWD, |
| g_param_spec_string ("passwd", "User password", |
| "User password to use for server authentication", NULL, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_PROXY, |
| g_param_spec_string ("proxy", "Proxy", "HTTP proxy server URI", NULL, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_PROXY_PORT, |
| g_param_spec_int ("proxy-port", "Proxy port", |
| "HTTP proxy server port", 0, G_MAXINT, DEFAULT_PROXY_PORT, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_PROXY_USER_NAME, |
| g_param_spec_string ("proxy-user", "Proxy user name", |
| "Proxy user name to use for proxy authentication", |
| NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_PROXY_USER_PASSWD, |
| g_param_spec_string ("proxy-passwd", "Proxy user password", |
| "Proxy user password to use for proxy authentication", |
| NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_FILE_NAME, |
| g_param_spec_string ("file-name", "Base file name", |
| "The base file name for the uploaded images", NULL, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_TIMEOUT, |
| g_param_spec_int ("timeout", "Timeout", |
| "Number of seconds waiting to write before timeout", |
| 0, G_MAXINT, DEFAULT_TIMEOUT, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_QOS_DSCP, |
| g_param_spec_int ("qos-dscp", |
| "QoS diff srv code point", |
| "Quality of Service, differentiated services code point (0 default)", |
| DSCP_MIN, DSCP_MAX, DEFAULT_QOS_DSCP, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_ACCEPT_SELF_SIGNED, |
| g_param_spec_boolean ("accept-self-signed", |
| "Accept self-signed certificates", |
| "Accept self-signed SSL/TLS certificates", |
| DEFAULT_ACCEPT_SELF_SIGNED, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_USE_CONTENT_LENGTH, |
| g_param_spec_boolean ("use-content-length", "Use content length header", |
| "Use the Content-Length HTTP header instead of " |
| "Transfer-Encoding header", DEFAULT_USE_CONTENT_LENGTH, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_CONTENT_TYPE, |
| g_param_spec_string ("content-type", "Content type", |
| "The mime type of the body of the request", NULL, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| } |
| |
| static void |
| gst_curl_sink_init (GstCurlSink * sink, GstCurlSinkClass * klass) |
| { |
| sink->transfer_buf = g_malloc (sizeof (TransferBuffer)); |
| sink->transfer_cond = g_malloc (sizeof (TransferCondition)); |
| sink->transfer_cond->cond = g_cond_new (); |
| sink->transfer_cond->data_sent = FALSE; |
| sink->transfer_cond->data_available = FALSE; |
| sink->timeout = DEFAULT_TIMEOUT; |
| sink->proxy_port = DEFAULT_PROXY_PORT; |
| sink->qos_dscp = DEFAULT_QOS_DSCP; |
| sink->url = g_strdup (DEFAULT_URL); |
| sink->header_list = NULL; |
| sink->accept_self_signed = DEFAULT_ACCEPT_SELF_SIGNED; |
| sink->use_content_length = DEFAULT_USE_CONTENT_LENGTH; |
| sink->transfer_thread_close = FALSE; |
| sink->new_file = TRUE; |
| sink->proxy_headers_set = FALSE; |
| sink->content_type = NULL; |
| } |
| |
| static void |
| gst_curl_sink_finalize (GObject * gobject) |
| { |
| GstCurlSink *this = GST_CURL_SINK (gobject); |
| |
| GST_DEBUG ("finalizing curlsink"); |
| if (this->transfer_thread != NULL) { |
| g_thread_join (this->transfer_thread); |
| } |
| |
| gst_curl_sink_transfer_cleanup (this); |
| g_cond_free (this->transfer_cond->cond); |
| g_free (this->transfer_cond); |
| |
| g_free (this->transfer_buf); |
| |
| g_free (this->url); |
| g_free (this->user); |
| g_free (this->passwd); |
| g_free (this->proxy); |
| g_free (this->proxy_user); |
| g_free (this->proxy_passwd); |
| g_free (this->file_name); |
| g_free (this->content_type); |
| |
| if (this->header_list) { |
| curl_slist_free_all (this->header_list); |
| this->header_list = NULL; |
| } |
| |
| if (this->fdset != NULL) { |
| gst_poll_free (this->fdset); |
| this->fdset = NULL; |
| } |
| G_OBJECT_CLASS (parent_class)->finalize (gobject); |
| } |
| |
| static GstFlowReturn |
| gst_curl_sink_render (GstBaseSink * bsink, GstBuffer * buf) |
| { |
| GstCurlSink *sink = GST_CURL_SINK (bsink); |
| guint8 *data; |
| size_t size; |
| GstFlowReturn ret; |
| |
| GST_LOG ("enter render"); |
| |
| sink = GST_CURL_SINK (bsink); |
| data = GST_BUFFER_DATA (buf); |
| size = GST_BUFFER_SIZE (buf); |
| |
| if (sink->content_type == NULL) { |
| GstCaps *caps; |
| GstStructure *structure; |
| const gchar *mime_type; |
| |
| caps = buf->caps; |
| structure = gst_caps_get_structure (caps, 0); |
| mime_type = gst_structure_get_name (structure); |
| sink->content_type = g_strdup (mime_type); |
| } |
| |
| GST_OBJECT_LOCK (sink); |
| |
| /* check if the transfer thread has encountered problems while the |
| * pipeline thread was working elsewhere */ |
| if (sink->flow_ret != GST_FLOW_OK) { |
| goto done; |
| } |
| |
| g_assert (sink->transfer_cond->data_available == FALSE); |
| |
| /* if there is no transfer thread created, lets create one */ |
| if (sink->transfer_thread == NULL) { |
| if (!gst_curl_sink_transfer_start_unlocked (sink)) { |
| sink->flow_ret = GST_FLOW_ERROR; |
| goto done; |
| } |
| } |
| |
| /* make data available for the transfer thread and notify */ |
| sink->transfer_buf->ptr = data; |
| sink->transfer_buf->len = size; |
| sink->transfer_buf->offset = 0; |
| gst_curl_sink_transfer_thread_notify_unlocked (sink); |
| |
| /* wait for the transfer thread to send the data. This will be notified |
| * either when transfer is completed by the curl read callback or by |
| * the thread function if an error has occured. */ |
| gst_curl_sink_wait_for_transfer_thread_to_send_unlocked (sink); |
| |
| done: |
| ret = sink->flow_ret; |
| GST_OBJECT_UNLOCK (sink); |
| |
| GST_LOG ("exit render"); |
| |
| return ret; |
| } |
| |
| static gboolean |
| gst_curl_sink_event (GstBaseSink * bsink, GstEvent * event) |
| { |
| GstCurlSink *sink = GST_CURL_SINK (bsink); |
| |
| switch (event->type) { |
| case GST_EVENT_EOS: |
| GST_DEBUG_OBJECT (sink, "received EOS"); |
| GST_OBJECT_LOCK (sink); |
| gst_curl_sink_transfer_thread_close_unlocked (sink); |
| GST_OBJECT_UNLOCK (sink); |
| if (sink->transfer_thread != NULL) { |
| g_thread_join (sink->transfer_thread); |
| sink->transfer_thread = NULL; |
| } |
| break; |
| default: |
| break; |
| } |
| return TRUE; |
| } |
| |
| static gboolean |
| gst_curl_sink_start (GstBaseSink * bsink) |
| { |
| GstCurlSink *sink; |
| |
| sink = GST_CURL_SINK (bsink); |
| |
| if ((sink->fdset = gst_poll_new (TRUE)) == NULL) { |
| GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_READ_WRITE, |
| ("gst_poll_new failed: %s", g_strerror (errno)), (NULL)); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| gst_curl_sink_stop (GstBaseSink * bsink) |
| { |
| GstCurlSink *sink = GST_CURL_SINK (bsink); |
| |
| GST_OBJECT_LOCK (sink); |
| gst_curl_sink_transfer_thread_close_unlocked (sink); |
| GST_OBJECT_UNLOCK (sink); |
| if (sink->fdset != NULL) { |
| gst_poll_free (sink->fdset); |
| sink->fdset = NULL; |
| } |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| gst_curl_sink_unlock (GstBaseSink * bsink) |
| { |
| GstCurlSink *sink; |
| |
| sink = GST_CURL_SINK (bsink); |
| |
| GST_LOG_OBJECT (sink, "Flushing"); |
| gst_poll_set_flushing (sink->fdset, TRUE); |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| gst_curl_sink_unlock_stop (GstBaseSink * bsink) |
| { |
| GstCurlSink *sink; |
| |
| sink = GST_CURL_SINK (bsink); |
| |
| GST_LOG_OBJECT (sink, "No longer flushing"); |
| gst_poll_set_flushing (sink->fdset, FALSE); |
| |
| return TRUE; |
| } |
| |
| static void |
| gst_curl_sink_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec) |
| { |
| GstCurlSink *sink; |
| GstState cur_state; |
| |
| g_return_if_fail (GST_IS_CURL_SINK (object)); |
| sink = GST_CURL_SINK (object); |
| |
| gst_element_get_state (GST_ELEMENT (sink), &cur_state, NULL, 0); |
| if (cur_state != GST_STATE_PLAYING && cur_state != GST_STATE_PAUSED) { |
| GST_OBJECT_LOCK (sink); |
| |
| switch (prop_id) { |
| case PROP_LOCATION: |
| g_free (sink->url); |
| sink->url = g_value_dup_string (value); |
| GST_DEBUG_OBJECT (sink, "url set to %s", sink->url); |
| break; |
| case PROP_USER_NAME: |
| g_free (sink->user); |
| sink->user = g_value_dup_string (value); |
| GST_DEBUG_OBJECT (sink, "user set to %s", sink->user); |
| break; |
| case PROP_USER_PASSWD: |
| g_free (sink->passwd); |
| sink->passwd = g_value_dup_string (value); |
| GST_DEBUG_OBJECT (sink, "passwd set to %s", sink->passwd); |
| break; |
| case PROP_PROXY: |
| g_free (sink->proxy); |
| sink->proxy = g_value_dup_string (value); |
| GST_DEBUG_OBJECT (sink, "proxy set to %s", sink->proxy); |
| break; |
| case PROP_PROXY_PORT: |
| sink->proxy_port = g_value_get_int (value); |
| GST_DEBUG_OBJECT (sink, "proxy port set to %d", sink->proxy_port); |
| break; |
| case PROP_PROXY_USER_NAME: |
| g_free (sink->proxy_user); |
| sink->proxy_user = g_value_dup_string (value); |
| GST_DEBUG_OBJECT (sink, "proxy user set to %s", sink->proxy_user); |
| break; |
| case PROP_PROXY_USER_PASSWD: |
| g_free (sink->proxy_passwd); |
| sink->proxy_passwd = g_value_dup_string (value); |
| GST_DEBUG_OBJECT (sink, "proxy password set to %s", sink->proxy_passwd); |
| break; |
| case PROP_FILE_NAME: |
| g_free (sink->file_name); |
| sink->file_name = g_value_dup_string (value); |
| GST_DEBUG_OBJECT (sink, "file_name set to %s", sink->file_name); |
| break; |
| case PROP_TIMEOUT: |
| sink->timeout = g_value_get_int (value); |
| GST_DEBUG_OBJECT (sink, "timeout set to %d", sink->timeout); |
| break; |
| case PROP_QOS_DSCP: |
| sink->qos_dscp = g_value_get_int (value); |
| gst_curl_sink_setup_dscp_unlocked (sink); |
| GST_DEBUG_OBJECT (sink, "dscp set to %d", sink->qos_dscp); |
| break; |
| case PROP_ACCEPT_SELF_SIGNED: |
| sink->accept_self_signed = g_value_get_boolean (value); |
| GST_DEBUG_OBJECT (sink, "accept_self_signed set to %d", |
| sink->accept_self_signed); |
| break; |
| case PROP_USE_CONTENT_LENGTH: |
| sink->use_content_length = g_value_get_boolean (value); |
| GST_DEBUG_OBJECT (sink, "use_content_length set to %d", |
| sink->use_content_length); |
| break; |
| case PROP_CONTENT_TYPE: |
| g_free (sink->content_type); |
| sink->content_type = g_value_dup_string (value); |
| GST_DEBUG_OBJECT (sink, "content type set to %s", sink->content_type); |
| break; |
| default: |
| GST_DEBUG_OBJECT (sink, "invalid property id %d", prop_id); |
| break; |
| } |
| |
| GST_OBJECT_UNLOCK (sink); |
| |
| return; |
| } |
| |
| /* in PLAYING or PAUSED state */ |
| GST_OBJECT_LOCK (sink); |
| |
| switch (prop_id) { |
| case PROP_FILE_NAME: |
| g_free (sink->file_name); |
| sink->file_name = g_value_dup_string (value); |
| GST_DEBUG_OBJECT (sink, "file_name set to %s", sink->file_name); |
| gst_curl_sink_new_file_notify_unlocked (sink); |
| break; |
| case PROP_TIMEOUT: |
| sink->timeout = g_value_get_int (value); |
| GST_DEBUG_OBJECT (sink, "timeout set to %d", sink->timeout); |
| break; |
| case PROP_QOS_DSCP: |
| sink->qos_dscp = g_value_get_int (value); |
| gst_curl_sink_setup_dscp_unlocked (sink); |
| GST_DEBUG_OBJECT (sink, "dscp set to %d", sink->qos_dscp); |
| break; |
| case PROP_CONTENT_TYPE: |
| g_free (sink->content_type); |
| sink->content_type = g_value_dup_string (value); |
| GST_DEBUG_OBJECT (sink, "content type set to %s", sink->content_type); |
| break; |
| default: |
| GST_WARNING_OBJECT (sink, "cannot set property when PLAYING"); |
| break; |
| } |
| |
| GST_OBJECT_UNLOCK (sink); |
| } |
| |
| static void |
| gst_curl_sink_get_property (GObject * object, guint prop_id, |
| GValue * value, GParamSpec * pspec) |
| { |
| GstCurlSink *sink; |
| |
| g_return_if_fail (GST_IS_CURL_SINK (object)); |
| sink = GST_CURL_SINK (object); |
| |
| switch (prop_id) { |
| case PROP_LOCATION: |
| g_value_set_string (value, sink->url); |
| break; |
| case PROP_USER_NAME: |
| g_value_set_string (value, sink->user); |
| break; |
| case PROP_USER_PASSWD: |
| g_value_set_string (value, sink->passwd); |
| break; |
| case PROP_PROXY: |
| g_value_set_string (value, sink->proxy); |
| break; |
| case PROP_PROXY_PORT: |
| g_value_set_int (value, sink->proxy_port); |
| break; |
| case PROP_PROXY_USER_NAME: |
| g_value_set_string (value, sink->proxy_user); |
| break; |
| case PROP_PROXY_USER_PASSWD: |
| g_value_set_string (value, sink->proxy_passwd); |
| break; |
| case PROP_FILE_NAME: |
| g_value_set_string (value, sink->file_name); |
| break; |
| case PROP_TIMEOUT: |
| g_value_set_int (value, sink->timeout); |
| break; |
| case PROP_QOS_DSCP: |
| g_value_set_int (value, sink->qos_dscp); |
| break; |
| case PROP_ACCEPT_SELF_SIGNED: |
| g_value_set_boolean (value, sink->accept_self_signed); |
| break; |
| case PROP_USE_CONTENT_LENGTH: |
| g_value_set_boolean (value, sink->use_content_length); |
| break; |
| case PROP_CONTENT_TYPE: |
| g_value_set_string (value, sink->content_type); |
| break; |
| default: |
| GST_DEBUG_OBJECT (sink, "invalid property id"); |
| break; |
| } |
| } |
| |
| static void |
| gst_curl_sink_set_http_header_unlocked (GstCurlSink * sink) |
| { |
| gchar *tmp; |
| |
| if (sink->header_list) { |
| curl_slist_free_all (sink->header_list); |
| sink->header_list = NULL; |
| } |
| |
| if (proxy_auth && !sink->proxy_headers_set && !proxy_conn_established) { |
| sink->header_list = |
| curl_slist_append (sink->header_list, "Content-Length: 0"); |
| sink->proxy_headers_set = TRUE; |
| goto set_headers; |
| } |
| if (sink->use_content_length) { |
| /* if content length is used we assume that every buffer is one |
| * entire file, which is the case when uploading several jpegs */ |
| tmp = g_strdup_printf ("Content-Length: %d", (int) sink->transfer_buf->len); |
| sink->header_list = curl_slist_append (sink->header_list, tmp); |
| g_free (tmp); |
| } else { |
| /* when sending a POST request to a HTTP 1.1 server, you can send data |
| * without knowing the size before starting the POST if you use chunked |
| * encoding */ |
| sink->header_list = curl_slist_append (sink->header_list, |
| "Transfer-Encoding: chunked"); |
| } |
| |
| tmp = g_strdup_printf ("Content-Type: %s", sink->content_type); |
| sink->header_list = curl_slist_append (sink->header_list, tmp); |
| g_free (tmp); |
| |
| set_headers: |
| |
| tmp = g_strdup_printf ("Content-Disposition: attachment; filename=" |
| "\"%s\"", sink->file_name); |
| sink->header_list = curl_slist_append (sink->header_list, tmp); |
| g_free (tmp); |
| curl_easy_setopt (sink->curl, CURLOPT_HTTPHEADER, sink->header_list); |
| } |
| |
| static gboolean |
| gst_curl_sink_transfer_set_options_unlocked (GstCurlSink * sink) |
| { |
| #ifdef DEBUG |
| curl_easy_setopt (sink->curl, CURLOPT_VERBOSE, 1); |
| #endif |
| |
| curl_easy_setopt (sink->curl, CURLOPT_URL, sink->url); |
| curl_easy_setopt (sink->curl, CURLOPT_CONNECTTIMEOUT, sink->timeout); |
| |
| curl_easy_setopt (sink->curl, CURLOPT_SOCKOPTDATA, sink); |
| curl_easy_setopt (sink->curl, CURLOPT_SOCKOPTFUNCTION, |
| gst_curl_sink_transfer_socket_cb); |
| |
| if (sink->user != NULL && strlen (sink->user)) { |
| curl_easy_setopt (sink->curl, CURLOPT_USERNAME, sink->user); |
| curl_easy_setopt (sink->curl, CURLOPT_PASSWORD, sink->passwd); |
| curl_easy_setopt (sink->curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY); |
| } |
| |
| if (sink->accept_self_signed && g_str_has_prefix (sink->url, "https")) { |
| /* TODO verify the authenticity of the peer's certificate */ |
| curl_easy_setopt (sink->curl, CURLOPT_SSL_VERIFYPEER, 0L); |
| /* TODO check the servers's claimed identity */ |
| curl_easy_setopt (sink->curl, CURLOPT_SSL_VERIFYHOST, 0L); |
| } |
| |
| /* proxy settings */ |
| if (sink->proxy != NULL && strlen (sink->proxy)) { |
| if (curl_easy_setopt (sink->curl, CURLOPT_PROXY, sink->proxy) |
| != CURLE_OK) { |
| return FALSE; |
| } |
| if (curl_easy_setopt (sink->curl, CURLOPT_PROXYPORT, sink->proxy_port) |
| != CURLE_OK) { |
| return FALSE; |
| } |
| if (sink->proxy_user != NULL && |
| strlen (sink->proxy_user) && |
| sink->proxy_passwd != NULL && strlen (sink->proxy_passwd)) { |
| curl_easy_setopt (sink->curl, CURLOPT_PROXYUSERNAME, sink->proxy_user); |
| curl_easy_setopt (sink->curl, CURLOPT_PROXYPASSWORD, sink->proxy_passwd); |
| curl_easy_setopt (sink->curl, CURLOPT_PROXYAUTH, CURLAUTH_ANY); |
| proxy_auth = TRUE; |
| } |
| /* tunnel all operations through a given HTTP proxy */ |
| if (curl_easy_setopt (sink->curl, CURLOPT_HTTPPROXYTUNNEL, 1L) |
| != CURLE_OK) { |
| return FALSE; |
| } |
| } |
| |
| /* POST options */ |
| curl_easy_setopt (sink->curl, CURLOPT_POST, 1L); |
| |
| curl_easy_setopt (sink->curl, CURLOPT_READFUNCTION, |
| gst_curl_sink_transfer_read_cb); |
| curl_easy_setopt (sink->curl, CURLOPT_READDATA, sink); |
| curl_easy_setopt (sink->curl, CURLOPT_WRITEFUNCTION, |
| gst_curl_sink_transfer_write_cb); |
| |
| return TRUE; |
| } |
| |
| static size_t |
| gst_curl_sink_transfer_read_cb (void *curl_ptr, size_t size, size_t nmemb, |
| void *stream) |
| { |
| GstCurlSink *sink; |
| TransferBuffer *buffer; |
| size_t max_bytes_to_send; |
| guint buf_len; |
| |
| sink = (GstCurlSink *) stream; |
| |
| /* wait for data to come available, if new file or thread close is set |
| * then zero will be returned to indicate end of current transfer */ |
| GST_OBJECT_LOCK (sink); |
| if (gst_curl_sink_wait_for_data_unlocked (sink) == FALSE) { |
| GST_LOG ("returning 0, no more data to send in this file"); |
| GST_OBJECT_UNLOCK (sink); |
| return 0; |
| } |
| GST_OBJECT_UNLOCK (sink); |
| |
| |
| max_bytes_to_send = size * nmemb; |
| buffer = sink->transfer_buf; |
| |
| buf_len = buffer->len; |
| GST_LOG ("write buf len=%" G_GSIZE_FORMAT ", offset=%" G_GSIZE_FORMAT, |
| buffer->len, buffer->offset); |
| |
| /* more data in buffer */ |
| if (buffer->len > 0) { |
| size_t bytes_to_send = MIN (max_bytes_to_send, buf_len); |
| |
| memcpy ((guint8 *) curl_ptr, buffer->ptr + buffer->offset, bytes_to_send); |
| |
| buffer->offset = buffer->offset + bytes_to_send; |
| buffer->len = buffer->len - bytes_to_send; |
| |
| /* the last data chunk */ |
| if (bytes_to_send == buf_len) { |
| buffer->ptr = NULL; |
| buffer->offset = 0; |
| buffer->len = 0; |
| GST_OBJECT_LOCK (sink); |
| gst_curl_sink_data_sent_notify_unlocked (sink); |
| GST_OBJECT_UNLOCK (sink); |
| } |
| |
| GST_LOG ("sent : %" G_GSIZE_FORMAT, bytes_to_send); |
| |
| return bytes_to_send; |
| } else { |
| GST_WARNING ("got zero-length buffer"); |
| return 0; |
| } |
| } |
| |
| static size_t |
| gst_curl_sink_transfer_write_cb (void G_GNUC_UNUSED * ptr, size_t size, |
| size_t nmemb, void G_GNUC_UNUSED * stream) |
| { |
| size_t realsize = size * nmemb; |
| |
| GST_DEBUG ("response %s", (gchar *) ptr); |
| return realsize; |
| } |
| |
| static CURLcode |
| gst_curl_sink_transfer_check (GstCurlSink * sink) |
| { |
| CURLcode code = CURLE_OK; |
| CURL *easy; |
| CURLMsg *msg; |
| gint msgs_left; |
| gchar *eff_url = NULL; |
| |
| do { |
| easy = NULL; |
| while ((msg = curl_multi_info_read (sink->multi_handle, &msgs_left))) { |
| if (msg->msg == CURLMSG_DONE) { |
| easy = msg->easy_handle; |
| code = msg->data.result; |
| break; |
| } |
| } |
| if (easy) { |
| curl_easy_getinfo (easy, CURLINFO_EFFECTIVE_URL, &eff_url); |
| GST_DEBUG ("transfer done %s (%s-%d)\n", eff_url, |
| curl_easy_strerror (code), code); |
| } |
| } while (easy); |
| |
| return code; |
| } |
| |
| static GstFlowReturn |
| gst_curl_sink_handle_transfer (GstCurlSink * sink) |
| { |
| gint retval; |
| gint running_handles; |
| gint timeout; |
| CURLMcode m_code; |
| CURLcode e_code; |
| glong resp = -1; |
| glong resp_proxy = -1; |
| |
| GST_OBJECT_LOCK (sink); |
| timeout = sink->timeout; |
| GST_OBJECT_UNLOCK (sink); |
| |
| /* Receiving CURLM_CALL_MULTI_PERFORM means that libcurl may have more data |
| available to send or receive - call simply curl_multi_perform before |
| poll() on more actions */ |
| do { |
| m_code = curl_multi_perform (sink->multi_handle, &running_handles); |
| } while (m_code == CURLM_CALL_MULTI_PERFORM); |
| |
| while (running_handles && (m_code == CURLM_OK)) { |
| if (!proxy_conn_established && (resp_proxy != RESPONSE_CONNECT_PROXY) |
| && proxy_auth) { |
| curl_easy_getinfo (sink->curl, CURLINFO_HTTP_CONNECTCODE, &resp_proxy); |
| if (resp_proxy == RESPONSE_CONNECT_PROXY) { |
| GST_LOG ("received HTTP/1.0 200 Connection Established"); |
| /* Workaround: redefine HTTP headers before connecting to HTTP server. |
| * When talking to proxy, the Content-Length: 0 is send with the request. |
| */ |
| curl_multi_remove_handle (sink->multi_handle, sink->curl); |
| gst_curl_sink_set_http_header_unlocked (sink); |
| curl_multi_add_handle (sink->multi_handle, sink->curl); |
| proxy_conn_established = TRUE; |
| } |
| } |
| |
| retval = gst_poll_wait (sink->fdset, timeout * GST_SECOND); |
| if (G_UNLIKELY (retval == -1)) { |
| if (errno == EAGAIN || errno == EINTR) { |
| GST_DEBUG_OBJECT (sink, "interrupted by signal"); |
| } else if (errno == EBUSY) { |
| goto poll_stopped; |
| } else { |
| goto poll_error; |
| } |
| } else if (G_UNLIKELY (retval == 0)) { |
| GST_DEBUG ("timeout"); |
| goto poll_timeout; |
| } |
| |
| /* readable/writable sockets */ |
| do { |
| m_code = curl_multi_perform (sink->multi_handle, &running_handles); |
| } while (m_code == CURLM_CALL_MULTI_PERFORM); |
| |
| if (resp != RESPONSE_100_CONTINUE) { |
| curl_easy_getinfo (sink->curl, CURLINFO_RESPONSE_CODE, &resp); |
| } |
| } |
| |
| if (resp != RESPONSE_100_CONTINUE) { |
| /* No 100 Continue response received. Using POST with HTTP 1.1 implies |
| * the use of a "Expect: 100-continue" header. If the server doesn't |
| * send HTTP/1.1 100 Continue, libcurl will not call transfer_read_cb |
| * in order to send POST data. |
| */ |
| goto no_100_continue_response; |
| } |
| |
| if (m_code != CURLM_OK) { |
| goto curl_multi_error; |
| } |
| |
| /* problems still might have occurred on individual transfers even when |
| * curl_multi_perform returns CURLM_OK */ |
| if ((e_code = gst_curl_sink_transfer_check (sink)) != CURLE_OK) { |
| goto curl_easy_error; |
| } |
| |
| /* check response code */ |
| curl_easy_getinfo (sink->curl, CURLINFO_RESPONSE_CODE, &resp); |
| GST_DEBUG_OBJECT (sink, "response code: %ld", resp); |
| if (resp < 200 || resp >= 300) { |
| goto response_error; |
| } |
| |
| return GST_FLOW_OK; |
| |
| poll_error: |
| { |
| GST_DEBUG_OBJECT (sink, "poll failed: %s", g_strerror (errno)); |
| GST_ELEMENT_ERROR (sink, RESOURCE, WRITE, ("poll failed"), (NULL)); |
| return GST_FLOW_ERROR; |
| } |
| |
| poll_stopped: |
| { |
| GST_DEBUG_OBJECT (sink, "poll stopped"); |
| GST_ELEMENT_ERROR (sink, RESOURCE, WRITE, ("poll stopped"), (NULL)); |
| return GST_FLOW_ERROR; |
| } |
| |
| poll_timeout: |
| { |
| GST_DEBUG_OBJECT (sink, "poll timed out"); |
| GST_ELEMENT_ERROR (sink, RESOURCE, WRITE, ("poll timed out"), (NULL)); |
| return GST_FLOW_ERROR; |
| } |
| |
| curl_multi_error: |
| { |
| GST_DEBUG_OBJECT (sink, "curl multi error"); |
| GST_ELEMENT_ERROR (sink, RESOURCE, WRITE, ("%s", |
| curl_multi_strerror (m_code)), (NULL)); |
| return GST_FLOW_ERROR; |
| } |
| |
| curl_easy_error: |
| { |
| GST_DEBUG_OBJECT (sink, "curl easy error"); |
| GST_ELEMENT_ERROR (sink, RESOURCE, WRITE, ("%s", |
| curl_easy_strerror (e_code)), (NULL)); |
| return GST_FLOW_ERROR; |
| } |
| |
| no_100_continue_response: |
| { |
| GST_DEBUG_OBJECT (sink, "100 continue response missing"); |
| GST_ELEMENT_ERROR (sink, RESOURCE, WRITE, ("100 continue response missing"), |
| (NULL)); |
| return GST_FLOW_ERROR; |
| } |
| |
| response_error: |
| { |
| GST_DEBUG_OBJECT (sink, "response error"); |
| GST_ELEMENT_ERROR (sink, RESOURCE, WRITE, ("response error: %ld", resp), |
| (NULL)); |
| return GST_FLOW_ERROR; |
| } |
| } |
| |
| /* This function gets called by libcurl after the socket() call but before |
| * the connect() call. */ |
| static int |
| gst_curl_sink_transfer_socket_cb (void *clientp, curl_socket_t curlfd, |
| curlsocktype G_GNUC_UNUSED purpose) |
| { |
| GstCurlSink *sink; |
| gboolean ret = TRUE; |
| |
| sink = (GstCurlSink *) clientp; |
| |
| g_assert (sink); |
| |
| if (curlfd < 0) { |
| /* signal an unrecoverable error to the library which will close the socket |
| and return CURLE_COULDNT_CONNECT |
| */ |
| return 1; |
| } |
| |
| gst_poll_fd_init (&sink->fd); |
| sink->fd.fd = curlfd; |
| |
| ret = ret && gst_poll_add_fd (sink->fdset, &sink->fd); |
| ret = ret && gst_poll_fd_ctl_write (sink->fdset, &sink->fd, TRUE); |
| ret = ret && gst_poll_fd_ctl_read (sink->fdset, &sink->fd, TRUE); |
| GST_DEBUG ("fd: %d", sink->fd.fd); |
| GST_OBJECT_LOCK (sink); |
| gst_curl_sink_setup_dscp_unlocked (sink); |
| GST_OBJECT_UNLOCK (sink); |
| |
| /* success */ |
| if (ret) { |
| return 0; |
| } else { |
| return 1; |
| } |
| } |
| |
| static gboolean |
| gst_curl_sink_transfer_start_unlocked (GstCurlSink * sink) |
| { |
| GError *error = NULL; |
| gboolean ret = TRUE; |
| |
| GST_LOG ("creating transfer thread"); |
| sink->transfer_thread_close = FALSE; |
| sink->new_file = TRUE; |
| sink->transfer_thread = |
| g_thread_create ((GThreadFunc) gst_curl_sink_transfer_thread_func, sink, |
| TRUE, &error); |
| |
| if (sink->transfer_thread == NULL || error != NULL) { |
| ret = FALSE; |
| if (error) { |
| GST_ERROR_OBJECT (sink, "could not create thread %s", error->message); |
| g_error_free (error); |
| } else { |
| GST_ERROR_OBJECT (sink, "could not create thread for unknown reason"); |
| } |
| } |
| |
| return ret; |
| } |
| |
| static gpointer |
| gst_curl_sink_transfer_thread_func (gpointer data) |
| { |
| GstCurlSink *sink = (GstCurlSink *) data; |
| GstFlowReturn ret = GST_FLOW_OK; |
| gboolean data_available; |
| |
| GST_LOG ("transfer thread started"); |
| GST_OBJECT_LOCK (sink); |
| if (!gst_curl_sink_transfer_setup_unlocked (sink)) { |
| GST_DEBUG_OBJECT (sink, "curl setup error"); |
| GST_ELEMENT_ERROR (sink, RESOURCE, WRITE, ("curl setup error"), (NULL)); |
| sink->flow_ret = GST_FLOW_ERROR; |
| goto done; |
| } |
| |
| while (!sink->transfer_thread_close && sink->flow_ret == GST_FLOW_OK) { |
| /* we are working on a new file, clearing flag and setting file |
| * name in http header */ |
| sink->new_file = FALSE; |
| |
| /* wait for data to arrive for this new file, if we get a new file name |
| * again before getting data we will simply skip transfering anything |
| * for this file and go directly to the new file */ |
| data_available = gst_curl_sink_wait_for_data_unlocked (sink); |
| if (data_available) { |
| gst_curl_sink_set_http_header_unlocked (sink); |
| } |
| |
| /* stay unlocked while handling the actual transfer */ |
| GST_OBJECT_UNLOCK (sink); |
| |
| if (data_available) { |
| curl_multi_add_handle (sink->multi_handle, sink->curl); |
| |
| /* Start driving the transfer. */ |
| ret = gst_curl_sink_handle_transfer (sink); |
| |
| /* easy handle will be possibly re-used for next transfer, thus it needs to |
| * be removed from the multi stack and re-added again */ |
| curl_multi_remove_handle (sink->multi_handle, sink->curl); |
| } |
| |
| /* lock again before looping to check the thread closed flag */ |
| GST_OBJECT_LOCK (sink); |
| |
| /* if we have transfered data, then set the return code */ |
| if (data_available) { |
| sink->flow_ret = ret; |
| } |
| } |
| |
| done: |
| /* if there is a flow error, always notify the render function so it |
| * can return the flow error up along the pipeline */ |
| if (sink->flow_ret != GST_FLOW_OK) { |
| gst_curl_sink_data_sent_notify_unlocked (sink); |
| } |
| |
| GST_OBJECT_UNLOCK (sink); |
| GST_DEBUG ("exit thread func - transfer thread close flag: %d", |
| sink->transfer_thread_close); |
| |
| return NULL; |
| } |
| |
| static gboolean |
| gst_curl_sink_transfer_setup_unlocked (GstCurlSink * sink) |
| { |
| g_assert (sink); |
| |
| if (sink->curl == NULL) { |
| /* curl_easy_init automatically calls curl_global_init(3) */ |
| if ((sink->curl = curl_easy_init ()) == NULL) { |
| g_warning ("Failed to init easy handle"); |
| return FALSE; |
| } |
| } |
| |
| if (!gst_curl_sink_transfer_set_options_unlocked (sink)) { |
| g_warning ("Failed to setup easy handle"); |
| GST_OBJECT_UNLOCK (sink); |
| return FALSE; |
| } |
| |
| /* init a multi stack (non-blocking interface to liburl) */ |
| if (sink->multi_handle == NULL) { |
| if ((sink->multi_handle = curl_multi_init ()) == NULL) { |
| return FALSE; |
| } |
| } |
| |
| return TRUE; |
| } |
| |
| static void |
| gst_curl_sink_transfer_cleanup (GstCurlSink * sink) |
| { |
| if (sink->curl != NULL) { |
| if (sink->multi_handle != NULL) { |
| curl_multi_remove_handle (sink->multi_handle, sink->curl); |
| } |
| curl_easy_cleanup (sink->curl); |
| sink->curl = NULL; |
| } |
| |
| if (sink->multi_handle != NULL) { |
| curl_multi_cleanup (sink->multi_handle); |
| sink->multi_handle = NULL; |
| } |
| } |
| |
| static gboolean |
| gst_curl_sink_wait_for_data_unlocked (GstCurlSink * sink) |
| { |
| gboolean data_available = FALSE; |
| |
| GST_LOG ("waiting for data"); |
| while (!sink->transfer_cond->data_available && |
| !sink->transfer_thread_close && !sink->new_file) { |
| g_cond_wait (sink->transfer_cond->cond, GST_OBJECT_GET_LOCK (sink)); |
| } |
| |
| if (sink->transfer_thread_close) { |
| GST_LOG ("wait for data aborted due to thread close"); |
| } else if (sink->new_file) { |
| GST_LOG ("wait for data aborted due to new file name"); |
| } else { |
| GST_LOG ("wait for data completed"); |
| data_available = TRUE; |
| } |
| |
| return data_available; |
| } |
| |
| static void |
| gst_curl_sink_transfer_thread_notify_unlocked (GstCurlSink * sink) |
| { |
| GST_LOG ("more data to send"); |
| sink->transfer_cond->data_available = TRUE; |
| sink->transfer_cond->data_sent = FALSE; |
| g_cond_signal (sink->transfer_cond->cond); |
| } |
| |
| static void |
| gst_curl_sink_new_file_notify_unlocked (GstCurlSink * sink) |
| { |
| GST_LOG ("new file name"); |
| sink->new_file = TRUE; |
| g_cond_signal (sink->transfer_cond->cond); |
| } |
| |
| static void |
| gst_curl_sink_transfer_thread_close_unlocked (GstCurlSink * sink) |
| { |
| GST_LOG ("setting transfer thread close flag"); |
| sink->transfer_thread_close = TRUE; |
| g_cond_signal (sink->transfer_cond->cond); |
| } |
| |
| static void |
| gst_curl_sink_wait_for_transfer_thread_to_send_unlocked (GstCurlSink * sink) |
| { |
| GST_LOG ("waiting for buffer send to complete"); |
| |
| /* this function should not check if the transfer thread is set to be closed |
| * since that flag only can be set by the EoS event (by the pipeline thread). |
| * This can therefore never happen while this function is running since this |
| * function also is called by the pipeline thread (in the render function) */ |
| while (!sink->transfer_cond->data_sent) { |
| g_cond_wait (sink->transfer_cond->cond, GST_OBJECT_GET_LOCK (sink)); |
| } |
| GST_LOG ("buffer send completed"); |
| } |
| |
| static void |
| gst_curl_sink_data_sent_notify_unlocked (GstCurlSink * sink) |
| { |
| GST_LOG ("transfer completed"); |
| sink->transfer_cond->data_available = FALSE; |
| sink->transfer_cond->data_sent = TRUE; |
| g_cond_signal (sink->transfer_cond->cond); |
| } |
| |
| static gint |
| gst_curl_sink_setup_dscp_unlocked (GstCurlSink * sink) |
| { |
| gint tos; |
| gint af; |
| gint ret = -1; |
| union |
| { |
| struct sockaddr sa; |
| struct sockaddr_in6 sa_in6; |
| struct sockaddr_storage sa_stor; |
| } sa; |
| socklen_t slen = sizeof (sa); |
| |
| if (getsockname (sink->fd.fd, &sa.sa, &slen) < 0) { |
| GST_DEBUG_OBJECT (sink, "could not get sockname: %s", g_strerror (errno)); |
| return ret; |
| } |
| af = sa.sa.sa_family; |
| |
| /* if this is an IPv4-mapped address then do IPv4 QoS */ |
| if (af == AF_INET6) { |
| GST_DEBUG_OBJECT (sink, "check IP6 socket"); |
| if (IN6_IS_ADDR_V4MAPPED (&(sa.sa_in6.sin6_addr))) { |
| GST_DEBUG_OBJECT (sink, "mapped to IPV4"); |
| af = AF_INET; |
| } |
| } |
| /* extract and shift 6 bits of the DSCP */ |
| tos = (sink->qos_dscp & 0x3f) << 2; |
| |
| switch (af) { |
| case AF_INET: |
| ret = setsockopt (sink->fd.fd, IPPROTO_IP, IP_TOS, &tos, sizeof (tos)); |
| break; |
| case AF_INET6: |
| #ifdef IPV6_TCLASS |
| ret = setsockopt (sink->fd.fd, IPPROTO_IPV6, IPV6_TCLASS, &tos, |
| sizeof (tos)); |
| break; |
| #endif |
| default: |
| GST_ERROR_OBJECT (sink, "unsupported AF"); |
| break; |
| } |
| if (ret) { |
| GST_DEBUG_OBJECT (sink, "could not set DSCP: %s", g_strerror (errno)); |
| } |
| |
| return ret; |
| } |