#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "log.h" #include "ppp.h" #include "shaper.h" #include "tc_core.h" #include "libnetlink.h" static int parse_size(const char *str, int *r) { double sz; char *endptr; sz = strtod(str, &endptr); if (endptr == str) return -1; if (*endptr == 0) { *r = sz; return 0; } if (strcmp(endptr, "kb") == 0 || strcmp(endptr, "k") == 0) *r = sz * 1024; else if (strcmp(endptr, "mb") == 0 || strcmp(endptr, "m") == 0) *r = sz * 1024 * 1024; else if (strcmp(endptr, "gb") == 0 || strcmp(endptr, "g") == 0) *r = sz * 1024 * 1024 * 1024; else if (strcmp(endptr, "kbit") == 0) *r = sz * 1024 / 8; else if (strcmp(endptr, "mbit") == 0) *r = sz * 1024 * 1024 / 8; else if (strcmp(endptr, "gbit") == 0) *r = sz * 1024 * 1024 * 1024 / 8; else if (strcmp(endptr, "b") == 0) *r = sz; else return -1; return 0; } #ifdef TCA_FQ_CODEL_MAX static int parse_time(const char *str, int *r) { double t; char *endptr; t = strtod(str, &endptr); if (endptr == str) return -1; if (*endptr == 0) { *r = t; return 0; } if (strcmp(endptr, "s") == 0 || strcmp(endptr, "sec") == 0) *r = t * TIME_UNITS_PER_SEC; else if (strcmp(endptr, "ms") == 0 || strcmp(endptr, "msec") == 0) *r = t * TIME_UNITS_PER_SEC/1000; else if (strcmp(endptr, "us") == 0 || strcmp(endptr, "usec") == 0) *r = t * TIME_UNITS_PER_SEC/1000000; else return -1; return 0; } #endif static int parse_int(const char *str, int *r) { char *endptr; *r = strtol(str, &endptr, 10); return *endptr != 0; } static int parse_u32(const char *str, int *r) { char *endptr; *r = strtol(str, &endptr, 10); return *endptr != 0 || *r < 0; } static int parse_sfq(char *str) { char *key, *value, *ptr; if (!str) goto out; for (key = strtok_r(str, " ", &ptr); key; key = strtok_r(NULL, " ", &ptr)) { value = strtok_r(NULL, " ", &ptr); if (!value) return -1; if (strcmp(key, "quantum") == 0) { if (parse_size(value, &conf_lq_arg1)) return -1; } else if (strcmp(key, "perturb") == 0) { if (parse_int(value, &conf_lq_arg2)) return -1; } else if (strcmp(key, "limit") == 0) { if (parse_u32(value, &conf_lq_arg3)) return -1; } else return -1; } out: conf_leaf_qdisc = LEAF_QDISC_SFQ; return 0; } #ifdef TCA_FQ_CODEL_MAX static int parse_fq_codel(char *str) { char *key, *value, *ptr; conf_lq_arg6 = -1; if (!str) goto out; for (key = strtok_r(str, " ", &ptr); key; key = strtok_r(NULL, " ", &ptr)) { if (strcmp(key, "ecn") == 0) { conf_lq_arg6 = 1; continue; } else if (strcmp(key, "noecn") == 0) { conf_lq_arg6 = 0; continue; } value = strtok_r(NULL, " ", &ptr); if (!value) return -1; if (strcmp(key, "limit") == 0) { if (parse_u32(value, &conf_lq_arg1)) return -1; } else if (strcmp(key, "flows") == 0) { if (parse_u32(value, &conf_lq_arg2)) return -1; } else if (strcmp(key, "quantum") == 0) { if (parse_u32(value, &conf_lq_arg3)) return -1; } else if (strcmp(key, "target") == 0) { if (parse_time(value, &conf_lq_arg4)) return -1; } else if (strcmp(key, "interval") == 0) { if (parse_time(value, &conf_lq_arg5)) return -1; } else return -1; } out: conf_leaf_qdisc = LEAF_QDISC_FQ_CODEL; return 0; } #endif void leaf_qdisc_parse(const char *opt) { char *str, *qdisc, *params; str = strdup(opt); if (!str) { log_emerg("shaper: out of memory\n"); return; } qdisc = strtok_r(str, " ", ¶ms); if (qdisc) { if (strcmp(qdisc, "sfq") == 0) { if (parse_sfq(params)) goto out_err; #ifdef TCA_FQ_CODEL_MAX } else if (strcmp(qdisc, "fq_codel") == 0) { if (parse_fq_codel(params)) goto out_err; #endif } else log_emerg("shaper: unknown leaf-qdisc '%s'\n", qdisc); } free(str); return; out_err: log_emerg("shaper: failed to parse '%s'\n", opt); free(str); } static int qdisc_sfq(struct qdisc_opt *qopt, struct nlmsghdr *n) { struct tc_sfq_qopt opt = { .quantum = conf_lq_arg1, .perturb_period = conf_lq_arg2, .limit = conf_lq_arg3, }; addattr_l(n, 1024, TCA_OPTIONS, &opt, sizeof(opt)); return 0; } static int install_sfq(struct rtnl_handle *rth, int ifindex, int parent, int handle) { struct qdisc_opt opt = { .kind = "sfq", .handle = handle, .parent = parent, .qdisc = qdisc_sfq, }; return tc_qdisc_modify(rth, ifindex, RTM_NEWQDISC, NLM_F_EXCL|NLM_F_CREATE, &opt); } #ifdef TCA_FQ_CODEL_MAX static int qdisc_fq_codel(struct qdisc_opt *qopt, struct nlmsghdr *n) { struct rtattr *tail = NLMSG_TAIL(n); addattr_l(n, 1024, TCA_OPTIONS, NULL, 0); if (conf_lq_arg1) addattr_l(n, 1024, TCA_FQ_CODEL_LIMIT, &conf_lq_arg1, sizeof(conf_lq_arg1)); if (conf_lq_arg2) addattr_l(n, 1024, TCA_FQ_CODEL_FLOWS, &conf_lq_arg2, sizeof(conf_lq_arg2)); if (conf_lq_arg3) addattr_l(n, 1024, TCA_FQ_CODEL_QUANTUM, &conf_lq_arg3, sizeof(conf_lq_arg3)); if (conf_lq_arg4) addattr_l(n, 1024, TCA_FQ_CODEL_TARGET, &conf_lq_arg4, sizeof(conf_lq_arg4)); if (conf_lq_arg5) addattr_l(n, 1024, TCA_FQ_CODEL_INTERVAL, &conf_lq_arg5, sizeof(conf_lq_arg5)); if (conf_lq_arg6 != -1) addattr_l(n, 1024, TCA_FQ_CODEL_ECN, &conf_lq_arg6, sizeof(conf_lq_arg6)); tail->rta_len = (void *)NLMSG_TAIL(n) - (void *)tail; return 0; } static int install_fq_codel(struct rtnl_handle *rth, int ifindex, int parent, int handle) { struct qdisc_opt opt = { .kind = "fq_codel", .handle = handle, .parent = parent, .qdisc = qdisc_fq_codel, }; return tc_qdisc_modify(rth, ifindex, RTM_NEWQDISC, NLM_F_EXCL|NLM_F_CREATE, &opt); } #endif int install_leaf_qdisc(struct rtnl_handle *rth, int ifindex, int parent, int handle) { if (conf_leaf_qdisc == LEAF_QDISC_SFQ) return install_sfq(rth, ifindex, parent, handle); #ifdef TCA_FQ_CODEL_MAX else if (conf_leaf_qdisc == LEAF_QDISC_FQ_CODEL) return install_fq_codel(rth, ifindex, parent, handle); #endif return 0; }