You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by so...@apache.org on 2018/02/28 18:22:47 UTC

[trafficserver] branch master updated: Pacing Plugin. Requires fq qdisc.

This is an automated email from the ASF dual-hosted git repository.

sorber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new 2465ed4  Pacing Plugin. Requires fq qdisc.
2465ed4 is described below

commit 2465ed44c797c29c90d2d286e4cd80ec307fc105
Author: Eric Friedrich <ef...@cisco.com>
AuthorDate: Wed Nov 15 09:01:03 2017 -0500

    Pacing Plugin. Requires fq qdisc.
---
 doc/admin-guide/plugins/fq_pacing.en.rst    |  48 ++++++
 doc/admin-guide/plugins/index.en.rst        |   4 +
 plugins/Makefile.am                         |   1 +
 plugins/experimental/fq_pacing/Makefile.inc |  20 +++
 plugins/experimental/fq_pacing/README.md    |  22 +++
 plugins/experimental/fq_pacing/fq_pacing.c  | 237 ++++++++++++++++++++++++++++
 6 files changed, 332 insertions(+)

diff --git a/doc/admin-guide/plugins/fq_pacing.en.rst b/doc/admin-guide/plugins/fq_pacing.en.rst
new file mode 100644
index 0000000..8b5f47e
--- /dev/null
+++ b/doc/admin-guide/plugins/fq_pacing.en.rst
@@ -0,0 +1,48 @@
+.. Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed
+   with this work for additional information regarding copyright
+   ownership.  The ASF licenses this file to you under the Apache
+   License, Version 2.0 (the "License"); you may not use this file
+   except in compliance with the License.  You may obtain a copy of
+   the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+   implied.  See the License for the specific language governing
+   permissions and limitations under the License.
+
+.. _admin-plugins-fq-pacing:
+
+
+FQ Pacing Plugin
+==================
+
+This is a remap plugin  that allows ATS to rate limit an individual TCP connection. It is based on
+Linux support for the Fair Queuing qdisc. FQ and SO_MAX_PACING_RATE is available in RedHat/Centos 7.2+,
+Debian 8+, and any other Linux distro with a kernel 3.18 or greater.
+
+
+How it Works
+------------
+When activated during remap processing, this plugin calls ``setsockopt(SO_MAX_PACING_RATE)`` on the
+client socket. To prevent the rate from leaking to other remap rules the client may access in future
+requests, a hook is set to deactivate the pacing when the current transaction completes. 
+
+
+Installation
+------------
+First, enable the FQ qdisc by setting ``net.core.default_qdisc=fq`` in ``/etc/sysctl.conf`` and rebooting.
+
+The `FQ Pacing` plugin is a :term:`remap plugin`.  Enable it by adding
+``fq_pacing.so`` to your :file:`remap.config` file.  Provide a ``--rate=BytesPerSec`` option to set
+the maxmimum rate of a TCP connection matching that remap line.
+
+Here is an example remap.config entry:
+
+::
+
+  map http://reverse-fqdn.com http://origin.com @plugin=fq_pacing.so @pparam=--rate=100000
+
diff --git a/doc/admin-guide/plugins/index.en.rst b/doc/admin-guide/plugins/index.en.rst
index 9081182..9e2b171 100644
--- a/doc/admin-guide/plugins/index.en.rst
+++ b/doc/admin-guide/plugins/index.en.rst
@@ -131,6 +131,7 @@ directory of the |TS| source tree. Experimental plugins can be compiled by passi
    Epic <epic.en>
    Escalate <escalate.en>
    GeoIP ACL <geoip_acl.en>
+   FQ Pacing <fq_pacing.en>
    Header Frequency <header_freq.en>
    HIPES <hipes.en>
    Hook Trace <hook-trace.en>
@@ -168,6 +169,9 @@ directory of the |TS| source tree. Experimental plugins can be compiled by passi
 :doc:`Escalate <escalate.en>`
    Escalate: when the origin returns specific status codes, retry the request at a secondary origin (failover/fail-action)
 
+:doc:`FQ Pacing <fq_pacing.en>`
+   FQ Pacing: Rate Limit TCP connections using Linux's Fair Queuing queue discipline
+
 :doc:`GeoIP ACL <geoip_acl.en>`
    Deny or allow requests based on the source IP geo-location.
 
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index 20337dc..f7046fd 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -56,6 +56,7 @@ include experimental/collapsed_forwarding/Makefile.inc
 include experimental/custom_redirect/Makefile.inc
 include experimental/epic/Makefile.inc
 include experimental/escalate/Makefile.inc
+include experimental/fq_pacing/Makefile.inc
 include experimental/geoip_acl/Makefile.inc
 include experimental/header_freq/Makefile.inc
 include experimental/header_normalize/Makefile.inc
