summaryrefslogtreecommitdiff
path: root/accel-pppd/shaper/limiter.c
diff options
context:
space:
mode:
Diffstat (limited to 'accel-pppd/shaper/limiter.c')
-rw-r--r--accel-pppd/shaper/limiter.c536
1 files changed, 536 insertions, 0 deletions
diff --git a/accel-pppd/shaper/limiter.c b/accel-pppd/shaper/limiter.c
new file mode 100644
index 00000000..d985682b
--- /dev/null
+++ b/accel-pppd/shaper/limiter.c
@@ -0,0 +1,536 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/ioctl.h>
+#include <linux/if.h>
+#include <linux/if_ether.h>
+#include <linux/pkt_cls.h>
+#include <linux/pkt_sched.h>
+#include <linux/tc_act/tc_mirred.h>
+#include <linux/tc_act/tc_skbedit.h>
+
+#include "log.h"
+#include "ppp.h"
+
+#include "shaper.h"
+#include "tc_core.h"
+#include "libnetlink.h"
+
+#define TCA_BUF_MAX 64*1024
+#define MAX_MSG 16384
+
+static __thread struct {
+ struct nlmsghdr n;
+ struct tcmsg t;
+ char buf[TCA_BUF_MAX];
+} req;
+
+struct qdisc_opt
+{
+ char *kind;
+ int handle;
+ int parent;
+ double latency;
+ int rate;
+ int buffer;
+ int quantum;
+ int defcls;
+ int (*qdisc)(struct qdisc_opt *opt, struct nlmsghdr *n);
+};
+
+static int qdisc_tbf(struct qdisc_opt *qopt, struct nlmsghdr *n)
+{
+ struct tc_tbf_qopt opt;
+ __u32 rtab[256];
+ int mtu = 0;
+ int Rcell_log = -1;
+ unsigned int linklayer = LINKLAYER_ETHERNET; /* Assume ethernet */
+ struct rtattr *tail;
+
+ memset(&opt, 0, sizeof(opt));
+
+ opt.rate.rate = qopt->rate;
+ opt.limit = (double)qopt->rate * qopt->latency + qopt->buffer;
+ opt.rate.mpu = conf_mpu;
+ if (tc_calc_rtable(&opt.rate, rtab, Rcell_log, mtu, linklayer) < 0) {
+ log_ppp_error("shaper: failed to calculate rate table.\n");
+ return -1;
+ }
+ opt.buffer = tc_calc_xmittime(opt.rate.rate, qopt->buffer);
+
+ tail = NLMSG_TAIL(n);
+ addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
+ addattr_l(n, 2024, TCA_TBF_PARMS, &opt, sizeof(opt));
+ addattr_l(n, 3024, TCA_TBF_RTAB, rtab, 1024);
+ tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+
+ return 0;
+}
+
+static int qdisc_htb_root(struct qdisc_opt *qopt, struct nlmsghdr *n)
+{
+ struct tc_htb_glob opt;
+ struct rtattr *tail;
+
+ memset(&opt,0,sizeof(opt));
+
+ opt.rate2quantum = qopt->quantum;
+ opt.version = 3;
+ opt.defcls = qopt->defcls;
+
+ tail = NLMSG_TAIL(n);
+ addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
+ addattr_l(n, 2024, TCA_HTB_INIT, &opt, NLMSG_ALIGN(sizeof(opt)));
+ tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+ return 0;
+}
+
+static int qdisc_htb_class(struct qdisc_opt *qopt, struct nlmsghdr *n)
+{
+ struct tc_htb_opt opt;
+ __u32 rtab[256],ctab[256];
+ int cell_log=-1,ccell_log = -1;
+ unsigned mtu = 1600;
+ unsigned int linklayer = LINKLAYER_ETHERNET; /* Assume ethernet */
+ struct rtattr *tail;
+
+ memset(&opt, 0, sizeof(opt));
+
+ opt.rate.rate = qopt->rate;
+ opt.rate.mpu = conf_mpu;
+ opt.ceil.rate = qopt->rate;
+ opt.ceil.mpu = conf_mpu;
+
+ if (tc_calc_rtable(&opt.rate, rtab, cell_log, mtu, linklayer) < 0) {
+ log_ppp_error("shaper: failed to calculate rate table.\n");
+ return -1;
+ }
+ opt.buffer = tc_calc_xmittime(opt.rate.rate, qopt->buffer);
+
+ if (tc_calc_rtable(&opt.ceil, ctab, ccell_log, mtu, linklayer) < 0) {
+ log_ppp_error("shaper: failed to calculate ceil rate table.\n");
+ return -1;
+ }
+ opt.cbuffer = tc_calc_xmittime(opt.ceil.rate, qopt->buffer);
+
+ tail = NLMSG_TAIL(n);
+ addattr_l(n, 1024, TCA_OPTIONS, NULL, 0);
+ addattr_l(n, 2024, TCA_HTB_PARMS, &opt, sizeof(opt));
+ addattr_l(n, 3024, TCA_HTB_RTAB, rtab, 1024);
+ addattr_l(n, 4024, TCA_HTB_CTAB, ctab, 1024);
+ tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail;
+ return 0;
+}
+
+static int tc_qdisc_modify(struct rtnl_handle *rth, int ifindex, int cmd, unsigned flags, struct qdisc_opt *opt)
+{
+ memset(&req, 0, sizeof(req));
+
+ req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg));
+ req.n.nlmsg_flags = NLM_F_REQUEST|flags;
+ req.n.nlmsg_type = cmd;
+ req.t.tcm_family = AF_UNSPEC;
+
+ req.t.tcm_ifindex = ifindex;
+
+ if (opt->handle)
+ req.t.tcm_handle = opt->handle;
+
+ req.t.tcm_parent = opt->parent;
+
+ if (opt->kind)
+ addattr_l(&req.n, sizeof(req), TCA_KIND, opt->kind, strlen(opt->kind) + 1);
+
+ if (opt->qdisc)
+ opt->qdisc(opt, &req.n);
+
+ if (rtnl_talk(rth, &req.n, 0, 0, NULL, NULL, NULL, cmd == RTM_DELQDISC) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int install_tbf(struct rtnl_handle *rth, int ifindex, int rate, int burst)
+{
+ struct qdisc_opt opt = {
+ .kind = "tbf",
+ .handle = 0x00010000,
+ .parent = TC_H_ROOT,
+ .rate = rate,
+ .buffer = burst,
+ .latency = conf_latency,
+ .qdisc = qdisc_tbf,
+ };
+
+ return tc_qdisc_modify(rth, ifindex, RTM_NEWQDISC, NLM_F_EXCL|NLM_F_CREATE, &opt);
+}
+
+static int install_htb(struct rtnl_handle *rth, int ifindex, int rate, int burst)
+{
+ struct qdisc_opt opt1 = {
+ .kind = "htb",
+ .handle = 0x00010000,
+ .parent = TC_H_ROOT,
+ .quantum = conf_r2q,
+ .defcls = 1,
+ .qdisc = qdisc_htb_root,
+ };
+
+ struct qdisc_opt opt2 = {
+ .kind = "htb",
+ .handle = 0x00010001,
+ .parent = 0x00010000,
+ .rate = rate,
+ .buffer = burst,
+ .quantum = conf_quantum,
+ .qdisc = qdisc_htb_class,
+ };
+
+
+ if (tc_qdisc_modify(rth, ifindex, RTM_NEWQDISC, NLM_F_EXCL|NLM_F_CREATE, &opt1))
+ return -1;
+
+ if (tc_qdisc_modify(rth, ifindex, RTM_NEWTCLASS, NLM_F_EXCL|NLM_F_CREATE, &opt2))
+ return -1;
+
+ return 0;
+}
+
+static int install_police(struct rtnl_handle *rth, int ifindex, int rate, int burst)
+{
+ __u32 rtab[256];
+ struct rtattr *tail, *tail1, *tail2, *tail3;
+ int Rcell_log = -1;
+ int mtu = 0, flowid = 1;
+ unsigned int linklayer = LINKLAYER_ETHERNET; /* Assume ethernet */
+
+ struct qdisc_opt opt1 = {
+ .kind = "ingress",
+ .handle = 0xffff0000,
+ .parent = TC_H_INGRESS,
+ };
+
+ struct sel {
+ struct tc_u32_sel sel;
+ struct tc_u32_key key;
+ } sel = {
+ .sel.nkeys = 1,
+ .sel.flags = TC_U32_TERMINAL,
+ .key.off = 12,
+ };
+
+ struct tc_police police = {
+ .action = TC_POLICE_SHOT,
+ .rate.rate = rate,
+ .rate.mpu = conf_mpu,
+ .limit = (double)rate * conf_latency + burst,
+ .burst = tc_calc_xmittime(rate, burst),
+ };
+
+ if (tc_qdisc_modify(rth, ifindex, RTM_NEWQDISC, NLM_F_EXCL|NLM_F_CREATE, &opt1))
+ return -1;
+
+ if (tc_calc_rtable(&police.rate, rtab, Rcell_log, mtu, linklayer) < 0) {
+ log_ppp_error("shaper: failed to calculate ceil rate table.\n");
+ return -1;
+ }
+
+ memset(&req, 0, sizeof(req));
+
+ req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg));
+ req.n.nlmsg_flags = NLM_F_REQUEST|NLM_F_EXCL|NLM_F_CREATE;
+ req.n.nlmsg_type = RTM_NEWTFILTER;
+ req.t.tcm_family = AF_UNSPEC;
+ req.t.tcm_ifindex = ifindex;
+ req.t.tcm_handle = 1;
+ req.t.tcm_parent = 0xffff0000;
+ req.t.tcm_info = TC_H_MAKE(1 << 16, ntohs(ETH_P_IP));
+
+ addattr_l(&req.n, sizeof(req), TCA_KIND, "u32", 4);
+
+ tail = NLMSG_TAIL(&req.n);
+ addattr_l(&req.n, MAX_MSG, TCA_OPTIONS, NULL, 0);
+
+ tail1 = NLMSG_TAIL(&req.n);
+ addattr_l(&req.n, MAX_MSG, TCA_U32_ACT, NULL, 0);
+
+ tail2 = NLMSG_TAIL(&req.n);
+ addattr_l(&req.n, MAX_MSG, 1, NULL, 0);
+ addattr_l(&req.n, MAX_MSG, TCA_ACT_KIND, "police", 7);
+
+ tail3 = NLMSG_TAIL(&req.n);
+ addattr_l(&req.n, MAX_MSG, TCA_ACT_OPTIONS, NULL, 0);
+ addattr_l(&req.n, MAX_MSG, TCA_POLICE_TBF, &police, sizeof(police));
+ addattr_l(&req.n, MAX_MSG, TCA_POLICE_RATE, rtab, 1024);
+ tail3->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)tail3;
+
+ tail2->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)tail2;
+
+ tail1->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)tail1;
+
+ addattr_l(&req.n, MAX_MSG, TCA_U32_CLASSID, &flowid, 4);
+ addattr_l(&req.n, MAX_MSG, TCA_U32_SEL, &sel, sizeof(sel));
+ tail->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)tail;
+
+ if (rtnl_talk(rth, &req.n, 0, 0, NULL, NULL, NULL, 0) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int install_htb_ifb(struct rtnl_handle *rth, int ifindex, __u32 priority, int rate, int burst)
+{
+ struct rtattr *tail, *tail1, *tail2, *tail3;
+
+ struct qdisc_opt opt1 = {
+ .kind = "htb",
+ .handle = 0x00010001 + priority,
+ .parent = 0x00010000,
+ .rate = rate,
+ .buffer = burst,
+ .quantum = conf_quantum,
+ .qdisc = qdisc_htb_class,
+ };
+
+ struct qdisc_opt opt2 = {
+ .kind = "ingress",
+ .handle = 0xffff0000,
+ .parent = TC_H_INGRESS,
+ };
+
+ struct sel {
+ struct tc_u32_sel sel;
+ struct tc_u32_key key;
+ } sel = {
+ .sel.nkeys = 1,
+ .sel.flags = TC_U32_TERMINAL,
+ .key.off = 0,
+ };
+
+ struct tc_skbedit p1 = {
+ .action = TC_ACT_PIPE,
+ };
+
+ struct tc_mirred p2 = {
+ .eaction = TCA_EGRESS_REDIR,
+ .action = TC_ACT_STOLEN,
+ .ifindex = conf_ifb_ifindex,
+ };
+
+ if (tc_qdisc_modify(rth, conf_ifb_ifindex, RTM_NEWTCLASS, NLM_F_EXCL|NLM_F_CREATE, &opt1))
+ return -1;
+
+ if (tc_qdisc_modify(rth, ifindex, RTM_NEWQDISC, NLM_F_EXCL|NLM_F_CREATE, &opt2))
+ return -1;
+
+ memset(&req, 0, sizeof(req));
+
+ req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg));
+ req.n.nlmsg_flags = NLM_F_REQUEST|NLM_F_EXCL|NLM_F_CREATE;
+ req.n.nlmsg_type = RTM_NEWTFILTER;
+ req.t.tcm_family = AF_UNSPEC;
+ req.t.tcm_ifindex = ifindex;
+ req.t.tcm_handle = 1;
+ req.t.tcm_parent = 0xffff0000;
+ req.t.tcm_info = TC_H_MAKE(1 << 16, ntohs(ETH_P_IP));
+
+ addattr_l(&req.n, sizeof(req), TCA_KIND, "u32", 4);
+
+ tail = NLMSG_TAIL(&req.n);
+ addattr_l(&req.n, MAX_MSG, TCA_OPTIONS, NULL, 0);
+
+ tail1 = NLMSG_TAIL(&req.n);
+ addattr_l(&req.n, MAX_MSG, TCA_U32_ACT, NULL, 0);
+
+ // action skbedit priority X pipe
+ tail2 = NLMSG_TAIL(&req.n);
+ addattr_l(&req.n, MAX_MSG, 1, NULL, 0);
+ addattr_l(&req.n, MAX_MSG, TCA_ACT_KIND, "skbedit", 8);
+
+ tail3 = NLMSG_TAIL(&req.n);
+ addattr_l(&req.n, MAX_MSG, TCA_ACT_OPTIONS, NULL, 0);
+ addattr_l(&req.n, MAX_MSG, TCA_SKBEDIT_PARMS, &p1, sizeof(p1));
+ addattr_l(&req.n, MAX_MSG, TCA_SKBEDIT_PRIORITY, &priority, sizeof(priority));
+ tail3->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)tail3;
+
+ tail2->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)tail2;
+
+ tail1->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)tail1;
+
+ // action mirred egress redirect dev ifb0
+ tail2 = NLMSG_TAIL(&req.n);
+ addattr_l(&req.n, MAX_MSG, 2, NULL, 0);
+ addattr_l(&req.n, MAX_MSG, TCA_ACT_KIND, "mirred", 7);
+
+ tail3 = NLMSG_TAIL(&req.n);
+ addattr_l(&req.n, MAX_MSG, TCA_ACT_OPTIONS, NULL, 0);
+ addattr_l(&req.n, MAX_MSG, TCA_MIRRED_PARMS, &p2, sizeof(p2));
+ tail3->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)tail3;
+
+ tail2->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)tail2;
+
+ tail1->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)tail1;
+ //
+
+ addattr32(&req.n, 4096, TCA_U32_CLASSID, 1);
+ addattr_l(&req.n, MAX_MSG, TCA_U32_SEL, &sel, sizeof(sel));
+ tail->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)tail;
+
+ if (rtnl_talk(rth, &req.n, 0, 0, NULL, NULL, NULL, 0) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int remove_root(struct rtnl_handle *rth, int ifindex)
+{
+ struct qdisc_opt opt = {
+ .handle = 0x00010000,
+ .parent = TC_H_ROOT,
+ };
+
+ return tc_qdisc_modify(rth, ifindex, RTM_DELQDISC, 0, &opt);
+}
+
+static int remove_ingress(struct rtnl_handle *rth, int ifindex)
+{
+ struct qdisc_opt opt = {
+ .handle = 0xffff0000,
+ .parent = TC_H_INGRESS,
+ };
+
+ return tc_qdisc_modify(rth, ifindex, RTM_DELQDISC, 0, &opt);
+}
+
+static int remove_htb_ifb(struct rtnl_handle *rth, int ifindex, int priority)
+{
+ struct qdisc_opt opt = {
+ .handle = 0x00010001 + priority,
+ .parent = 0x00010000,
+ };
+
+ return tc_qdisc_modify(rth, conf_ifb_ifindex, RTM_DELTCLASS, 0, &opt);
+}
+
+int install_limiter(struct ppp_t *ppp, int down_speed, int down_burst, int up_speed, int up_burst)
+{
+ struct rtnl_handle rth;
+ int r;
+
+ if (rtnl_open(&rth, 0)) {
+ log_ppp_error("shaper: cannot open rtnetlink\n");
+ return -1;
+ }
+
+ down_speed = down_speed * 1000 / 8;
+ down_burst = down_burst ? down_burst : conf_down_burst_factor * down_speed;
+ up_speed = up_speed * 1000 / 8;
+ up_burst = up_burst ? up_burst : conf_up_burst_factor * up_speed;
+
+ if (conf_down_limiter == LIM_TBF)
+ r = install_tbf(&rth, ppp->ifindex, down_speed, down_burst);
+ else
+ r = install_htb(&rth, ppp->ifindex, down_speed, down_burst);
+
+ if (conf_up_limiter == LIM_POLICE)
+ r = install_police(&rth, ppp->ifindex, up_speed, up_burst);
+ else
+ r = install_htb_ifb(&rth, ppp->ifindex, ppp->unit_idx + 1, down_speed, down_burst);
+
+ rtnl_close(&rth);
+
+ return r;
+}
+
+int remove_limiter(struct ppp_t *ppp)
+{
+ struct rtnl_handle rth;
+
+ if (rtnl_open(&rth, 0)) {
+ log_ppp_error("shaper: cannot open rtnetlink\n");
+ return -1;
+ }
+
+ remove_root(&rth, ppp->ifindex);
+ remove_ingress(&rth, ppp->ifindex);
+
+ if (conf_up_limiter == LIM_HTB)
+ remove_htb_ifb(&rth, ppp->ifindex, ppp->unit_idx + 1);
+
+ return 0;
+}
+
+int init_ifb(const char *name)
+{
+ struct rtnl_handle rth;
+ struct rtattr *tail;
+ struct ifreq ifr;
+ int r;
+
+ struct qdisc_opt opt = {
+ .kind = "htb",
+ .handle = 0x00010000,
+ .parent = TC_H_ROOT,
+ .quantum = conf_r2q,
+ .qdisc = qdisc_htb_root,
+ };
+
+ memset(&ifr, 0, sizeof(ifr));
+ strcpy(ifr.ifr_name, name);
+
+ if (ioctl(sock_fd, SIOCGIFINDEX, &ifr)) {
+ log_emerg("shaper: ioctl(SIOCGIFINDEX): %s\n", strerror(errno));
+ return -1;
+ }
+
+ conf_ifb_ifindex = ifr.ifr_ifindex;
+
+ ifr.ifr_flags |= IFF_UP;
+
+ if (ioctl(sock_fd, SIOCSIFFLAGS, &ifr)) {
+ log_emerg("shaper: ioctl(SIOCSIFINDEX): %s\n", strerror(errno));
+ return -1;
+ }
+
+ if (rtnl_open(&rth, 0)) {
+ log_emerg("shaper: cannot open rtnetlink\n");
+ return -1;
+ }
+
+ tc_qdisc_modify(&rth, conf_ifb_ifindex, RTM_DELQDISC, 0, &opt);
+
+ r = tc_qdisc_modify(&rth, conf_ifb_ifindex, RTM_NEWQDISC, NLM_F_CREATE | NLM_F_REPLACE, &opt);
+ if (r)
+ goto out;
+
+ memset(&req, 0, sizeof(req));
+
+ req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg));
+ req.n.nlmsg_flags = NLM_F_REQUEST|NLM_F_EXCL|NLM_F_CREATE;
+ req.n.nlmsg_type = RTM_NEWTFILTER;
+ req.t.tcm_family = AF_UNSPEC;
+ req.t.tcm_ifindex = conf_ifb_ifindex;
+ req.t.tcm_handle = 1;
+ req.t.tcm_parent = 0x00010000;
+ req.t.tcm_info = TC_H_MAKE(1 << 16, ntohs(ETH_P_IP));
+
+ addattr_l(&req.n, sizeof(req), TCA_KIND, "flow", 5);
+
+ tail = NLMSG_TAIL(&req.n);
+ addattr_l(&req.n, 4096, TCA_OPTIONS, NULL, 0);
+ addattr32(&req.n, 4096, TCA_FLOW_KEYS, 1 << FLOW_KEY_PRIORITY);
+ addattr32(&req.n, 4096, TCA_FLOW_MODE, FLOW_MODE_MAP);
+ tail->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)tail;
+
+ r = rtnl_talk(&rth, &req.n, 0, 0, NULL, NULL, NULL, 0);
+
+out:
+ rtnl_close(&rth);
+
+ return r;
+}