Docs/traffic-control

From Apertis
Jump to: navigation, search

Traffic control is a technique to control network traffic in order to optimize or guarantee performance, low-latency, and/or bandwidth. Apertis includes the daemon tcmmd and a demo GStreamer application tcdemo. They show how traffic control helps a multimedia application can get enough bandwidth for its purpose when competiting with other downloads.

Ref. chapter 2.11, 3.10 in Multimedia design

Contents

The sources

The first publicly available version of tcmmd and tcdemo is 0.1.10. The current version of tcmmd and tcdemo in Apertis is 0.1.12. The code is available in git:

git clone git@github.com:alban/tcmmd.git

Test cases

QA/Test Cases/traffic-control-tcmmd

How to use it in an application

A multimedia application rendering streamed content wishing to benefit from traffic control needs to:

  • Get the 5-tuple identifying the connection and send it to tcmmd
  • Monitor the usage of its downloading queue in the pipeline and notify tcmmd

The tcdemo.c can be taken as an example doing this.

Identify the connection

The application needs to get:

  1. the IP source (the same IP visible with the command ifconfig on the correct interface)
  2. the IP destination (the IP of the web server)
  3. the TCP source port (usually chosen randomly by the kernel)
  4. the TCP destination port (likely to be 80 for http)

The TCP source port is probably the most difficult to get. With the low-level socket API, there is two different ways to do this:

  1. The application can call bind(2) before connect(2) to choose a TCP source port. This solution allows to install the traffic control rules before the call to connect(2) triggers the emission and reception of the first packets on the network. It is a more difficult solution because it involves more invasive changes in GStreamer and libsoup.
  2. The application can call getsockname(2) after connect(2) to retrieve the TCP source port assigned automatically by the kernel. This solution means the first few packets will be exchanged without being shaped by the traffic control but it does not have a big impact. Tcdemo implements this solution to avoid more invasive changes in the souphttpsrc GStreamer element and libsoup. See bgo#721807.

In an application using GStreamer and libsoup, there are no needs to use the low-level socket API (bind, connect, getsockname, etc.).

The change in GStreamer adds a "soup-socket" property on the source GstElement.

  • Find the souphttpsrc element:

static void
source_setup_cb (GstElement *playbin,
    GstElement *source,
    DemoData *self);
