diff options
-rw-r--r-- | accel-pppd/CMakeLists.txt | 1 | ||||
-rw-r--r-- | accel-pppd/accel-ppp.conf | 19 | ||||
-rw-r--r-- | accel-pppd/accel-ppp.conf.5 | 38 | ||||
-rw-r--r-- | accel-pppd/extra/chap-secrets.c | 2 | ||||
-rw-r--r-- | accel-pppd/shaper/CMakeLists.txt | 2 | ||||
-rw-r--r-- | accel-pppd/shaper/libnetlink.c | 692 | ||||
-rw-r--r-- | accel-pppd/shaper/libnetlink.h | 115 | ||||
-rw-r--r-- | accel-pppd/shaper/limiter.c | 536 | ||||
-rw-r--r-- | accel-pppd/shaper/shaper.c | 929 | ||||
-rw-r--r-- | accel-pppd/shaper/shaper.h | 23 | ||||
-rw-r--r-- | accel-pppd/shaper/tc_core.c | 211 | ||||
-rw-r--r-- | accel-pppd/shaper/tc_core.h | 33 |
12 files changed, 2598 insertions, 3 deletions
diff --git a/accel-pppd/CMakeLists.txt b/accel-pppd/CMakeLists.txt index bee823d8..4e6c6d6e 100644 --- a/accel-pppd/CMakeLists.txt +++ b/accel-pppd/CMakeLists.txt @@ -39,6 +39,7 @@ ADD_SUBDIRECTORY(auth) ADD_SUBDIRECTORY(logs) ADD_SUBDIRECTORY(extra) ADD_SUBDIRECTORY(ipv6) +ADD_SUBDIRECTORY(shaper) ADD_EXECUTABLE(accel-pppd ppp/ppp.c diff --git a/accel-pppd/accel-ppp.conf b/accel-pppd/accel-ppp.conf index b65f41be..c59b2056 100644 --- a/accel-pppd/accel-ppp.conf +++ b/accel-pppd/accel-ppp.conf @@ -19,7 +19,8 @@ ippool sigchld pppd_compat -#shaper_tbf +#shaper +#shaper_tbf (obsolete) #chap-secrets #net-snmp #logwtmp @@ -142,7 +143,21 @@ verbose=1 gw-ip-address=192.168.100.1 #chap-secrets=/etc/ppp/chap-secrets -[tbf] +[shaper] +#attr=Filter-Id +#down-burst-factor=0.1 +#up-burst-factor=1.0 +#latency=50 +#mpu=0 +#r2q=10 +#quantum=1500 +#ifb=ifb0 +up-limiter=police +down-limiter=tbf +verbose=1 + +#tbf is obsolete, use shaper module +#[tbf] #attr=Filter-Id #down-burst-factor=0.1 #up-burst-factor=1.0 diff --git a/accel-pppd/accel-ppp.conf.5 b/accel-pppd/accel-ppp.conf.5 index f294486a..c622ce3e 100644 --- a/accel-pppd/accel-ppp.conf.5 +++ b/accel-pppd/accel-ppp.conf.5 @@ -523,3 +523,41 @@ Specifies acceptable rate of connections, for example limit=1/s or limit=10/m. .TP .BI "timeout=" n Specifies timeout in seconds after which module doesn't check rate until burst number of connections will be arrived. +.TP +.SH [shaper] +.br +This module controls shaper. +.TP +.BI "attr=" name +Specifies which radius attribute contains rate information. Default - Filter-ID. +.TP +.BI "attr-up=" name +.TP +.BI "attr-down=" name +Specifies which radius attributes contains rate information for upstream and downstream respectively. +.TP +.BI "burst-factor=" n +Burst will be calculated as rate multyply burst-factor. +.TP +.BI "up-burst-factor=" n +.TP +.BI "down-burst-factor=" n +Specifies burst factor for upstream and downstream respectively. +.TP +.BI "latency=" n +Specifies latency (in miliseconds) parameter of tbf qdisc. +.TP +.BI "mpu=" n +Specifies mpu parameter of tbf qdisc and policer. +.TP +.BI "r2q=" n +Specifies r2q parameter of root htb qdisc. +.TP +.BI "quantum=" n +Specifies quantum parameter of htb classes. +.TP +.BI "up-limiter=" police|htb +Specifes upstream rate limiting method. +.TP +.BI "down-limiter=" tbf|htb +Specifies downstream rate limiting method. diff --git a/accel-pppd/extra/chap-secrets.c b/accel-pppd/extra/chap-secrets.c index e8f1b589..db1bbb2f 100644 --- a/accel-pppd/extra/chap-secrets.c +++ b/accel-pppd/extra/chap-secrets.c @@ -280,4 +280,4 @@ static void init(void) triton_event_register_handler(EV_CONFIG_RELOAD, (triton_event_func)load_config); } -DEFINE_INIT(200, init); +DEFINE_INIT(51, init); diff --git a/accel-pppd/shaper/CMakeLists.txt b/accel-pppd/shaper/CMakeLists.txt new file mode 100644 index 00000000..989aceb7 --- /dev/null +++ b/accel-pppd/shaper/CMakeLists.txt @@ -0,0 +1,2 @@ +ADD_LIBRARY(shaper SHARED shaper.c limiter.c tc_core.c libnetlink.c) + diff --git a/accel-pppd/shaper/libnetlink.c b/accel-pppd/shaper/libnetlink.c new file mode 100644 index 00000000..74cd5cb5 --- /dev/null +++ b/accel-pppd/shaper/libnetlink.c @@ -0,0 +1,692 @@ +/* + * libnetlink.c RTnetlink service routines. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <syslog.h> +#include <fcntl.h> +#include <net/if_arp.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <string.h> +#include <errno.h> +#include <time.h> +#include <sys/uio.h> + +#include "libnetlink.h" +#include "log.h" + +int rcvbuf = 1024 * 1024; + +void rtnl_close(struct rtnl_handle *rth) +{ + if (rth->fd >= 0) { + close(rth->fd); + rth->fd = -1; + } +} + +int rtnl_open_byproto(struct rtnl_handle *rth, unsigned subscriptions, + int protocol) +{ + socklen_t addr_len; + int sndbuf = 32768; + + memset(rth, 0, sizeof(*rth)); + + rth->fd = socket(AF_NETLINK, SOCK_RAW, protocol); + if (rth->fd < 0) { + log_error("libnetlink: ""Cannot open netlink socket: %s\n", strerror(errno)); + return -1; + } + + if (setsockopt(rth->fd,SOL_SOCKET,SO_SNDBUF,&sndbuf,sizeof(sndbuf)) < 0) { + log_error("libnetlink: ""SO_SNDBUF: %s\n", strerror(errno)); + return -1; + } + + if (setsockopt(rth->fd,SOL_SOCKET,SO_RCVBUF,&rcvbuf,sizeof(rcvbuf)) < 0) { + log_error("libnetlink: ""SO_RCVBUF: %s\n", strerror(errno)); + return -1; + } + + memset(&rth->local, 0, sizeof(rth->local)); + rth->local.nl_family = AF_NETLINK; + rth->local.nl_groups = subscriptions; + + if (bind(rth->fd, (struct sockaddr*)&rth->local, sizeof(rth->local)) < 0) { + log_error("libnetlink: ""Cannot bind netlink socket: %s\n", strerror(errno)); + return -1; + } + addr_len = sizeof(rth->local); + if (getsockname(rth->fd, (struct sockaddr*)&rth->local, &addr_len) < 0) { + log_error("libnetlink: ""Cannot getsockname: %s\n", strerror(errno)); + return -1; + } + if (addr_len != sizeof(rth->local)) { + log_error("libnetlink: ""Wrong address length %d\n", addr_len); + return -1; + } + if (rth->local.nl_family != AF_NETLINK) { + log_error("libnetlink: ""Wrong address family %d\n", rth->local.nl_family); + return -1; + } + rth->seq = time(NULL); + return 0; +} + +int rtnl_open(struct rtnl_handle *rth, unsigned subscriptions) +{ + return rtnl_open_byproto(rth, subscriptions, NETLINK_ROUTE); +} + +int rtnl_wilddump_request(struct rtnl_handle *rth, int family, int type) +{ + struct { + struct nlmsghdr nlh; + struct rtgenmsg g; + } req; + + memset(&req, 0, sizeof(req)); + req.nlh.nlmsg_len = sizeof(req); + req.nlh.nlmsg_type = type; + req.nlh.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST; + req.nlh.nlmsg_pid = 0; + req.nlh.nlmsg_seq = rth->dump = ++rth->seq; + req.g.rtgen_family = family; + + return send(rth->fd, (void*)&req, sizeof(req), 0); +} + +int rtnl_send(struct rtnl_handle *rth, const char *buf, int len) +{ + return send(rth->fd, buf, len, 0); +} + +int rtnl_send_check(struct rtnl_handle *rth, const char *buf, int len) +{ + struct nlmsghdr *h; + int status; + char resp[1024]; + + status = send(rth->fd, buf, len, 0); + if (status < 0) + return status; + + /* Check for immediate errors */ + status = recv(rth->fd, resp, sizeof(resp), MSG_DONTWAIT|MSG_PEEK); + if (status < 0) { + if (errno == EAGAIN) + return 0; + return -1; + } + + for (h = (struct nlmsghdr *)resp; NLMSG_OK(h, status); + h = NLMSG_NEXT(h, status)) { + if (h->nlmsg_type == NLMSG_ERROR) { + struct nlmsgerr *err = (struct nlmsgerr*)NLMSG_DATA(h); + if (h->nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr))) + log_error("libnetlink: ""ERROR truncated\n"); + else + errno = -err->error; + return -1; + } + } + + return 0; +} + +int rtnl_dump_request(struct rtnl_handle *rth, int type, void *req, int len) +{ + struct nlmsghdr nlh; + struct sockaddr_nl nladdr; + struct iovec iov[2] = { + { .iov_base = &nlh, .iov_len = sizeof(nlh) }, + { .iov_base = req, .iov_len = len } + }; + struct msghdr msg = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = iov, + .msg_iovlen = 2, + }; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + + nlh.nlmsg_len = NLMSG_LENGTH(len); + nlh.nlmsg_type = type; + nlh.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST; + nlh.nlmsg_pid = 0; + nlh.nlmsg_seq = rth->dump = ++rth->seq; + + return sendmsg(rth->fd, &msg, 0); +} + +int rtnl_dump_filter_l(struct rtnl_handle *rth, + const struct rtnl_dump_filter_arg *arg) +{ + struct sockaddr_nl nladdr; + struct iovec iov; + struct msghdr msg = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + char buf[16384]; + + iov.iov_base = buf; + while (1) { + int status; + const struct rtnl_dump_filter_arg *a; + int found_done = 0; + int msglen = 0; + + iov.iov_len = sizeof(buf); + status = recvmsg(rth->fd, &msg, 0); + + if (status < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + log_error("libnetlink: ""netlink receive error %s (%d)\n", + strerror(errno), errno); + return -1; + } + + if (status == 0) { + log_error("libnetlink: ""EOF on netlink\n"); + return -1; + } + + for (a = arg; a->filter; a++) { + struct nlmsghdr *h = (struct nlmsghdr*)buf; + msglen = status; + + while (NLMSG_OK(h, msglen)) { + int err; + + if (nladdr.nl_pid != 0 || + h->nlmsg_pid != rth->local.nl_pid || + h->nlmsg_seq != rth->dump) { + if (a->junk) { + err = a->junk(&nladdr, h, + a->arg2); + if (err < 0) + return err; + } + goto skip_it; + } + + if (h->nlmsg_type == NLMSG_DONE) { + found_done = 1; + break; /* process next filter */ + } + if (h->nlmsg_type == NLMSG_ERROR) { + struct nlmsgerr *err = (struct nlmsgerr*)NLMSG_DATA(h); + if (h->nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr))) { + fprintf(stderr, + "ERROR truncated\n"); + } else { + errno = -err->error; + log_error("libnetlink: ""RTNETLINK answers: %s\n", strerror(errno)); + } + return -1; + } + err = a->filter(&nladdr, h, a->arg1); + if (err < 0) + return err; + +skip_it: + h = NLMSG_NEXT(h, msglen); + } + } + + if (found_done) + return 0; + + if (msg.msg_flags & MSG_TRUNC) { + log_error("libnetlink: ""Message truncated\n"); + continue; + } + if (msglen) { + log_error("libnetlink: ""!!!Remnant of size %d\n", msglen); + exit(1); + } + } +} + +int rtnl_dump_filter(struct rtnl_handle *rth, + rtnl_filter_t filter, + void *arg1, + rtnl_filter_t junk, + void *arg2) +{ + const struct rtnl_dump_filter_arg a[2] = { + { .filter = filter, .arg1 = arg1, .junk = junk, .arg2 = arg2 }, + { .filter = NULL, .arg1 = NULL, .junk = NULL, .arg2 = NULL } + }; + + return rtnl_dump_filter_l(rth, a); +} + +int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, pid_t peer, + unsigned groups, struct nlmsghdr *answer, + rtnl_filter_t junk, + void *jarg, int ignore_einval) +{ + int status; + unsigned seq; + struct nlmsghdr *h; + struct sockaddr_nl nladdr; + struct iovec iov = { + .iov_base = (void*) n, + .iov_len = n->nlmsg_len + }; + struct msghdr msg = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + char buf[16384]; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + nladdr.nl_pid = peer; + nladdr.nl_groups = groups; + + n->nlmsg_seq = seq = ++rtnl->seq; + + if (answer == NULL) + n->nlmsg_flags |= NLM_F_ACK; + + status = sendmsg(rtnl->fd, &msg, 0); + + if (status < 0) { + log_error("libnetlink: ""Cannot talk to rtnetlink: %s\n", strerror(errno)); + return -1; + } + + memset(buf,0,sizeof(buf)); + + iov.iov_base = buf; + + while (1) { + iov.iov_len = sizeof(buf); + status = recvmsg(rtnl->fd, &msg, 0); + + if (status < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + log_error("libnetlink: ""netlink receive error %s (%d)\n", + strerror(errno), errno); + return -1; + } + if (status == 0) { + log_error("libnetlink: ""EOF on netlink\n"); + return -1; + } + if (msg.msg_namelen != sizeof(nladdr)) { + log_error("libnetlink: ""sender address length == %d\n", msg.msg_namelen); + exit(1); + } + for (h = (struct nlmsghdr*)buf; status >= sizeof(*h); ) { + int err; + int len = h->nlmsg_len; + int l = len - sizeof(*h); + + if (l<0 || len>status) { + if (msg.msg_flags & MSG_TRUNC) { + log_error("libnetlink: ""Truncated message\n"); + return -1; + } + log_error("libnetlink: ""!!!malformed message: len=%d\n", len); + exit(1); + } + + if (nladdr.nl_pid != peer || + h->nlmsg_pid != rtnl->local.nl_pid || + h->nlmsg_seq != seq) { + if (junk) { + err = junk(&nladdr, h, jarg); + if (err < 0) + return err; + } + /* Don't forget to skip that message. */ + status -= NLMSG_ALIGN(len); + h = (struct nlmsghdr*)((char*)h + NLMSG_ALIGN(len)); + continue; + } + + if (h->nlmsg_type == NLMSG_ERROR) { + struct nlmsgerr *err = (struct nlmsgerr*)NLMSG_DATA(h); + if (l < sizeof(struct nlmsgerr)) { + log_error("libnetlink: ""ERROR truncated\n"); + } else { + errno = -err->error; + if (errno == 0 || (errno == EINVAL && ignore_einval)) { + if (answer) + memcpy(answer, h, h->nlmsg_len); + return 0; + } + log_error("libnetlink: ""RTNETLINK answers: %s\n", strerror(errno)); + } + return -1; + } + if (answer) { + memcpy(answer, h, h->nlmsg_len); + return 0; + } + + log_error("libnetlink: ""Unexpected reply!!!\n"); + + status -= NLMSG_ALIGN(len); + h = (struct nlmsghdr*)((char*)h + NLMSG_ALIGN(len)); + } + if (msg.msg_flags & MSG_TRUNC) { + log_error("libnetlink: ""Message truncated\n"); + continue; + } + if (status) { + log_error("libnetlink: ""!!!Remnant of size %d\n", status); + exit(1); + } + } +} + +int rtnl_listen(struct rtnl_handle *rtnl, + rtnl_filter_t handler, + void *jarg) +{ + int status; + struct nlmsghdr *h; + struct sockaddr_nl nladdr; + struct iovec iov; + struct msghdr msg = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + char buf[8192]; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + nladdr.nl_pid = 0; + nladdr.nl_groups = 0; + + iov.iov_base = buf; + while (1) { + iov.iov_len = sizeof(buf); + status = recvmsg(rtnl->fd, &msg, 0); + + if (status < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + log_error("libnetlink: ""netlink receive error %s (%d)\n", + strerror(errno), errno); + if (errno == ENOBUFS) + continue; + return -1; + } + if (status == 0) { + log_error("libnetlink: ""EOF on netlink\n"); + return -1; + } + if (msg.msg_namelen != sizeof(nladdr)) { + log_error("libnetlink: ""Sender address length == %d\n", msg.msg_namelen); + exit(1); + } + for (h = (struct nlmsghdr*)buf; status >= sizeof(*h); ) { + int err; + int len = h->nlmsg_len; + int l = len - sizeof(*h); + + if (l<0 || len>status) { + if (msg.msg_flags & MSG_TRUNC) { + log_error("libnetlink: ""Truncated message\n"); + return -1; + } + log_error("libnetlink: ""!!!malformed message: len=%d\n", len); + exit(1); + } + + err = handler(&nladdr, h, jarg); + if (err < 0) + return err; + + status -= NLMSG_ALIGN(len); + h = (struct nlmsghdr*)((char*)h + NLMSG_ALIGN(len)); + } + if (msg.msg_flags & MSG_TRUNC) { + log_error("libnetlink: ""Message truncated\n"); + continue; + } + if (status) { + log_error("libnetlink: ""!!!Remnant of size %d\n", status); + exit(1); + } + } +} + +int rtnl_from_file(FILE *rtnl, rtnl_filter_t handler, + void *jarg) +{ + int status; + struct sockaddr_nl nladdr; + char buf[8192]; + struct nlmsghdr *h = (void*)buf; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + nladdr.nl_pid = 0; + nladdr.nl_groups = 0; + + while (1) { + int err, len, type; + int l; + + status = fread(&buf, 1, sizeof(*h), rtnl); + + if (status < 0) { + if (errno == EINTR) + continue; + log_error("libnetlink: ""rtnl_from_file: fread"); + return -1; + } + if (status == 0) + return 0; + + len = h->nlmsg_len; + type= h->nlmsg_type; + l = len - sizeof(*h); + + if (l<0 || len>sizeof(buf)) { + log_error("libnetlink: ""!!!malformed message: len=%d @%lu\n", + len, ftell(rtnl)); + return -1; + } + + status = fread(NLMSG_DATA(h), 1, NLMSG_ALIGN(l), rtnl); + + if (status < 0) { + log_error("libnetlink: ""rtnl_from_file: fread"); + return -1; + } + if (status < l) { + log_error("libnetlink: ""rtnl-from_file: truncated message\n"); + return -1; + } + + err = handler(&nladdr, h, jarg); + if (err < 0) + return err; + } +} + +int addattr32(struct nlmsghdr *n, int maxlen, int type, __u32 data) +{ + int len = RTA_LENGTH(4); + struct rtattr *rta; + if (NLMSG_ALIGN(n->nlmsg_len) + len > maxlen) { + fprintf(stderr,"addattr32: Error! max allowed bound %d exceeded\n",maxlen); + return -1; + } + rta = NLMSG_TAIL(n); + rta->rta_type = type; + rta->rta_len = len; + memcpy(RTA_DATA(rta), &data, 4); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len; + return 0; +} + +int addattr_l(struct nlmsghdr *n, int maxlen, int type, const void *data, + int alen) +{ + int len = RTA_LENGTH(alen); + struct rtattr *rta; + + if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen) { + log_error("libnetlink: ""addattr_l ERROR: message exceeded bound of %d\n",maxlen); + return -1; + } + rta = NLMSG_TAIL(n); + rta->rta_type = type; + rta->rta_len = len; + memcpy(RTA_DATA(rta), data, alen); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len); + return 0; +} + +int addraw_l(struct nlmsghdr *n, int maxlen, const void *data, int len) +{ + if (NLMSG_ALIGN(n->nlmsg_len) + NLMSG_ALIGN(len) > maxlen) { + log_error("libnetlink: ""addraw_l ERROR: message exceeded bound of %d\n",maxlen); + return -1; + } + + memcpy(NLMSG_TAIL(n), data, len); + memset((void *) NLMSG_TAIL(n) + len, 0, NLMSG_ALIGN(len) - len); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + NLMSG_ALIGN(len); + return 0; +} + +struct rtattr *addattr_nest(struct nlmsghdr *n, int maxlen, int type) +{ + struct rtattr *nest = NLMSG_TAIL(n); + + addattr_l(n, maxlen, type, NULL, 0); + return nest; +} + +int addattr_nest_end(struct nlmsghdr *n, struct rtattr *nest) +{ + nest->rta_len = (void *)NLMSG_TAIL(n) - (void *)nest; + return n->nlmsg_len; +} + +struct rtattr *addattr_nest_compat(struct nlmsghdr *n, int maxlen, int type, + const void *data, int len) +{ + struct rtattr *start = NLMSG_TAIL(n); + + addattr_l(n, maxlen, type, data, len); + addattr_nest(n, maxlen, type); + return start; +} + +int addattr_nest_compat_end(struct nlmsghdr *n, struct rtattr *start) +{ + struct rtattr *nest = (void *)start + NLMSG_ALIGN(start->rta_len); + + start->rta_len = (void *)NLMSG_TAIL(n) - (void *)start; + addattr_nest_end(n, nest); + return n->nlmsg_len; +} + +int rta_addattr32(struct rtattr *rta, int maxlen, int type, __u32 data) +{ + int len = RTA_LENGTH(4); + struct rtattr *subrta; + + if (RTA_ALIGN(rta->rta_len) + len > maxlen) { + fprintf(stderr,"rta_addattr32: Error! max allowed bound %d exceeded\n",maxlen); + return -1; + } + subrta = (struct rtattr*)(((char*)rta) + RTA_ALIGN(rta->rta_len)); + subrta->rta_type = type; + subrta->rta_len = len; + memcpy(RTA_DATA(subrta), &data, 4); + rta->rta_len = NLMSG_ALIGN(rta->rta_len) + len; + return 0; +} + +int rta_addattr_l(struct rtattr *rta, int maxlen, int type, + const void *data, int alen) +{ + struct rtattr *subrta; + int len = RTA_LENGTH(alen); + + if (RTA_ALIGN(rta->rta_len) + RTA_ALIGN(len) > maxlen) { + fprintf(stderr,"rta_addattr_l: Error! max allowed bound %d exceeded\n",maxlen); + return -1; + } + subrta = (struct rtattr*)(((char*)rta) + RTA_ALIGN(rta->rta_len)); + subrta->rta_type = type; + subrta->rta_len = len; + memcpy(RTA_DATA(subrta), data, alen); + rta->rta_len = NLMSG_ALIGN(rta->rta_len) + RTA_ALIGN(len); + return 0; +} + +int parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len) +{ + memset(tb, 0, sizeof(struct rtattr *) * (max + 1)); + while (RTA_OK(rta, len)) { + if ((rta->rta_type <= max) && (!tb[rta->rta_type])) + tb[rta->rta_type] = rta; + rta = RTA_NEXT(rta,len); + } + if (len) + log_error("libnetlink: ""!!!Deficit %d, rta_len=%d\n", len, rta->rta_len); + return 0; +} + +int parse_rtattr_byindex(struct rtattr *tb[], int max, struct rtattr *rta, int len) +{ + int i = 0; + + memset(tb, 0, sizeof(struct rtattr *) * max); + while (RTA_OK(rta, len)) { + if (rta->rta_type <= max && i < max) + tb[i++] = rta; + rta = RTA_NEXT(rta,len); + } + if (len) + log_error("libnetlink: ""!!!Deficit %d, rta_len=%d\n", len, rta->rta_len); + return i; +} + +int __parse_rtattr_nested_compat(struct rtattr *tb[], int max, struct rtattr *rta, + int len) +{ + if (RTA_PAYLOAD(rta) < len) + return -1; + if (RTA_PAYLOAD(rta) >= RTA_ALIGN(len) + sizeof(struct rtattr)) { + rta = RTA_DATA(rta) + RTA_ALIGN(len); + return parse_rtattr_nested(tb, max, rta); + } + memset(tb, 0, sizeof(struct rtattr *) * (max + 1)); + return 0; +} diff --git a/accel-pppd/shaper/libnetlink.h b/accel-pppd/shaper/libnetlink.h new file mode 100644 index 00000000..63ba5ad1 --- /dev/null +++ b/accel-pppd/shaper/libnetlink.h @@ -0,0 +1,115 @@ +#ifndef __LIBNETLINK_H__ +#define __LIBNETLINK_H__ 1 + +#include <asm/types.h> +#include <linux/netlink.h> +#include <linux/rtnetlink.h> +#include <linux/if_link.h> +#include <linux/if_addr.h> +#include <linux/neighbour.h> + +struct rtnl_handle +{ + int fd; + struct sockaddr_nl local; + struct sockaddr_nl peer; + __u32 seq; + __u32 dump; +}; + +extern int rcvbuf; + +extern int rtnl_open(struct rtnl_handle *rth, unsigned subscriptions); +extern int rtnl_open_byproto(struct rtnl_handle *rth, unsigned subscriptions, int protocol); +extern void rtnl_close(struct rtnl_handle *rth); +extern int rtnl_wilddump_request(struct rtnl_handle *rth, int fam, int type); +extern int rtnl_dump_request(struct rtnl_handle *rth, int type, void *req, int len); + +typedef int (*rtnl_filter_t)(const struct sockaddr_nl *, + struct nlmsghdr *n, void *); + +struct rtnl_dump_filter_arg +{ + rtnl_filter_t filter; + void *arg1; + rtnl_filter_t junk; + void *arg2; +}; + +extern int rtnl_dump_filter_l(struct rtnl_handle *rth, + const struct rtnl_dump_filter_arg *arg); +extern int rtnl_dump_filter(struct rtnl_handle *rth, rtnl_filter_t filter, + void *arg1, + rtnl_filter_t junk, + void *arg2); + +extern int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, pid_t peer, + unsigned groups, struct nlmsghdr *answer, + rtnl_filter_t junk, + void *jarg, int ignore_einval); +extern int rtnl_send(struct rtnl_handle *rth, const char *buf, int); +extern int rtnl_send_check(struct rtnl_handle *rth, const char *buf, int); + +extern int addattr32(struct nlmsghdr *n, int maxlen, int type, __u32 data); +extern int addattr_l(struct nlmsghdr *n, int maxlen, int type, const void *data, int alen); +extern int addraw_l(struct nlmsghdr *n, int maxlen, const void *data, int len); +extern struct rtattr *addattr_nest(struct nlmsghdr *n, int maxlen, int type); +extern int addattr_nest_end(struct nlmsghdr *n, struct rtattr *nest); +extern struct rtattr *addattr_nest_compat(struct nlmsghdr *n, int maxlen, int type, const void *data, int len); +extern int addattr_nest_compat_end(struct nlmsghdr *n, struct rtattr *nest); +extern int rta_addattr32(struct rtattr *rta, int maxlen, int type, __u32 data); +extern int rta_addattr_l(struct rtattr *rta, int maxlen, int type, const void *data, int alen); + +extern int parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len); +extern int parse_rtattr_byindex(struct rtattr *tb[], int max, struct rtattr *rta, int len); +extern int __parse_rtattr_nested_compat(struct rtattr *tb[], int max, struct rtattr *rta, int len); + +#define parse_rtattr_nested(tb, max, rta) \ + (parse_rtattr((tb), (max), RTA_DATA(rta), RTA_PAYLOAD(rta))) + +#define parse_rtattr_nested_compat(tb, max, rta, data, len) \ +({ data = RTA_PAYLOAD(rta) >= len ? RTA_DATA(rta) : NULL; \ + __parse_rtattr_nested_compat(tb, max, rta, len); }) + +extern int rtnl_listen(struct rtnl_handle *, rtnl_filter_t handler, + void *jarg); +extern int rtnl_from_file(FILE *, rtnl_filter_t handler, + void *jarg); + +#define NLMSG_TAIL(nmsg) \ + ((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len))) + +#ifndef IFA_RTA +#define IFA_RTA(r) \ + ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ifaddrmsg)))) +#endif +#ifndef IFA_PAYLOAD +#define IFA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ifaddrmsg)) +#endif + +#ifndef IFLA_RTA +#define IFLA_RTA(r) \ + ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ifinfomsg)))) +#endif +#ifndef IFLA_PAYLOAD +#define IFLA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ifinfomsg)) +#endif + +#ifndef NDA_RTA +#define NDA_RTA(r) \ + ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ndmsg)))) +#endif +#ifndef NDA_PAYLOAD +#define NDA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ndmsg)) +#endif + +#ifndef NDTA_RTA +#define NDTA_RTA(r) \ + ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ndtmsg)))) +#endif +#ifndef NDTA_PAYLOAD +#define NDTA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ndtmsg)) +#endif + +#endif /* __LIBNETLINK_H__ */ + 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; +} diff --git a/accel-pppd/shaper/shaper.c b/accel-pppd/shaper/shaper.c new file mode 100644 index 00000000..1363b98b --- /dev/null +++ b/accel-pppd/shaper/shaper.c @@ -0,0 +1,929 @@ +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <stdio.h> +#include <netinet/in.h> +#include <linux/if.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <pthread.h> + +#include "triton.h" +#include "events.h" +#include "log.h" +#include "ppp.h" +#include "cli.h" + +#ifdef RADIUS +#include "radius.h" +#endif + +#include "memdebug.h" + +#include "shaper.h" +#include "tc_core.h" + +#define ATTR_UP 1 +#define ATTR_DOWN 2 + +static int conf_verbose = 0; +#ifdef RADIUS +static int conf_attr_down = 11; //Filter-Id +static int conf_attr_up = 11; //Filter-Id +static int conf_vendor = 0; +#endif +double conf_down_burst_factor = 0.1; +double conf_up_burst_factor = 1; +double conf_latency = 0.05; +int conf_mpu = 0; +int conf_quantum = 1500; +int conf_r2q = 10; +int conf_ifb_ifindex; + +int conf_up_limiter = LIM_POLICE; +int conf_down_limiter = LIM_TBF; + +static int temp_down_speed; +static int temp_up_speed; + +static pthread_rwlock_t shaper_lock = PTHREAD_RWLOCK_INITIALIZER; +static LIST_HEAD(shaper_list); + +struct time_range_pd_t; +struct shaper_pd_t +{ + struct list_head entry; + struct ppp_t *ppp; + struct ppp_pd_t pd; + int temp_down_speed; + int temp_up_speed; + int down_speed; + int up_speed; + struct list_head tr_list; + struct time_range_pd_t *cur_tr; +}; + +struct time_range_pd_t +{ + struct list_head entry; + int id; + int down_speed; + int down_burst; + int up_speed; + int up_burst; +}; + +struct time_range_t +{ + struct list_head entry; + int id; + struct triton_timer_t begin; + struct triton_timer_t end; +}; + +static void *pd_key; + +static LIST_HEAD(time_range_list); +static int time_range_id = 0; + +static void shaper_ctx_close(struct triton_context_t *); +static struct triton_context_t shaper_ctx = { + .close = shaper_ctx_close, + .before_switch = log_switch, +}; + +static struct shaper_pd_t *find_pd(struct ppp_t *ppp, int create) +{ + struct ppp_pd_t *pd; + struct shaper_pd_t *spd; + + list_for_each_entry(pd, &ppp->pd_list, entry) { + if (pd->key == &pd_key) { + spd = container_of(pd, typeof(*spd), pd); + return spd; + } + } + + if (create) { + spd = _malloc(sizeof(*spd)); + if (!spd) { + log_emerg("shaper: out of memory\n"); + return NULL; + } + + memset(spd, 0, sizeof(*spd)); + spd->ppp = ppp; + list_add_tail(&spd->pd.entry, &ppp->pd_list); + spd->pd.key = &pd_key; + INIT_LIST_HEAD(&spd->tr_list); + + pthread_rwlock_wrlock(&shaper_lock); + list_add_tail(&spd->entry, &shaper_list); + pthread_rwlock_unlock(&shaper_lock); + return spd; + } + + return NULL; +} + +static void parse_string(const char *str, int dir, int *speed, int *burst, int *tr_id) +{ + char *endptr; + long int val; + unsigned int n1, n2, n3; + + if (strstr(str, "lcp:interface-config#1=rate-limit output access-group") == str) { + if (dir == ATTR_DOWN) { + val = sscanf(str, "lcp:interface-config#1=rate-limit output access-group %i %u %u %u conform-action transmit exceed-action drop", tr_id, &n1, &n2, &n3); + if (val == 4) { + *speed = n1/1000; + *burst = n2; + } + } + return; + } else if (strstr(str, "lcp:interface-config#1=rate-limit input access-group") == str) { + if (dir == ATTR_UP) { + val = sscanf(str, "lcp:interface-config#1=rate-limit input access-group %i %u %u %u conform-action transmit exceed-action drop", tr_id, &n1, &n2, &n3); + if (val == 4) { + *speed = n1/1000; + *burst = n2; + } + } + return; + } else if (strstr(str, "lcp:interface-config#1=rate-limit output") == str) { + if (dir == ATTR_DOWN) { + val = sscanf(str, "lcp:interface-config#1=rate-limit output %u %u %u conform-action transmit exceed-action drop", &n1, &n2, &n3); + if (val == 3) { + *speed = n1/1000; + *burst = n2; + } + } + return; + } else if (strstr(str, "lcp:interface-config#1=rate-limit input") == str) { + if (dir == ATTR_UP) { + val = sscanf(str, "lcp:interface-config#1=rate-limit input %u %u %u conform-action transmit exceed-action drop", &n1, &n2, &n3); + if (val == 3) { + *speed = n1/1000; + *burst = n2; + } + } + return; + } + + val = strtol(str, &endptr, 10); + if (*endptr == 0) { + *speed = val; + return; + } + if (*endptr == ',') { + *tr_id = val; + val = strtol(endptr + 1, &endptr, 10); + } + if (*endptr == 0) { + *speed = val; + return; + } else { + if (*endptr == '/' || *endptr == '\\' || *endptr == ':') { + if (dir == ATTR_DOWN) + *speed = val; + else + *speed = strtol(endptr + 1, &endptr, 10); + } + } +} + +static struct time_range_pd_t *get_tr_pd(struct shaper_pd_t *pd, int id) +{ + struct time_range_pd_t *tr_pd; + + list_for_each_entry(tr_pd, &pd->tr_list, entry) { + if (tr_pd->id == id) + return tr_pd; + } + + tr_pd = _malloc(sizeof(*tr_pd)); + memset(tr_pd, 0, sizeof(*tr_pd)); + tr_pd->id = id; + + if (id == time_range_id || id == 0) + pd->cur_tr = tr_pd; + + list_add_tail(&tr_pd->entry, &pd->tr_list); + + return tr_pd; +} + +static void clear_tr_pd(struct shaper_pd_t *pd) +{ + struct time_range_pd_t *tr_pd; + + while (!list_empty(&pd->tr_list)) { + tr_pd = list_entry(pd->tr_list.next, typeof(*tr_pd), entry); + list_del(&tr_pd->entry); + _free(tr_pd); + } +} + +#ifdef RADIUS +static void parse_attr(struct rad_attr_t *attr, int dir, int *speed, int *burst, int *tr_id) +{ + if (attr->attr->type == ATTR_TYPE_STRING) + parse_string(attr->val.string, dir, speed, burst, tr_id); + else if (attr->attr->type == ATTR_TYPE_INTEGER) + *speed = attr->val.integer; +} + +static void check_radius_attrs(struct shaper_pd_t *pd, struct rad_packet_t *pack) +{ + struct rad_attr_t *attr; + int down_speed, down_burst; + int up_speed, up_burst; + int tr_id; + struct time_range_pd_t *tr_pd; + + list_for_each_entry(attr, &pack->attrs, entry) { + if (attr->vendor && attr->vendor->id != conf_vendor) + continue; + if (!attr->vendor && conf_vendor) + continue; + if (attr->attr->id != conf_attr_down && attr->attr->id != conf_attr_up) + continue; + tr_id = 0; + down_speed = 0; + down_burst = 0; + up_speed = 0; + up_burst = 0; + if (attr->attr->id == conf_attr_down) + parse_attr(attr, ATTR_DOWN, &down_speed, &down_burst, &tr_id); + if (attr->attr->id == conf_attr_up) + parse_attr(attr, ATTR_UP, &up_speed, &up_burst, &tr_id); + tr_pd = get_tr_pd(pd, tr_id); + if (down_speed) + tr_pd->down_speed = down_speed; + if (down_burst) + tr_pd->down_burst = down_burst; + if (up_speed) + tr_pd->up_speed = up_speed; + if (up_burst) + tr_pd->up_burst = up_burst; + } +} + +static void ev_radius_access_accept(struct ev_radius_t *ev) +{ + int down_speed, down_burst; + int up_speed, up_burst; + struct shaper_pd_t *pd = find_pd(ev->ppp, 1); + + if (!pd) + return; + + check_radius_attrs(pd, ev->reply); + + if (temp_down_speed || temp_up_speed) { + pd->temp_down_speed = temp_down_speed; + pd->temp_up_speed = temp_up_speed; + pd->down_speed = temp_down_speed; + pd->up_speed = temp_up_speed; + down_speed = temp_down_speed; + up_speed = temp_up_speed; + down_burst = 0; + up_burst = 0; + } else { + if (!pd->cur_tr) + return; + pd->down_speed = pd->cur_tr->down_speed; + pd->up_speed = pd->cur_tr->up_speed; + down_speed = pd->cur_tr->down_speed; + up_speed = pd->cur_tr->up_speed; + down_burst = pd->cur_tr->down_burst; + up_burst = pd->cur_tr->up_burst; + } + + if (down_speed > 0 && up_speed > 0) { + if (!install_limiter(ev->ppp, down_speed, down_burst, up_speed, up_burst)) { + if (conf_verbose) + log_ppp_info2("shaper: installed shaper %i/%i (Kbit)\n", down_speed, up_speed); + } + } +} + +static void ev_radius_coa(struct ev_radius_t *ev) +{ + struct shaper_pd_t *pd = find_pd(ev->ppp, 0); + + if (!pd) { + ev->res = -1; + return; + } + + clear_tr_pd(pd); + check_radius_attrs(pd, ev->request); + + if (pd->temp_down_speed || pd->temp_up_speed) + return; + + if (!pd->cur_tr) { + if (pd->down_speed || pd->up_speed) { + pd->down_speed = 0; + pd->up_speed = 0; + if (conf_verbose) + log_ppp_info2("shaper: removed shaper\n"); + remove_limiter(ev->ppp); + } + return; + } + + if (pd->down_speed != pd->cur_tr->down_speed || pd->up_speed != pd->cur_tr->up_speed) { + pd->down_speed = pd->cur_tr->down_speed; + pd->up_speed = pd->cur_tr->up_speed; + + if (remove_limiter(ev->ppp)) { + ev->res = -1; + return; + } + + if (pd->down_speed > 0 || pd->up_speed > 0) { + if (install_limiter(ev->ppp, pd->cur_tr->down_speed, pd->cur_tr->down_burst, pd->cur_tr->up_speed, pd->cur_tr->up_burst)) { + ev->res= -1; + return; + } else { + if (conf_verbose) + log_ppp_info2("shaper: changed shaper %i/%i (Kbit)\n", pd->down_speed, pd->up_speed); + } + } else { + if (conf_verbose) + log_ppp_info2("shaper: removed shaper\n"); + } + } +} +#endif + +static void ev_shaper(struct ev_shaper_t *ev) +{ + struct shaper_pd_t *pd = find_pd(ev->ppp, 1); + int down_speed = 0, down_burst = 0; + int up_speed = 0, up_burst = 0; + int tr_id = 0; + struct time_range_pd_t *tr_pd; + + if (!pd) + return; + + parse_string(ev->val, ATTR_DOWN, &down_speed, &down_burst, &tr_id); + parse_string(ev->val, ATTR_UP, &up_speed, &up_burst, &tr_id); + + tr_pd = get_tr_pd(pd, tr_id); + tr_pd->down_speed = down_speed; + tr_pd->down_burst = down_burst; + tr_pd->up_speed = up_speed; + tr_pd->up_burst = up_burst; + + if (temp_down_speed || temp_up_speed) { + pd->temp_down_speed = temp_down_speed; + pd->temp_up_speed = temp_up_speed; + pd->down_speed = temp_down_speed; + pd->up_speed = temp_up_speed; + down_speed = temp_down_speed; + up_speed = temp_up_speed; + down_burst = 0; + up_burst = 0; + } else { + if (!pd->cur_tr) + return; + pd->down_speed = down_speed; + pd->up_speed = up_speed; + } + + if (pd->down_speed > 0 && pd->up_speed > 0) { + if (!install_limiter(ev->ppp, down_speed, down_burst, up_speed, up_burst)) { + if (conf_verbose) + log_ppp_info2("shaper: installed shaper %i/%i (Kbit)\n", down_speed, up_speed); + } + } +} + +static void ev_ppp_pre_up(struct ppp_t *ppp) +{ + struct shaper_pd_t *pd = find_pd(ppp, 1); + if (!pd) + return; + + if (temp_down_speed || temp_up_speed) { + pd->temp_down_speed = temp_down_speed; + pd->temp_up_speed = temp_up_speed; + pd->down_speed = temp_down_speed; + pd->up_speed = temp_up_speed; + if (!install_limiter(ppp, temp_down_speed, 0, temp_up_speed, 0)) { + if (conf_verbose) + log_ppp_info2("shaper: installed shaper %i/%i (Kbit)\n", temp_down_speed, temp_up_speed); + } + } +} + +static void ev_ppp_finishing(struct ppp_t *ppp) +{ + struct shaper_pd_t *pd = find_pd(ppp, 0); + + if (pd) { + clear_tr_pd(pd); + pthread_rwlock_wrlock(&shaper_lock); + list_del(&pd->entry); + pthread_rwlock_unlock(&shaper_lock); + list_del(&pd->pd.entry); + + if (pd->down_speed || pd->up_speed) + remove_limiter(ppp); + + _free(pd); + } +} + +static void shaper_change_help(char * const *f, int f_cnt, void *cli) +{ + cli_send(cli, "shaper change <interface> <value> [temp] - change shaper on specified interface, if temp is set then previous settings may be restored later by 'shaper restore'\r\n"); + cli_send(cli, "shaper change all <value> [temp] - change shaper on all interfaces, if temp is set also new interfaces will have specified shaper value\r\n"); +} + +static void shaper_change(struct shaper_pd_t *pd) +{ + if (pd->down_speed || pd->up_speed) + remove_limiter(pd->ppp); + + if (pd->temp_down_speed || pd->temp_up_speed) { + pd->down_speed = pd->temp_down_speed; + pd->up_speed = pd->temp_up_speed; + install_limiter(pd->ppp, pd->temp_down_speed, 0, pd->temp_up_speed, 0); + } else if (pd->cur_tr->down_speed || pd->cur_tr->up_speed) { + pd->down_speed = pd->cur_tr->down_speed; + pd->up_speed = pd->cur_tr->up_speed; + install_limiter(pd->ppp, pd->cur_tr->down_speed, pd->cur_tr->down_burst, pd->cur_tr->up_speed, pd->cur_tr->up_burst); + } else { + pd->down_speed = 0; + pd->up_speed = 0; + } +} + +static int shaper_change_exec(const char *cmd, char * const *f, int f_cnt, void *cli) +{ + struct shaper_pd_t *pd; + int down_speed = 0, up_speed = 0, down_burst = 0, up_burst = 0; + int all = 0, temp = 0, found = 0; + int tr_id; + + if (f_cnt < 4) + return CLI_CMD_SYNTAX; + + parse_string(f[3], ATTR_DOWN, &down_speed, &down_burst, &tr_id); + parse_string(f[3], ATTR_UP, &up_speed, &up_burst, &tr_id); + + //if (down_speed == 0 || up_speed == 0) + // return CLI_CMD_INVAL; + + if (!strcmp(f[2], "all")) + all = 1; + + if (f_cnt == 5) { + if (strcmp(f[4], "temp")) + return CLI_CMD_SYNTAX; + else + temp = 1; + } + + if (all && temp) { + temp_down_speed = down_speed; + temp_up_speed = up_speed; + } + + pthread_rwlock_rdlock(&shaper_lock); + list_for_each_entry(pd, &shaper_list, entry) { + if (all || !strcmp(f[2], pd->ppp->ifname)) { + if (temp) { + pd->temp_down_speed = down_speed; + pd->temp_up_speed = up_speed; + } else { + pd->temp_down_speed = 0; + pd->temp_up_speed = 0; + if (!pd->cur_tr) + pd->cur_tr = get_tr_pd(pd, 0); + pd->cur_tr->down_speed = down_speed; + pd->cur_tr->down_burst = down_burst; + pd->cur_tr->up_speed = up_speed; + pd->cur_tr->up_burst = up_burst; + } + triton_context_call(pd->ppp->ctrl->ctx, (triton_event_func)shaper_change, pd); + if (!all) { + found = 1; + break; + } + } + } + pthread_rwlock_unlock(&shaper_lock); + + if (!all && !found) + cli_send(cli, "not found\r\n"); + + return CLI_CMD_OK; +} + +static void shaper_restore_help(char * const *f, int f_cnt, void *cli) +{ + cli_send(cli, "shaper restore <interface> - restores shaper settings on specified interface made by 'shaper change' command with 'temp' flag\r\n"); + cli_send(cli, "shaper restore all - restores shaper settings on all interfaces made by 'shaper change' command with 'temp' flag\r\n"); +} + +static void shaper_restore(struct shaper_pd_t *pd) +{ + remove_limiter(pd->ppp); + + if (pd->cur_tr) { + pd->down_speed = pd->cur_tr->down_speed; + pd->up_speed = pd->cur_tr->up_speed; + install_limiter(pd->ppp, pd->cur_tr->down_speed, pd->cur_tr->down_burst, pd->cur_tr->up_speed, pd->cur_tr->up_burst); + } else { + pd->down_speed = 0; + pd->up_speed = 0; + } +} + +static int shaper_restore_exec(const char *cmd, char * const *f, int f_cnt, void *cli) +{ + struct shaper_pd_t *pd; + int all, found = 0;; + + if (f_cnt != 3) + return CLI_CMD_SYNTAX; + + if (strcmp(f[2], "all")) + all = 0; + else + all = 1; + + pthread_rwlock_rdlock(&shaper_lock); + if (all) { + temp_down_speed = 0; + temp_up_speed = 0; + } + list_for_each_entry(pd, &shaper_list, entry) { + if (!pd->temp_down_speed) + continue; + if (all || !strcmp(f[2], pd->ppp->ifname)) { + pd->temp_down_speed = 0; + pd->temp_up_speed = 0; + triton_context_call(pd->ppp->ctrl->ctx, (triton_event_func)shaper_restore, pd); + if (!all) { + found = 1; + break; + } + } + } + pthread_rwlock_unlock(&shaper_lock); + + if (!all && !found) + cli_send(cli, "not found\r\n"); + + return CLI_CMD_OK; +} + +static void print_rate(const struct ppp_t *ppp, char *buf) +{ + struct shaper_pd_t *pd = find_pd((struct ppp_t *)ppp, 0); + + if (pd && (pd->down_speed || pd->up_speed)) + sprintf(buf, "%i/%i", pd->down_speed, pd->up_speed); + else + *buf = 0; +} + +static void shaper_ctx_close(struct triton_context_t *ctx) +{ + struct time_range_t *r; + + while (!list_empty(&time_range_list)) { + r = list_entry(time_range_list.next, typeof(*r), entry); + list_del(&r->entry); + if (r->begin.tpd) + triton_timer_del(&r->begin); + if (r->end.tpd) + triton_timer_del(&r->end); + _free(r); + } + + triton_context_unregister(ctx); +} + +static void update_shaper_tr(struct shaper_pd_t *pd) +{ + struct time_range_pd_t *tr; + + if (pd->ppp->terminating) + return; + + list_for_each_entry(tr, &pd->tr_list, entry) { + if (tr->id != time_range_id) + continue; + pd->cur_tr = tr; + break; + } + + if (pd->temp_down_speed || pd->temp_up_speed) + return; + + if (pd->down_speed || pd->up_speed) { + if (pd->cur_tr && pd->down_speed == pd->cur_tr->down_speed && pd->up_speed == pd->cur_tr->up_speed) + return; + remove_limiter(pd->ppp); + } + + if (pd->cur_tr && (pd->cur_tr->down_speed || pd->cur_tr->up_speed)) { + pd->down_speed = pd->cur_tr->down_speed; + pd->up_speed = pd->cur_tr->up_speed; + if (!install_limiter(pd->ppp, pd->cur_tr->down_speed, pd->cur_tr->down_burst, pd->cur_tr->up_speed, pd->cur_tr->up_burst)) { + if (conf_verbose) + log_ppp_info2("shaper: changed shaper %i/%i (Kbit)\n", pd->cur_tr->down_speed, pd->cur_tr->up_speed); + } + } else + if (conf_verbose) + log_ppp_info2("shaper: removed shaper\n"); +} + +static void time_range_begin_timer(struct triton_timer_t *t) +{ + struct time_range_t *tr = container_of(t, typeof(*tr), begin); + struct shaper_pd_t *pd; + + time_range_id = tr->id; + + log_debug("shaper: time_range_begin_timer: id=%i\n", time_range_id); + + pthread_rwlock_rdlock(&shaper_lock); + list_for_each_entry(pd, &shaper_list, entry) + triton_context_call(pd->ppp->ctrl->ctx, (triton_event_func)update_shaper_tr, pd); + pthread_rwlock_unlock(&shaper_lock); +} + +static void time_range_end_timer(struct triton_timer_t *t) +{ + struct shaper_pd_t *pd; + + time_range_id = 0; + + log_debug("shaper: time_range_end_timer\n"); + + pthread_rwlock_rdlock(&shaper_lock); + list_for_each_entry(pd, &shaper_list, entry) + triton_context_call(pd->ppp->ctrl->ctx, (triton_event_func)update_shaper_tr, pd); + pthread_rwlock_unlock(&shaper_lock); +} + +static struct time_range_t *parse_range(const char *val) +{ + char *endptr; + int id; + time_t t; + struct tm begin_tm, end_tm; + struct time_range_t *r; + + id = strtol(val, &endptr, 10); + if (*endptr != ',') + return NULL; + if (id <= 0) + return NULL; + + time(&t); + localtime_r(&t, &begin_tm); + begin_tm.tm_sec = 1; + end_tm = begin_tm; + end_tm.tm_sec = 0; + + endptr = strptime(endptr + 1, "%H:%M", &begin_tm); + if (*endptr != '-') + return NULL; + + endptr = strptime(endptr + 1, "%H:%M", &end_tm); + if (*endptr) + return NULL; + + r = _malloc(sizeof(*r)); + memset(r, 0, sizeof(*r)); + + r->id = id; + r->begin.expire_tv.tv_sec = mktime(&begin_tm); + r->begin.period = 24 * 60 * 60 * 1000; + r->begin.expire = time_range_begin_timer; + r->end.expire_tv.tv_sec = mktime(&end_tm); + r->end.period = 24 * 60 * 60 * 1000; + r->end.expire = time_range_end_timer; + + return r; +} + +static void load_time_ranges(void) +{ + struct conf_sect_t *s = conf_get_section("shaper"); + struct conf_option_t *opt; + struct time_range_t *r; + time_t ts; + + if (!s) + return; + + time(&ts); + + while (!list_empty(&time_range_list)) { + r = list_entry(time_range_list.next, typeof(*r), entry); + list_del(&r->entry); + if (r->begin.tpd) + triton_timer_del(&r->begin); + if (r->end.tpd) + triton_timer_del(&r->end); + _free(r); + } + + list_for_each_entry(opt, &s->items, entry) { + if (strcmp(opt->name, "time-range")) + continue; + r = parse_range(opt->val); + if (r) { + list_add_tail(&r->entry, &time_range_list); + if (r->begin.expire_tv.tv_sec > r->end.expire_tv.tv_sec) { + if (ts >= r->begin.expire_tv.tv_sec && ts <= r->end.expire_tv.tv_sec + 24*60*60) + time_range_begin_timer(&r->begin); + } else { + if (ts >= r->begin.expire_tv.tv_sec && ts <= r->end.expire_tv.tv_sec) + time_range_begin_timer(&r->begin); + } + if (r->begin.expire_tv.tv_sec < ts) + r->begin.expire_tv.tv_sec += 24 * 60 * 60; + if (r->end.expire_tv.tv_sec < ts) + r->end.expire_tv.tv_sec += 24 * 60 * 60; + triton_timer_add(&shaper_ctx, &r->begin, 1); + triton_timer_add(&shaper_ctx, &r->end, 1); + } else + log_emerg("shaper: failed to parse time-range '%s'\n", opt->val); + } +} + +#ifdef RADIUS +static int parse_attr_opt(const char *opt) +{ + struct rad_dict_attr_t *attr; + struct rad_dict_vendor_t *vendor; + + if (conf_vendor) + vendor = rad_dict_find_vendor_id(conf_vendor); + else + vendor = NULL; + + if (conf_vendor) { + if (vendor) + attr = rad_dict_find_vendor_attr(vendor, opt); + else + attr = NULL; + }else + attr = rad_dict_find_attr(opt); + + if (attr) + return attr->id; + + return atoi(opt); +} + +static int parse_vendor_opt(const char *opt) +{ + struct rad_dict_vendor_t *vendor; + + vendor = rad_dict_find_vendor_name(opt); + if (vendor) + return vendor->id; + + return atoi(opt); +} +#endif + +static void load_config(void) +{ + const char *opt; + +#ifdef RADIUS + if (triton_module_loaded("radius")) { + opt = conf_get_opt("shaper", "vendor"); + if (opt) + conf_vendor = parse_vendor_opt(opt); + + opt = conf_get_opt("shaper", "attr"); + if (opt) { + conf_attr_down = parse_attr_opt(opt); + conf_attr_up = parse_attr_opt(opt); + } + + opt = conf_get_opt("shaper", "attr-down"); + if (opt) + conf_attr_down = parse_attr_opt(opt); + + opt = conf_get_opt("shaper", "attr-up"); + if (opt) + conf_attr_up = parse_attr_opt(opt); + + if (conf_attr_up <= 0 || conf_attr_down <= 0) { + log_emerg("shaper: incorrect attribute(s), tbf disabled...\n"); + return; + } + } +#endif + + opt = conf_get_opt("shaper", "burst-factor"); + if (opt) { + conf_down_burst_factor = strtod(opt, NULL); + conf_up_burst_factor = conf_down_burst_factor * 10; + } + + opt = conf_get_opt("shaper", "down-burst-factor"); + if (opt) + conf_down_burst_factor = strtod(opt, NULL); + + opt = conf_get_opt("shaper", "up-burst-factor"); + if (opt) + conf_up_burst_factor = strtod(opt, NULL); + + opt = conf_get_opt("shaper", "latency"); + if (opt && atoi(opt) > 0) + conf_latency = (double)atoi(opt) / 1000; + + opt = conf_get_opt("shaper", "mpu"); + if (opt && atoi(opt) >= 0) + conf_mpu = atoi(opt); + + opt = conf_get_opt("shaper", "r2q"); + if (opt && atoi(opt) >= 0) + conf_r2q = atoi(opt); + + opt = conf_get_opt("shaper", "quantum"); + if (opt && atoi(opt) >= 0) + conf_quantum = atoi(opt); + + opt = conf_get_opt("shaper", "up-limiter"); + if (opt) { + if (!strcmp(opt, "police")) + conf_up_limiter = LIM_POLICE; + else if (!strcmp(opt, "htb")) + conf_up_limiter = LIM_HTB; + else + log_error("shaper: unknown upstream limiter '%s'\n", opt); + } + + opt = conf_get_opt("shaper", "down-limiter"); + if (opt) { + if (!strcmp(opt, "tbf")) + conf_down_limiter = LIM_TBF; + else if (!strcmp(opt, "htb")) + conf_down_limiter = LIM_HTB; + else + log_error("shaper: unknown downstream limiter '%s'\n", opt); + } + + if (conf_up_limiter == LIM_HTB && !conf_ifb_ifindex) { + log_warn("shaper: requested 'htb' upstream limiter, but no 'ifb' specified, falling back to police...\n"); + conf_up_limiter = LIM_POLICE; + } + + opt = conf_get_opt("shaper", "verbose"); + if (opt && atoi(opt) > 0) + conf_verbose = 1; + + triton_context_call(&shaper_ctx, (triton_event_func)load_time_ranges, NULL); +} + +static void init(void) +{ + const char *opt; + + tc_core_init(); + + opt = conf_get_opt("shaper", "ifb"); + if (opt && init_ifb(opt)) + _exit(0); + + triton_context_register(&shaper_ctx, NULL); + triton_context_wakeup(&shaper_ctx); + + load_config(); + +#ifdef RADIUS + if (triton_module_loaded("radius")) { + triton_event_register_handler(EV_RADIUS_ACCESS_ACCEPT, (triton_event_func)ev_radius_access_accept); + triton_event_register_handler(EV_RADIUS_COA, (triton_event_func)ev_radius_coa); + } +#endif + triton_event_register_handler(EV_PPP_PRE_UP, (triton_event_func)ev_ppp_pre_up); + triton_event_register_handler(EV_PPP_FINISHING, (triton_event_func)ev_ppp_finishing); + //triton_event_register_handler(EV_CTRL_FINISHED, (triton_event_func)ev_ctrl_finished); + triton_event_register_handler(EV_SHAPER, (triton_event_func)ev_shaper); + triton_event_register_handler(EV_CONFIG_RELOAD, (triton_event_func)load_config); + + cli_register_simple_cmd2(shaper_change_exec, shaper_change_help, 2, "shaper", "change"); + cli_register_simple_cmd2(shaper_restore_exec, shaper_restore_help, 2, "shaper", "restore"); + cli_show_ses_register("rate-limit", "rate limit down-stream/up-stream (Kbit)", print_rate); +} + +DEFINE_INIT(100, init); diff --git a/accel-pppd/shaper/shaper.h b/accel-pppd/shaper/shaper.h new file mode 100644 index 00000000..2b0514ba --- /dev/null +++ b/accel-pppd/shaper/shaper.h @@ -0,0 +1,23 @@ +#ifndef __SHAPER_H +#define __SHAPER_H + +#define LIM_POLICE 0 +#define LIM_TBF 1 +#define LIM_HTB 2 + +extern int conf_up_limiter; +extern int conf_down_limiter; + +extern double conf_down_burst_factor; +extern double conf_up_burst_factor; +extern double conf_latency; +extern int conf_mpu; +extern int conf_quantum; +extern int conf_r2q; +extern int conf_ifb_ifindex; + +int install_limiter(struct ppp_t *ppp, int down_speed, int down_burst, int up_speed, int up_burst); +int remove_limiter(struct ppp_t *ppp); +int init_ifb(const char *); + +#endif diff --git a/accel-pppd/shaper/tc_core.c b/accel-pppd/shaper/tc_core.c new file mode 100644 index 00000000..9a0ff391 --- /dev/null +++ b/accel-pppd/shaper/tc_core.c @@ -0,0 +1,211 @@ +/* + * tc_core.c TC core library. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <syslog.h> +#include <fcntl.h> +#include <math.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <string.h> + +#include "tc_core.h" +#include <linux/atm.h> + +static double tick_in_usec = 1; +static double clock_factor = 1; + +int tc_core_time2big(unsigned time) +{ + __u64 t = time; + + t *= tick_in_usec; + return (t >> 32) != 0; +} + + +unsigned tc_core_time2tick(unsigned time) +{ + return time*tick_in_usec; +} + +unsigned tc_core_tick2time(unsigned tick) +{ + return tick/tick_in_usec; +} + +unsigned tc_core_time2ktime(unsigned time) +{ + return time * clock_factor; +} + +unsigned tc_core_ktime2time(unsigned ktime) +{ + return ktime / clock_factor; +} + +unsigned tc_calc_xmittime(unsigned rate, unsigned size) +{ + return tc_core_time2tick(TIME_UNITS_PER_SEC*((double)size/rate)); +} + +unsigned tc_calc_xmitsize(unsigned rate, unsigned ticks) +{ + return ((double)rate*tc_core_tick2time(ticks))/TIME_UNITS_PER_SEC; +} + +/* + * The align to ATM cells is used for determining the (ATM) SAR + * alignment overhead at the ATM layer. (SAR = Segmentation And + * Reassembly). This is for example needed when scheduling packet on + * an ADSL connection. Note that the extra ATM-AAL overhead is _not_ + * included in this calculation. This overhead is added in the kernel + * before doing the rate table lookup, as this gives better precision + * (as the table will always be aligned for 48 bytes). + * --Hawk, d.7/11-2004. <hawk@diku.dk> + */ +unsigned tc_align_to_atm(unsigned size) +{ + int linksize, cells; + cells = size / ATM_CELL_PAYLOAD; + if ((size % ATM_CELL_PAYLOAD) > 0) + cells++; + + linksize = cells * ATM_CELL_SIZE; /* Use full cell size to add ATM tax */ + return linksize; +} + +unsigned tc_adjust_size(unsigned sz, unsigned mpu, enum link_layer linklayer) +{ + if (sz < mpu) + sz = mpu; + + switch (linklayer) { + case LINKLAYER_ATM: + return tc_align_to_atm(sz); + case LINKLAYER_ETHERNET: + default: + // No size adjustments on Ethernet + return sz; + } +} + +/* + rtab[pkt_len>>cell_log] = pkt_xmit_time + */ + +int tc_calc_rtable(struct tc_ratespec *r, __u32 *rtab, + int cell_log, unsigned mtu, + enum link_layer linklayer) +{ + int i; + unsigned sz; + unsigned bps = r->rate; + unsigned mpu = r->mpu; + + if (mtu == 0) + mtu = 2047; + + if (cell_log < 0) { + cell_log = 0; + while ((mtu >> cell_log) > 255) + cell_log++; + } + + for (i=0; i<256; i++) { + sz = tc_adjust_size((i + 1) << cell_log, mpu, linklayer); + rtab[i] = tc_calc_xmittime(bps, sz); + } + + r->cell_align=-1; // Due to the sz calc + r->cell_log=cell_log; + return cell_log; +} + +/* + stab[pkt_len>>cell_log] = pkt_xmit_size>>size_log + */ + +int tc_calc_size_table(struct tc_sizespec *s, __u16 **stab) +{ + int i; + enum link_layer linklayer = s->linklayer; + unsigned int sz; + + if (linklayer <= LINKLAYER_ETHERNET && s->mpu == 0) { + /* don't need data table in this case (only overhead set) */ + s->mtu = 0; + s->tsize = 0; + s->cell_log = 0; + s->cell_align = 0; + *stab = NULL; + return 0; + } + + if (s->mtu == 0) + s->mtu = 2047; + if (s->tsize == 0) + s->tsize = 512; + + s->cell_log = 0; + while ((s->mtu >> s->cell_log) > s->tsize - 1) + s->cell_log++; + + *stab = malloc(s->tsize * sizeof(__u16)); + if (!*stab) + return -1; + +again: + for (i = s->tsize - 1; i >= 0; i--) { + sz = tc_adjust_size((i + 1) << s->cell_log, s->mpu, linklayer); + if ((sz >> s->size_log) > UINT16_MAX) { + s->size_log++; + goto again; + } + (*stab)[i] = sz >> s->size_log; + } + + s->cell_align = -1; // Due to the sz calc + return 0; +} + +int tc_core_init() +{ + FILE *fp; + __u32 clock_res; + __u32 t2us; + __u32 us2t; + + fp = fopen("/proc/net/psched", "r"); + if (fp == NULL) + return -1; + + if (fscanf(fp, "%08x%08x%08x", &t2us, &us2t, &clock_res) != 3) { + fclose(fp); + return -1; + } + fclose(fp); + + /* compatibility hack: for old iproute binaries (ignoring + * the kernel clock resolution) the kernel advertises a + * tick multiplier of 1000 in case of nano-second resolution, + * which really is 1. */ + if (clock_res == 1000000000) + t2us = us2t; + + clock_factor = (double)clock_res / TIME_UNITS_PER_SEC; + tick_in_usec = (double)t2us / us2t * clock_factor; + return 0; +} diff --git a/accel-pppd/shaper/tc_core.h b/accel-pppd/shaper/tc_core.h new file mode 100644 index 00000000..cb85cc44 --- /dev/null +++ b/accel-pppd/shaper/tc_core.h @@ -0,0 +1,33 @@ +#ifndef _TC_CORE_H_ +#define _TC_CORE_H_ 1 + +#include <asm/types.h> +#include <linux/pkt_sched.h> + +#define TIME_UNITS_PER_SEC 1000000 + +enum link_layer { + LINKLAYER_UNSPEC, + LINKLAYER_ETHERNET, + LINKLAYER_ATM, +}; + + +int tc_core_time2big(unsigned time); +unsigned tc_core_time2tick(unsigned time); +unsigned tc_core_tick2time(unsigned tick); +unsigned tc_core_time2ktime(unsigned time); +unsigned tc_core_ktime2time(unsigned ktime); +unsigned tc_calc_xmittime(unsigned rate, unsigned size); +unsigned tc_calc_xmitsize(unsigned rate, unsigned ticks); +int tc_calc_rtable(struct tc_ratespec *r, __u32 *rtab, + int cell_log, unsigned mtu, enum link_layer link_layer); +int tc_calc_size_table(struct tc_sizespec *s, __u16 **stab); + +int tc_setup_estimator(unsigned A, unsigned time_const, struct tc_estimator *est); + +int tc_core_init(void); + +extern struct rtnl_handle g_rth; + +#endif |