diff --git a/plugins/experimental/fq_pacing/Makefile.inc b/plugins/experimental/fq_pacing/Makefile.inc
new file mode 100644
index 0000000..8da2803
--- /dev/null
+++ b/plugins/experimental/fq_pacing/Makefile.inc
@@ -0,0 +1,20 @@
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+pkglib_LTLIBRARIES += experimental/fq_pacing/fq_pacing.la
+
+experimental_fq_pacing_fq_pacing_la_SOURCES = \
+  experimental/fq_pacing/fq_pacing.c
\ No newline at end of file
diff --git a/plugins/experimental/fq_pacing/README.md b/plugins/experimental/fq_pacing/README.md
new file mode 100644
index 0000000..b2134c0
--- /dev/null
+++ b/plugins/experimental/fq_pacing/README.md
@@ -0,0 +1,22 @@
+fq_pacing Plugin
+====
+
+# Overview
+This plugin rate limits individual TCP connections on a per-remap basis.
+
+The pacing is accomplished by using `setsockopt(SO_MAX_PACING_RATE)` and the
+Fair Queuing (see `man 8 tc-fq`) qdisc.
+
+## Supported Platforms
+* Linux
+ * RedHat/Centos 7.2+
+ * Debian 8x
+ * Any Linux kernel >= 3.18
+
+# Configuration Instructions (Linux)
+1. Before activating this plugin, you must enable the fair queuing qdisc by setting `net.core.default_qdisc = fq` in /etc/sysctl.conf` and rebooting.
+2. Confirm the qdisc is active by running `sysctl net.core.default_qdisc`
+3. Specify the plugin name and rate (bytes per second) on a remap line in `remap.config` to activate:
+```
+map http://reverse-fqdn/ http://origin/ @plugin=fq_pacing.so @pparam=--rate=100000
+```
diff --git a/plugins/experimental/fq_pacing/fq_pacing.c b/plugins/experimental/fq_pacing/fq_pacing.c
new file mode 100644
index 0000000..dc79ee2
--- /dev/null
+++ b/plugins/experimental/fq_pacing/fq_pacing.c
@@ -0,0 +1,237 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ts/ts.h>
+#include <ts/remap.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+static const char *PLUGIN_NAME = "fq_pacing";
+
+// Sanity check max rate at 100Gbps
+#define MAX_PACING_RATE 100000000000
+
+typedef struct fq_pacing_config {
+  unsigned long pacing_rate;
+} fq_pacing_cfg_t;
+
+typedef struct fq_pacing_cont {
+  int client_fd;
+} fq_pacing_cont_t;
+
+// Copied from ts/ink_sock.cc since that function is not exposed to plugins
+int
+safe_setsockopt(int s, int level, int optname, char *optval, int optlevel)
+{
+  int r;
+  do {
+    r = setsockopt(s, level, optname, optval, optlevel);
+  } while (r < 0 && (errno == EAGAIN || errno == EINTR));
+  return r;
+}
+
+static int
+fq_is_default_qdisc()
+{
+  TSFile f       = 0;
+  size_t s       = 0;
+  char buffer[4] = {};
+  int rc         = 0;
+
+  f = TSfopen("/proc/sys/net/core/default_qdisc", "r");
+  if (!f) {
+    return 0;
+  }
+
+  s = TSfread(f, buffer, sizeof(buffer));
+  if (s > 0) {
+    buffer[s] = 0;
+  } else {
+    TSfclose(f);
+    return 0;
+  }
+
+  if (buffer[2] == '\n') {
+    buffer[2] = 0;
+  }
+
+  rc = (strncmp(buffer, "fq", sizeof(buffer)) == 0);
+  TSfclose(f);
+  return (rc);
+}
+
+void
+TSPluginInit(int argc, const char *argv[])
+{
+  TSPluginRegistrationInfo info;
+
+  info.plugin_name   = (char *)"fq_pacing";
+  info.vendor_name   = (char *)"Cisco Systems";
+  info.support_email = (char *)"omdbuild@cisco.com";
+
+  if (TSPluginRegister(&info) != TS_SUCCESS) {
+    TSError("[fq_pacing] plugin registration failed");
+  }
+}
+
+TSReturnCode
+TSRemapInit(TSRemapInterface *api_info, char *errbuf, int errbuf_size)
+{
+  if (!api_info) {
+    strncpy(errbuf, "[fq_pacing] - Invalid TSRemapInterface argument", (size_t)(errbuf_size - 1));
+    return TS_ERROR;
+  }
+
+  if (api_info->size < sizeof(TSRemapInterface)) {
+    strncpy(errbuf, "[TSRemapInit] - Incorrect size of TSRemapInterface structure", errbuf_size - 1);
+    return TS_ERROR;
+  }
+
+  if (api_info->tsremap_version < TSREMAP_VERSION) {
+    snprintf(errbuf, errbuf_size - 1, "[TSRemapInit] - Incorrect API version %ld.%ld", api_info->tsremap_version >> 16,
+             (api_info->tsremap_version & 0xffff));
+    return TS_ERROR;
+  }
+
+  if (!fq_is_default_qdisc()) {
+    snprintf(errbuf, errbuf_size - 1, "[TSRemapInit] - fq qdisc is not active");
+    return TS_ERROR;
+  }
+
+  TSDebug(PLUGIN_NAME, "plugin is succesfully initialized");
+  return TS_SUCCESS;
+}
+
+TSReturnCode
+TSRemapNewInstance(int argc, char *argv[], void **ih, char *errbuf, int errbuf_size)
+{
+  fq_pacing_cfg_t *cfg      = NULL;
+  unsigned long pacing_rate = 0;
+
+  TSDebug(PLUGIN_NAME, "Instantiating a new remap.config plugin rule");
+
+  if (argc > 1) {
+    int c;
+    static const struct option longopts[] = {{"rate", required_argument, NULL, 'r'}, {NULL, 0, NULL, 0}};
+
+    // The "-" in optstring is required to prevent permutation of argv, which
+    // makes the plugin loader crashy
+    while ((c = getopt_long(argc, (char *const *)argv, "-r:", longopts, NULL)) != -1) {
+      switch (c) {
+      case 'r':
+        errno       = 0;
+        pacing_rate = strtoul(optarg, NULL, 0);
+        if (errno != 0) {
+          snprintf(errbuf, errbuf_size - 1, "[TsRemapNewInstance] input pacing value is not a valid positive integer");
+          return TS_ERROR;
+        }
+
+        break;
+      }
+    }
+  }
+
+  if (pacing_rate > MAX_PACING_RATE) {
+    snprintf(errbuf, errbuf_size - 1, "[TsRemapNewInstance] input pacing value is too large (%lu), max(%lu)", pacing_rate,
+             MAX_PACING_RATE);
+    return TS_ERROR;
+  }
+
+  cfg = TSmalloc(sizeof(fq_pacing_cfg_t));
+  memset(cfg, 0, sizeof(*cfg));
+  cfg->pacing_rate = pacing_rate;
+  *ih              = (void *)cfg;
+  TSDebug(PLUGIN_NAME, "Setting pacing rate to %lu", pacing_rate);
+
+  return TS_SUCCESS;
+}
+
+void
+TSRemapDeleteInstance(void *instance)
+{
+  TSError("[fq_pacing] Cleaning up...");
+
+  if (instance != NULL) {
+    TSfree((fq_pacing_cfg_t *)instance);
+  }
+}
+
+static int
+reset_pacing_cont(TSCont contp, TSEvent event, void *edata)
+{
+  TSHttpTxn txnp             = (TSHttpTxn)edata;
+  fq_pacing_cont_t *txn_data = TSContDataGet(contp);
+
+#ifdef SO_MAX_PACING_RATE
+  unsigned int pacing_off = ~0U;
+  if (txn_data->client_fd > 0) {
+    TSDebug(PLUGIN_NAME, "Disabling SO_MAX_PACING_RATE for client_fd=%d", txn_data->client_fd);
+    int res = 0;
+    res     = safe_setsockopt(txn_data->client_fd, SOL_SOCKET, SO_MAX_PACING_RATE, (char *)&pacing_off, sizeof(pacing_off));
+    // EBADF indicates possible client abort
+    if ((res < 0) && (errno != EBADF)) {
+      TSError("[fq_pacing] Error disabling SO_MAX_PACING_RATE, errno=%d", errno);
+    }
+  }
+#endif
+
+  TSfree(txn_data);
+  TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
+  return 0;
+}
+
+TSRemapStatus
+TSRemapDoRemap(void *instance, TSHttpTxn txnp, TSRemapRequestInfo *rri)
+{
+  if (TSHttpTxnClientProtocolStackContains(txnp, TS_PROTO_TAG_HTTP_2_0) != NULL) {
+    TSDebug(PLUGIN_NAME, "Skipping plugin execution for HTTP/2 requests");
+    return TSREMAP_NO_REMAP;
+  }
+
+  int client_fd = 0;
+  if (TSHttpTxnClientFdGet(txnp, &client_fd) != TS_SUCCESS) {
+    TSError("[fq_pacing] Error getting client fd");
+  }
+
+#ifdef SO_MAX_PACING_RATE
+  fq_pacing_cfg_t *cfg = (fq_pacing_cfg_t *)instance;
+  int res              = 0;
+
+  res = safe_setsockopt(client_fd, SOL_SOCKET, SO_MAX_PACING_RATE, (char *)&cfg->pacing_rate, sizeof(cfg->pacing_rate));
+  if ((res < 0)) {
+    TSError("[fq_pacing] Error setting SO_MAX_PACING_RATE, errno=%d", errno);
+  }
+  TSDebug(PLUGIN_NAME, "Setting SO_MAX_PACING_RATE for client_fd=%d to %lu Bps", client_fd, cfg->pacing_rate);
+#endif
+
+  // Reset pacing at end of transaction in case session is
+  // reused for another delivery service w/o pacing
+  TSCont cont = TSContCreate(reset_pacing_cont, NULL);
+
+  fq_pacing_cont_t *txn_data = TSmalloc(sizeof(fq_pacing_cont_t));
+  txn_data->client_fd        = client_fd;
+  TSContDataSet(cont, txn_data);
+
+  TSHttpTxnHookAdd(txnp, TS_HTTP_TXN_CLOSE_HOOK, cont);
+  return TSREMAP_NO_REMAP;
+}

-- 
To stop receiving notification emails like this one, please contact
sorber@apache.org.