...
gint
main (gint argc,
      gchar *argv[])
{
  ...
  playbin = clutter_gst_player_get_pipeline (CLUTTER_GST_PLAYER (player));
  g_signal_connect (playbin, "source-setup",
      G_CALLBACK (source_setup_cb), &self);

  • Receive notifications for SoupSocket changes:

static void
update_soup_socket (DemoData *self);
...
static void
source_setup_cb (GstElement *playbin,
                 GstElement *source,
                 DemoData *self)
{
  ...
  /* Watch the SoupSocket on which current trafic is done */
  g_signal_connect_swapped (self->source, "notify::soup-socket",
      G_CALLBACK (update_soup_socket), self);

  • Get the SoupSocket

static void
update_soup_socket (DemoData *self)
{
  SoupSocket *socket;

  g_object_get (self->source, "soup-socket", &socket, NULL);

  • Get the socket information: IP source/destination, TCP source/destination port

 SoupAddress *local_address;
 SoupAddress *remote_address;

 local_address = soup_socket_get_local_address (self->socket);
 remote_address = soup_socket_get_remote_address (self->socket);

 soup_address_get_physical (local_address);
 soup_address_get_port (local_address);
 soup_address_get_physical (remote_address);
 soup_address_get_port (remote_address);

Monitor the downloading queue

We use ClutterGstPlayback to wrap the GStreamer pipeline in a Clutter widget. The pipeline contains a GstQueue2 which already emits a GstMessage of type GST_MESSAGE_BUFFERING on the GStreamer bus. That message is forwarded by the ClutterGstPlayback object so we can just get the notification with:

ClutterGstPlayback *player;
...
player = clutter_gst_playback_new ();
...
g_signal_connect (player, "notify::buffer-fill",
                  G_CALLBACK (buffer_fill_notify_cb), &self);

The callback can get the buffer_fill value between 0.0 (0%) and 1.0 (100%):

static void
buffer_fill_notify_cb (ClutterGstPlayer *player,
    GParamSpec *param_spec,
    DemoData *self)
{
  gdouble buffer_fill;

  g_object_get (player, "buffer-fill", &buffer_fill, NULL);

However, after the initial buffering, by default, the notifications get sent only when the value drops to lower than 10% (low-percent). Below 10%, the GStreamer queue is almost empty, so this is almost too late for tcmmd to change the traffic control rules. In order to get notified sooner, tcdemo changes the low-percent threshold on the GstQueue2 element:

g_object_set (element, "low-percent", 75, NULL);

It is also possible to change other parameters on the GstQueue2 at the same time if desired:

g_object_set (element,
              "low-percent", 75,
              "max-size-bytes", 4194304,
              "max-size-buffers", 200,
              "max-size-time", G_GUINT64_CONSTANT(4000000000),
              NULL);

How to get the reference to the GstQueue2 is more difficult because ClutterGstPlayback does not provide a direct API for that. Tcdemo iterates over the GStreamer elements in the pipeline to find it:

static void
_playbin_set_low_percent (GstBin *playbin)
{
  GstIterator *it;
  gboolean done;
  GValue value = { 0, };
  GstElement *element;

  it = gst_bin_iterate_recurse (GST_BIN (playbin));

  done = FALSE;
  while (!done) {
    switch (gst_iterator_next (it, &value)) {
      case GST_ITERATOR_OK:
        element = GST_ELEMENT (g_value_get_object (&value));
        /* the pipeline has a GstMultiQueue but it seems GstQueue2 is the one
         * used here. */
        if (element &&
            g_type_from_name ("GstQueue2") == G_TYPE_FROM_INSTANCE (element))
          {
            g_print ("- set low-percent on GstQueue2.\n");
            g_object_set (element,
                          "low-percent", 75,
                          "max-size-bytes", 4194304,
                          "max-size-buffers", 200,
                          "max-size-time", G_GUINT64_CONSTANT(4000000000),
                          NULL);
          }
        g_value_unset (&value);
        break;
      case GST_ITERATOR_RESYNC:
        gst_iterator_resync (it);
        break;
      case GST_ITERATOR_ERROR:
        done = TRUE;
        break;
      case GST_ITERATOR_DONE:
        done = TRUE;
        break;
    }
  }

  gst_iterator_free (it);
}

It can be called like this:

GstElement *playbin;

playbin = clutter_gst_player_get_pipeline (CLUTTER_GST_PLAYER (player));
_playbin_set_low_percent (GST_BIN (playbin));

Notify tcmmd

tcmmd implements a D-Bus interface defined in src/gdbus-tcmmd.xml. Tcdemo calls two D-Bus methods:

<method name="SetPolicy">
  <arg direction="in" type="s" name="src_ip"/>
  <arg direction="in" type="u" name="src_port"/>
  <arg direction="in" type="s" name="dest_ip"/>
  <arg direction="in" type="u" name="dest_port"/>

  <arg direction="in" type="u" name="bitrate"/>
  <arg direction="in" type="d" name="buffer_fill"/>
</method>

<method name="UnsetPolicy">
</method>

Tcdemo uses GDBus and the code generated by gdbus-codegen from the xml to keep a proxy on tcmmd's D-Bus object:

static void
got_proxy_cb (GObject *source,
    GAsyncResult *result,
    gpointer user_data)
{
  DemoData *self = user_data;
  GError *error = NULL;
  self->proxy = tcmmd_managed_connections_proxy_new_for_bus_finish (result,
                    &error);
  ...
}
...
tcmmd_managed_connections_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
    G_DBUS_PROXY_FLAGS_NONE,
    "org.tcmmd",
    "/org/tcmmd/ManagedConnections",
    NULL, got_proxy_cb, &self);

SetPolicy is called as soon as the connection details are known from libsoup:

 tcmmd_managed_connections_call_set_policy (self->proxy,
     soup_address_get_physical (local_address),
     soup_address_get_port (local_address),
     soup_address_get_physical (remote_address),
     soup_address_get_port (remote_address),
     bitrate,
     self->buffer_fill,
     NULL, NULL, NULL);

bitrate is not used by tcmmd and can be left to zero.

UnsetPolicy must be called when the SoupSocket is not used anymore in order to remove the traffic control rules and let any traffic go freely:

tcmmd_managed_connections_call_unset_policy (self->proxy, NULL, NULL, NULL);

If tcdemo leaves the D-Bus system bus, tcdemo removes the traffic control rules automatically.

Known limitations

  • tcmmd only works on one network interface at a time. It is a problem with tethering. Also, it selects the interface when it starts and does not handle interfaces going up and down. (Bug#2417)
  • tcmmd only supports one stream connection at a time. It is a problem if the user is watching two videos at the same time.

Presentations

Personal tools
Namespaces

Variants
Actions
Navigation
Tools