diff --git a/sys/netpfil/ipfw/dn_aqm.h b/sys/netpfil/ipfw/dn_aqm.h index a8a362a4bde9..8bbe9fe69e86 100644 --- a/sys/netpfil/ipfw/dn_aqm.h +++ b/sys/netpfil/ipfw/dn_aqm.h @@ -1,165 +1,162 @@ /*- * Copyright (C) 2016 Centre for Advanced Internet Architectures, * Swinburne University of Technology, Melbourne, Australia. * Portions of this code were made possible in part by a gift from * The Comcast Innovation Fund. * Implemented by Rasool Al-Saadi * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * API for writing an Active Queue Management algorithm for Dummynet * * $FreeBSD$ */ #ifndef _IP_DN_AQM_H #define _IP_DN_AQM_H /* NOW is the current time in millisecond*/ #define NOW ((dn_cfg.curr_time * tick) / 1000) #define AQM_UNOW (dn_cfg.curr_time * tick) #define AQM_TIME_1US ((aqm_time_t)(1)) #define AQM_TIME_1MS ((aqm_time_t)(1000)) #define AQM_TIME_1S ((aqm_time_t)(AQM_TIME_1MS * 1000)) /* aqm time allows to store up to 4294 seconds */ typedef uint32_t aqm_time_t; typedef int32_t aqm_stime_t; #define DN_AQM_MTAG_TS 55345 /* Macro for variable bounding */ #define BOUND_VAR(x,l,h) ((x) > (h)? (h) : ((x) > (l)? (x) : (l))) -/* sysctl variable to count number of dropped packets */ -extern unsigned long io_pkt_drop; - /* * Structure for holding data and function pointers that together represent a * AQM algorithm. */ struct dn_aqm { #define DN_AQM_NAME_MAX 50 char name[DN_AQM_NAME_MAX]; /* name of AQM algorithm */ uint32_t type; /* AQM type number */ /* Methods implemented by AQM algorithm: * * enqueue enqueue packet 'm' on queue 'q'. * Return 0 on success, 1 on drop. * * dequeue dequeue a packet from queue 'q'. * Return a packet, NULL if no packet available. * * config configure AQM algorithm * If required, this function should allocate space to store * the configurations and set 'fs->aqmcfg' to point to this space. * 'dn_extra_parms' includes array of parameters send * from ipfw userland command. * Return 0 on success, non-zero otherwise. * * deconfig deconfigure AQM algorithm. * The allocated configuration memory space should be freed here. * Return 0 on success, non-zero otherwise. * * init initialise AQM status variables of queue 'q' * This function is used to allocate space and init AQM status for a * queue and q->aqm_status to point to this space. * Return 0 on success, non-zero otherwise. * * cleanup cleanup AQM status variables of queue 'q' * The allocated memory space for AQM status should be freed here. * Return 0 on success, non-zero otherwise. * * getconfig retrieve AQM configurations * This function is used to return AQM parameters to userland * command. The function should fill 'dn_extra_parms' struct with * the AQM configurations using 'par' array. * */ int (*enqueue)(struct dn_queue *, struct mbuf *); struct mbuf * (*dequeue)(struct dn_queue *); int (*config)(struct dn_fsk *, struct dn_extra_parms *ep, int); int (*deconfig)(struct dn_fsk *); int (*init)(struct dn_queue *); int (*cleanup)(struct dn_queue *); int (*getconfig)(struct dn_fsk *, struct dn_extra_parms *); int ref_count; /*Number of queues instances in the system */ int cfg_ref_count; /*Number of AQM instances in the system */ SLIST_ENTRY (dn_aqm) next; /* Next AQM in the list */ }; /* Helper function to update queue and scheduler statistics. * negative len + drop -> drop * negative len -> dequeue * positive len -> enqueue * positive len + drop -> drop during enqueue */ __inline static void update_stats(struct dn_queue *q, int len, int drop) { int inc = 0; struct dn_flow *sni; struct dn_flow *qni; sni = &q->_si->ni; qni = &q->ni; if (len < 0) inc = -1; else if(len > 0) inc = 1; if (drop) { qni->drops++; sni->drops++; - io_pkt_drop++; + dn_cfg.io_pkt_drop++; } else { /*update queue stats */ qni->length += inc; qni->len_bytes += len; /*update scheduler instance stats */ sni->length += inc; sni->len_bytes += len; } /* tot_pkts is updated in dn_enqueue function */ } /* kernel module related function */ int dn_aqm_modevent(module_t mod, int cmd, void *arg); #define DECLARE_DNAQM_MODULE(name, dnaqm) \ static moduledata_t name##_mod = { \ #name, dn_aqm_modevent, dnaqm \ }; \ DECLARE_MODULE(name, name##_mod, \ SI_SUB_PROTO_IFATTACHDOMAIN, SI_ORDER_ANY); \ MODULE_DEPEND(name, dummynet, 3, 3, 3) #endif diff --git a/sys/netpfil/ipfw/dn_sched_fq_codel.h b/sys/netpfil/ipfw/dn_sched_fq_codel.h index 725189483ba2..a8369ac83129 100644 --- a/sys/netpfil/ipfw/dn_sched_fq_codel.h +++ b/sys/netpfil/ipfw/dn_sched_fq_codel.h @@ -1,166 +1,166 @@ /*- * Copyright (C) 2016 Centre for Advanced Internet Architectures, * Swinburne University of Technology, Melbourne, Australia. * Portions of this code were made possible in part by a gift from * The Comcast Innovation Fund. * Implemented by Rasool Al-Saadi * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * FQ_Codel Structures and helper functions * * $FreeBSD$ */ #ifndef _IP_DN_SCHED_FQ_CODEL_H #define _IP_DN_SCHED_FQ_CODEL_H /* list of queues */ STAILQ_HEAD(fq_codel_list, fq_codel_flow) ; /* fq_codel parameters including codel */ struct dn_sch_fq_codel_parms { struct dn_aqm_codel_parms ccfg; /* CoDel Parameters */ /* FQ_CODEL Parameters */ uint32_t flows_cnt; /* number of flows */ uint32_t limit; /* hard limit of fq_codel queue size*/ uint32_t quantum; }; /* defaults */ /* flow (sub-queue) stats */ struct flow_stats { uint64_t tot_pkts; /* statistics counters */ uint64_t tot_bytes; uint32_t length; /* Queue length, in packets */ uint32_t len_bytes; /* Queue length, in bytes */ uint32_t drops; }; /* A flow of packets (sub-queue).*/ struct fq_codel_flow { struct mq mq; /* list of packets */ struct flow_stats stats; /* statistics */ int deficit; int active; /* 1: flow is active (in a list) */ struct codel_status cst; STAILQ_ENTRY(fq_codel_flow) flowchain; }; /* extra fq_codel scheduler configurations */ struct fq_codel_schk { struct dn_sch_fq_codel_parms cfg; }; /* fq_codel scheduler instance */ struct fq_codel_si { struct dn_sch_inst _si; /* standard scheduler instance */ struct dn_queue main_q; /* main queue is after si directly */ struct fq_codel_flow *flows; /* array of flows (queues) */ uint32_t perturbation; /* random value */ struct fq_codel_list newflows; /* list of new queues */ struct fq_codel_list oldflows; /* list of old queues */ }; /* Helper function to update queue&main-queue and scheduler statistics. * negative len + drop -> drop * negative len -> dequeue * positive len -> enqueue * positive len + drop -> drop during enqueue */ __inline static void fq_update_stats(struct fq_codel_flow *q, struct fq_codel_si *si, int len, int drop) { int inc = 0; if (len < 0) inc = -1; else if (len > 0) inc = 1; if (drop) { si->main_q.ni.drops ++; q->stats.drops ++; si->_si.ni.drops ++; - io_pkt_drop ++; + dn_cfg.io_pkt_drop ++; } if (!drop || (drop && len < 0)) { /* Update stats for the main queue */ si->main_q.ni.length += inc; si->main_q.ni.len_bytes += len; /*update sub-queue stats */ q->stats.length += inc; q->stats.len_bytes += len; /*update scheduler instance stats */ si->_si.ni.length += inc; si->_si.ni.len_bytes += len; } if (inc > 0) { si->main_q.ni.tot_bytes += len; si->main_q.ni.tot_pkts ++; q->stats.tot_bytes +=len; q->stats.tot_pkts++; si->_si.ni.tot_bytes +=len; si->_si.ni.tot_pkts ++; } } /* extract the head of fq_codel sub-queue */ __inline static struct mbuf * fq_codel_extract_head(struct fq_codel_flow *q, aqm_time_t *pkt_ts, struct fq_codel_si *si) { struct mbuf *m = q->mq.head; if (m == NULL) return m; q->mq.head = m->m_nextpkt; fq_update_stats(q, si, -m->m_pkthdr.len, 0); if (si->main_q.ni.length == 0) /* queue is now idle */ si->main_q.q_time = dn_cfg.curr_time; /* extract packet timestamp*/ struct m_tag *mtag; mtag = m_tag_locate(m, MTAG_ABI_COMPAT, DN_AQM_MTAG_TS, NULL); if (mtag == NULL){ D("timestamp tag is not found!"); *pkt_ts = 0; } else { *pkt_ts = *(aqm_time_t *)(mtag + 1); m_tag_delete(m,mtag); } return m; } #endif diff --git a/sys/netpfil/ipfw/dn_sched_fq_pie.c b/sys/netpfil/ipfw/dn_sched_fq_pie.c index 2f21ca77e33a..257dada44345 100644 --- a/sys/netpfil/ipfw/dn_sched_fq_pie.c +++ b/sys/netpfil/ipfw/dn_sched_fq_pie.c @@ -1,1230 +1,1230 @@ /* * FQ_PIE - The FlowQueue-PIE scheduler/AQM * * $FreeBSD$ * * Copyright (C) 2016 Centre for Advanced Internet Architectures, * Swinburne University of Technology, Melbourne, Australia. * Portions of this code were made possible in part by a gift from * The Comcast Innovation Fund. * Implemented by Rasool Al-Saadi * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* Important note: * As there is no an office document for FQ-PIE specification, we used * FQ-CoDel algorithm with some modifications to implement FQ-PIE. * This FQ-PIE implementation is a beta version and have not been tested * extensively. Our FQ-PIE uses stand-alone PIE AQM per sub-queue. By * default, timestamp is used to calculate queue delay instead of departure * rate estimation method. Although departure rate estimation is available * as testing option, the results could be incorrect. Moreover, turning PIE on * and off option is available but it does not work properly in this version. */ #ifdef _KERNEL #include #include #include #include #include #include #include #include /* IFNAMSIZ */ #include #include /* ipfw_rule_ref */ #include /* flow_id */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #else #include #endif #define DN_SCHED_FQ_PIE 7 /* list of queues */ STAILQ_HEAD(fq_pie_list, fq_pie_flow) ; /* FQ_PIE parameters including PIE */ struct dn_sch_fq_pie_parms { struct dn_aqm_pie_parms pcfg; /* PIE configuration Parameters */ /* FQ_PIE Parameters */ uint32_t flows_cnt; /* number of flows */ uint32_t limit; /* hard limit of FQ_PIE queue size*/ uint32_t quantum; }; /* flow (sub-queue) stats */ struct flow_stats { uint64_t tot_pkts; /* statistics counters */ uint64_t tot_bytes; uint32_t length; /* Queue length, in packets */ uint32_t len_bytes; /* Queue length, in bytes */ uint32_t drops; }; /* A flow of packets (sub-queue)*/ struct fq_pie_flow { struct mq mq; /* list of packets */ struct flow_stats stats; /* statistics */ int deficit; int active; /* 1: flow is active (in a list) */ struct pie_status pst; /* pie status variables */ struct fq_pie_si_extra *psi_extra; STAILQ_ENTRY(fq_pie_flow) flowchain; }; /* extra fq_pie scheduler configurations */ struct fq_pie_schk { struct dn_sch_fq_pie_parms cfg; }; /* fq_pie scheduler instance extra state vars. * The purpose of separation this structure is to preserve number of active * sub-queues and the flows array pointer even after the scheduler instance * is destroyed. * Preserving these varaiables allows freeing the allocated memory by * fqpie_callout_cleanup() independently from fq_pie_free_sched(). */ struct fq_pie_si_extra { uint32_t nr_active_q; /* number of active queues */ struct fq_pie_flow *flows; /* array of flows (queues) */ }; /* fq_pie scheduler instance */ struct fq_pie_si { struct dn_sch_inst _si; /* standard scheduler instance. SHOULD BE FIRST */ struct dn_queue main_q; /* main queue is after si directly */ uint32_t perturbation; /* random value */ struct fq_pie_list newflows; /* list of new queues */ struct fq_pie_list oldflows; /* list of old queues */ struct fq_pie_si_extra *si_extra; /* extra state vars*/ }; static struct dn_alg fq_pie_desc; /* Default FQ-PIE parameters including PIE */ /* PIE defaults * target=15ms, max_burst=150ms, max_ecnth=0.1, * alpha=0.125, beta=1.25, tupdate=15ms * FQ- * flows=1024, limit=10240, quantum =1514 */ struct dn_sch_fq_pie_parms fq_pie_sysctl = {{15000 * AQM_TIME_1US, 15000 * AQM_TIME_1US, 150000 * AQM_TIME_1US, PIE_SCALE * 0.1, PIE_SCALE * 0.125, PIE_SCALE * 1.25, PIE_CAPDROP_ENABLED | PIE_DERAND_ENABLED}, 1024, 10240, 1514}; static int fqpie_sysctl_alpha_beta_handler(SYSCTL_HANDLER_ARGS) { int error; long value; if (!strcmp(oidp->oid_name,"alpha")) value = fq_pie_sysctl.pcfg.alpha; else value = fq_pie_sysctl.pcfg.beta; value = value * 1000 / PIE_SCALE; error = sysctl_handle_long(oidp, &value, 0, req); if (error != 0 || req->newptr == NULL) return (error); if (value < 1 || value > 7 * PIE_SCALE) return (EINVAL); value = (value * PIE_SCALE) / 1000; if (!strcmp(oidp->oid_name,"alpha")) fq_pie_sysctl.pcfg.alpha = value; else fq_pie_sysctl.pcfg.beta = value; return (0); } static int fqpie_sysctl_target_tupdate_maxb_handler(SYSCTL_HANDLER_ARGS) { int error; long value; if (!strcmp(oidp->oid_name,"target")) value = fq_pie_sysctl.pcfg.qdelay_ref; else if (!strcmp(oidp->oid_name,"tupdate")) value = fq_pie_sysctl.pcfg.tupdate; else value = fq_pie_sysctl.pcfg.max_burst; value = value / AQM_TIME_1US; error = sysctl_handle_long(oidp, &value, 0, req); if (error != 0 || req->newptr == NULL) return (error); if (value < 1 || value > 10 * AQM_TIME_1S) return (EINVAL); value = value * AQM_TIME_1US; if (!strcmp(oidp->oid_name,"target")) fq_pie_sysctl.pcfg.qdelay_ref = value; else if (!strcmp(oidp->oid_name,"tupdate")) fq_pie_sysctl.pcfg.tupdate = value; else fq_pie_sysctl.pcfg.max_burst = value; return (0); } static int fqpie_sysctl_max_ecnth_handler(SYSCTL_HANDLER_ARGS) { int error; long value; value = fq_pie_sysctl.pcfg.max_ecnth; value = value * 1000 / PIE_SCALE; error = sysctl_handle_long(oidp, &value, 0, req); if (error != 0 || req->newptr == NULL) return (error); if (value < 1 || value > PIE_SCALE) return (EINVAL); value = (value * PIE_SCALE) / 1000; fq_pie_sysctl.pcfg.max_ecnth = value; return (0); } /* define FQ- PIE sysctl variables */ SYSBEGIN(f4) SYSCTL_DECL(_net_inet); SYSCTL_DECL(_net_inet_ip); SYSCTL_DECL(_net_inet_ip_dummynet); static SYSCTL_NODE(_net_inet_ip_dummynet, OID_AUTO, fqpie, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "FQ_PIE"); #ifdef SYSCTL_NODE SYSCTL_PROC(_net_inet_ip_dummynet_fqpie, OID_AUTO, target, CTLTYPE_LONG | CTLFLAG_RW | CTLFLAG_NEEDGIANT, NULL, 0, fqpie_sysctl_target_tupdate_maxb_handler, "L", "queue target in microsecond"); SYSCTL_PROC(_net_inet_ip_dummynet_fqpie, OID_AUTO, tupdate, CTLTYPE_LONG | CTLFLAG_RW | CTLFLAG_NEEDGIANT, NULL, 0, fqpie_sysctl_target_tupdate_maxb_handler, "L", "the frequency of drop probability calculation in microsecond"); SYSCTL_PROC(_net_inet_ip_dummynet_fqpie, OID_AUTO, max_burst, CTLTYPE_LONG | CTLFLAG_RW | CTLFLAG_NEEDGIANT, NULL, 0, fqpie_sysctl_target_tupdate_maxb_handler, "L", "Burst allowance interval in microsecond"); SYSCTL_PROC(_net_inet_ip_dummynet_fqpie, OID_AUTO, max_ecnth, CTLTYPE_LONG | CTLFLAG_RW | CTLFLAG_NEEDGIANT, NULL, 0, fqpie_sysctl_max_ecnth_handler, "L", "ECN safeguard threshold scaled by 1000"); SYSCTL_PROC(_net_inet_ip_dummynet_fqpie, OID_AUTO, alpha, CTLTYPE_LONG | CTLFLAG_RW | CTLFLAG_NEEDGIANT, NULL, 0, fqpie_sysctl_alpha_beta_handler, "L", "PIE alpha scaled by 1000"); SYSCTL_PROC(_net_inet_ip_dummynet_fqpie, OID_AUTO, beta, CTLTYPE_LONG | CTLFLAG_RW | CTLFLAG_NEEDGIANT, NULL, 0, fqpie_sysctl_alpha_beta_handler, "L", "beta scaled by 1000"); SYSCTL_UINT(_net_inet_ip_dummynet_fqpie, OID_AUTO, quantum, CTLFLAG_RW, &fq_pie_sysctl.quantum, 1514, "quantum for FQ_PIE"); SYSCTL_UINT(_net_inet_ip_dummynet_fqpie, OID_AUTO, flows, CTLFLAG_RW, &fq_pie_sysctl.flows_cnt, 1024, "Number of queues for FQ_PIE"); SYSCTL_UINT(_net_inet_ip_dummynet_fqpie, OID_AUTO, limit, CTLFLAG_RW, &fq_pie_sysctl.limit, 10240, "limit for FQ_PIE"); #endif /* Helper function to update queue&main-queue and scheduler statistics. * negative len & drop -> drop * negative len -> dequeue * positive len -> enqueue * positive len + drop -> drop during enqueue */ __inline static void fq_update_stats(struct fq_pie_flow *q, struct fq_pie_si *si, int len, int drop) { int inc = 0; if (len < 0) inc = -1; else if (len > 0) inc = 1; if (drop) { si->main_q.ni.drops ++; q->stats.drops ++; si->_si.ni.drops ++; - io_pkt_drop ++; + dn_cfg.io_pkt_drop ++; } if (!drop || (drop && len < 0)) { /* Update stats for the main queue */ si->main_q.ni.length += inc; si->main_q.ni.len_bytes += len; /*update sub-queue stats */ q->stats.length += inc; q->stats.len_bytes += len; /*update scheduler instance stats */ si->_si.ni.length += inc; si->_si.ni.len_bytes += len; } if (inc > 0) { si->main_q.ni.tot_bytes += len; si->main_q.ni.tot_pkts ++; q->stats.tot_bytes +=len; q->stats.tot_pkts++; si->_si.ni.tot_bytes +=len; si->_si.ni.tot_pkts ++; } } /* * Extract a packet from the head of sub-queue 'q' * Return a packet or NULL if the queue is empty. * If getts is set, also extract packet's timestamp from mtag. */ __inline static struct mbuf * fq_pie_extract_head(struct fq_pie_flow *q, aqm_time_t *pkt_ts, struct fq_pie_si *si, int getts) { struct mbuf *m = q->mq.head; if (m == NULL) return m; q->mq.head = m->m_nextpkt; fq_update_stats(q, si, -m->m_pkthdr.len, 0); if (si->main_q.ni.length == 0) /* queue is now idle */ si->main_q.q_time = dn_cfg.curr_time; if (getts) { /* extract packet timestamp*/ struct m_tag *mtag; mtag = m_tag_locate(m, MTAG_ABI_COMPAT, DN_AQM_MTAG_TS, NULL); if (mtag == NULL){ D("PIE timestamp mtag not found!"); *pkt_ts = 0; } else { *pkt_ts = *(aqm_time_t *)(mtag + 1); m_tag_delete(m,mtag); } } return m; } /* * Callout function for drop probability calculation * This function is called over tupdate ms and takes pointer of FQ-PIE * flow as an argument */ static void fq_calculate_drop_prob(void *x) { struct fq_pie_flow *q = (struct fq_pie_flow *) x; struct pie_status *pst = &q->pst; struct dn_aqm_pie_parms *pprms; int64_t p, prob, oldprob; aqm_time_t now; int p_isneg; now = AQM_UNOW; pprms = pst->parms; prob = pst->drop_prob; /* calculate current qdelay using DRE method. * If TS is used and no data in the queue, reset current_qdelay * as it stays at last value during dequeue process. */ if (pprms->flags & PIE_DEPRATEEST_ENABLED) pst->current_qdelay = ((uint64_t)q->stats.len_bytes * pst->avg_dq_time) >> PIE_DQ_THRESHOLD_BITS; else if (!q->stats.len_bytes) pst->current_qdelay = 0; /* calculate drop probability */ p = (int64_t)pprms->alpha * ((int64_t)pst->current_qdelay - (int64_t)pprms->qdelay_ref); p +=(int64_t) pprms->beta * ((int64_t)pst->current_qdelay - (int64_t)pst->qdelay_old); /* take absolute value so right shift result is well defined */ p_isneg = p < 0; if (p_isneg) { p = -p; } /* We PIE_MAX_PROB shift by 12-bits to increase the division precision */ p *= (PIE_MAX_PROB << 12) / AQM_TIME_1S; /* auto-tune drop probability */ if (prob < (PIE_MAX_PROB / 1000000)) /* 0.000001 */ p >>= 11 + PIE_FIX_POINT_BITS + 12; else if (prob < (PIE_MAX_PROB / 100000)) /* 0.00001 */ p >>= 9 + PIE_FIX_POINT_BITS + 12; else if (prob < (PIE_MAX_PROB / 10000)) /* 0.0001 */ p >>= 7 + PIE_FIX_POINT_BITS + 12; else if (prob < (PIE_MAX_PROB / 1000)) /* 0.001 */ p >>= 5 + PIE_FIX_POINT_BITS + 12; else if (prob < (PIE_MAX_PROB / 100)) /* 0.01 */ p >>= 3 + PIE_FIX_POINT_BITS + 12; else if (prob < (PIE_MAX_PROB / 10)) /* 0.1 */ p >>= 1 + PIE_FIX_POINT_BITS + 12; else p >>= PIE_FIX_POINT_BITS + 12; oldprob = prob; if (p_isneg) { prob = prob - p; /* check for multiplication underflow */ if (prob > oldprob) { prob= 0; D("underflow"); } } else { /* Cap Drop adjustment */ if ((pprms->flags & PIE_CAPDROP_ENABLED) && prob >= PIE_MAX_PROB / 10 && p > PIE_MAX_PROB / 50 ) { p = PIE_MAX_PROB / 50; } prob = prob + p; /* check for multiplication overflow */ if (probcurrent_qdelay == 0 && pst->qdelay_old == 0) { /* 0.98 ~= 1- 1/64 */ prob = prob - (prob >> 6); } if (prob > PIE_MAX_PROB) { prob = PIE_MAX_PROB; } } pst->drop_prob = prob; /* store current delay value */ pst->qdelay_old = pst->current_qdelay; /* update burst allowance */ if ((pst->sflags & PIE_ACTIVE) && pst->burst_allowance) { if (pst->burst_allowance > pprms->tupdate) pst->burst_allowance -= pprms->tupdate; else pst->burst_allowance = 0; } if (pst->sflags & PIE_ACTIVE) callout_reset_sbt(&pst->aqm_pie_callout, (uint64_t)pprms->tupdate * SBT_1US, 0, fq_calculate_drop_prob, q, 0); mtx_unlock(&pst->lock_mtx); } /* * Reset PIE variables & activate the queue */ __inline static void fq_activate_pie(struct fq_pie_flow *q) { struct pie_status *pst = &q->pst; struct dn_aqm_pie_parms *pprms; mtx_lock(&pst->lock_mtx); pprms = pst->parms; pprms = pst->parms; pst->drop_prob = 0; pst->qdelay_old = 0; pst->burst_allowance = pprms->max_burst; pst->accu_prob = 0; pst->dq_count = 0; pst->avg_dq_time = 0; pst->sflags = PIE_INMEASUREMENT | PIE_ACTIVE; pst->measurement_start = AQM_UNOW; callout_reset_sbt(&pst->aqm_pie_callout, (uint64_t)pprms->tupdate * SBT_1US, 0, fq_calculate_drop_prob, q, 0); mtx_unlock(&pst->lock_mtx); } /* * Deactivate PIE and stop probe update callout */ __inline static void fq_deactivate_pie(struct pie_status *pst) { mtx_lock(&pst->lock_mtx); pst->sflags &= ~(PIE_ACTIVE | PIE_INMEASUREMENT); callout_stop(&pst->aqm_pie_callout); //D("PIE Deactivated"); mtx_unlock(&pst->lock_mtx); } /* * Initialize PIE for sub-queue 'q' */ static int pie_init(struct fq_pie_flow *q, struct fq_pie_schk *fqpie_schk) { struct pie_status *pst=&q->pst; struct dn_aqm_pie_parms *pprms = pst->parms; int err = 0; if (!pprms){ D("AQM_PIE is not configured"); err = EINVAL; } else { q->psi_extra->nr_active_q++; /* For speed optimization, we caculate 1/3 queue size once here */ // XXX limit divided by number of queues divided by 3 ??? pst->one_third_q_size = (fqpie_schk->cfg.limit / fqpie_schk->cfg.flows_cnt) / 3; mtx_init(&pst->lock_mtx, "mtx_pie", NULL, MTX_DEF); callout_init_mtx(&pst->aqm_pie_callout, &pst->lock_mtx, CALLOUT_RETURNUNLOCKED); } return err; } /* * callout function to destroy PIE lock, and free fq_pie flows and fq_pie si * extra memory when number of active sub-queues reaches zero. * 'x' is a fq_pie_flow to be destroyed */ static void fqpie_callout_cleanup(void *x) { struct fq_pie_flow *q = x; struct pie_status *pst = &q->pst; struct fq_pie_si_extra *psi_extra; mtx_unlock(&pst->lock_mtx); mtx_destroy(&pst->lock_mtx); psi_extra = q->psi_extra; DN_BH_WLOCK(); psi_extra->nr_active_q--; /* when all sub-queues are destroyed, free flows fq_pie extra vars memory */ if (!psi_extra->nr_active_q) { free(psi_extra->flows, M_DUMMYNET); free(psi_extra, M_DUMMYNET); fq_pie_desc.ref_count--; } DN_BH_WUNLOCK(); } /* * Clean up PIE status for sub-queue 'q' * Stop callout timer and destroy mtx using fqpie_callout_cleanup() callout. */ static int pie_cleanup(struct fq_pie_flow *q) { struct pie_status *pst = &q->pst; mtx_lock(&pst->lock_mtx); callout_reset_sbt(&pst->aqm_pie_callout, SBT_1US, 0, fqpie_callout_cleanup, q, 0); mtx_unlock(&pst->lock_mtx); return 0; } /* * Dequeue and return a pcaket from sub-queue 'q' or NULL if 'q' is empty. * Also, caculate depature time or queue delay using timestamp */ static struct mbuf * pie_dequeue(struct fq_pie_flow *q, struct fq_pie_si *si) { struct mbuf *m; struct dn_aqm_pie_parms *pprms; struct pie_status *pst; aqm_time_t now; aqm_time_t pkt_ts, dq_time; int32_t w; pst = &q->pst; pprms = q->pst.parms; /*we extarct packet ts only when Departure Rate Estimation dis not used*/ m = fq_pie_extract_head(q, &pkt_ts, si, !(pprms->flags & PIE_DEPRATEEST_ENABLED)); if (!m || !(pst->sflags & PIE_ACTIVE)) return m; now = AQM_UNOW; if (pprms->flags & PIE_DEPRATEEST_ENABLED) { /* calculate average depature time */ if(pst->sflags & PIE_INMEASUREMENT) { pst->dq_count += m->m_pkthdr.len; if (pst->dq_count >= PIE_DQ_THRESHOLD) { dq_time = now - pst->measurement_start; /* * if we don't have old avg dq_time i.e PIE is (re)initialized, * don't use weight to calculate new avg_dq_time */ if(pst->avg_dq_time == 0) pst->avg_dq_time = dq_time; else { /* * weight = PIE_DQ_THRESHOLD/2^6, but we scaled * weight by 2^8. Thus, scaled * weight = PIE_DQ_THRESHOLD /2^8 * */ w = PIE_DQ_THRESHOLD >> 8; pst->avg_dq_time = (dq_time* w + (pst->avg_dq_time * ((1L << 8) - w))) >> 8; pst->sflags &= ~PIE_INMEASUREMENT; } } } /* * Start new measurment cycle when the queue has * PIE_DQ_THRESHOLD worth of bytes. */ if(!(pst->sflags & PIE_INMEASUREMENT) && q->stats.len_bytes >= PIE_DQ_THRESHOLD) { pst->sflags |= PIE_INMEASUREMENT; pst->measurement_start = now; pst->dq_count = 0; } } /* Optionally, use packet timestamp to estimate queue delay */ else pst->current_qdelay = now - pkt_ts; return m; } /* * Enqueue a packet in q, subject to space and FQ-PIE queue management policy * (whose parameters are in q->fs). * Update stats for the queue and the scheduler. * Return 0 on success, 1 on drop. The packet is consumed anyways. */ static int pie_enqueue(struct fq_pie_flow *q, struct mbuf* m, struct fq_pie_si *si) { uint64_t len; struct pie_status *pst; struct dn_aqm_pie_parms *pprms; int t; len = m->m_pkthdr.len; pst = &q->pst; pprms = pst->parms; t = ENQUE; /* drop/mark the packet when PIE is active and burst time elapsed */ if (pst->sflags & PIE_ACTIVE && pst->burst_allowance == 0 && drop_early(pst, q->stats.len_bytes) == DROP) { /* * if drop_prob over ECN threshold, drop the packet * otherwise mark and enqueue it. */ if (pprms->flags & PIE_ECN_ENABLED && pst->drop_prob < (pprms->max_ecnth << (PIE_PROB_BITS - PIE_FIX_POINT_BITS)) && ecn_mark(m)) t = ENQUE; else t = DROP; } /* Turn PIE on when 1/3 of the queue is full */ if (!(pst->sflags & PIE_ACTIVE) && q->stats.len_bytes >= pst->one_third_q_size) { fq_activate_pie(q); } /* reset burst tolerance and optinally turn PIE off*/ if (pst->drop_prob == 0 && pst->current_qdelay < (pprms->qdelay_ref >> 1) && pst->qdelay_old < (pprms->qdelay_ref >> 1)) { pst->burst_allowance = pprms->max_burst; if (pprms->flags & PIE_ON_OFF_MODE_ENABLED && q->stats.len_bytes<=0) fq_deactivate_pie(pst); } /* Use timestamp if Departure Rate Estimation mode is disabled */ if (t != DROP && !(pprms->flags & PIE_DEPRATEEST_ENABLED)) { /* Add TS to mbuf as a TAG */ struct m_tag *mtag; mtag = m_tag_locate(m, MTAG_ABI_COMPAT, DN_AQM_MTAG_TS, NULL); if (mtag == NULL) mtag = m_tag_alloc(MTAG_ABI_COMPAT, DN_AQM_MTAG_TS, sizeof(aqm_time_t), M_NOWAIT); if (mtag == NULL) { m_freem(m); t = DROP; } *(aqm_time_t *)(mtag + 1) = AQM_UNOW; m_tag_prepend(m, mtag); } if (t != DROP) { mq_append(&q->mq, m); fq_update_stats(q, si, len, 0); return 0; } else { fq_update_stats(q, si, len, 1); pst->accu_prob = 0; FREE_PKT(m); return 1; } return 0; } /* Drop a packet form the head of FQ-PIE sub-queue */ static void pie_drop_head(struct fq_pie_flow *q, struct fq_pie_si *si) { struct mbuf *m = q->mq.head; if (m == NULL) return; q->mq.head = m->m_nextpkt; fq_update_stats(q, si, -m->m_pkthdr.len, 1); if (si->main_q.ni.length == 0) /* queue is now idle */ si->main_q.q_time = dn_cfg.curr_time; /* reset accu_prob after packet drop */ q->pst.accu_prob = 0; FREE_PKT(m); } /* * Classify a packet to queue number using Jenkins hash function. * Return: queue number * the input of the hash are protocol no, perturbation, src IP, dst IP, * src port, dst port, */ static inline int fq_pie_classify_flow(struct mbuf *m, uint16_t fcount, struct fq_pie_si *si) { struct ip *ip; struct tcphdr *th; struct udphdr *uh; uint8_t tuple[41]; uint16_t hash=0; ip = (struct ip *)mtodo(m, dn_tag_get(m)->iphdr_off); //#ifdef INET6 struct ip6_hdr *ip6; int isip6; isip6 = (ip->ip_v == 6); if(isip6) { ip6 = (struct ip6_hdr *)ip; *((uint8_t *) &tuple[0]) = ip6->ip6_nxt; *((uint32_t *) &tuple[1]) = si->perturbation; memcpy(&tuple[5], ip6->ip6_src.s6_addr, 16); memcpy(&tuple[21], ip6->ip6_dst.s6_addr, 16); switch (ip6->ip6_nxt) { case IPPROTO_TCP: th = (struct tcphdr *)(ip6 + 1); *((uint16_t *) &tuple[37]) = th->th_dport; *((uint16_t *) &tuple[39]) = th->th_sport; break; case IPPROTO_UDP: uh = (struct udphdr *)(ip6 + 1); *((uint16_t *) &tuple[37]) = uh->uh_dport; *((uint16_t *) &tuple[39]) = uh->uh_sport; break; default: memset(&tuple[37], 0, 4); } hash = jenkins_hash(tuple, 41, HASHINIT) % fcount; return hash; } //#endif /* IPv4 */ *((uint8_t *) &tuple[0]) = ip->ip_p; *((uint32_t *) &tuple[1]) = si->perturbation; *((uint32_t *) &tuple[5]) = ip->ip_src.s_addr; *((uint32_t *) &tuple[9]) = ip->ip_dst.s_addr; switch (ip->ip_p) { case IPPROTO_TCP: th = (struct tcphdr *)(ip + 1); *((uint16_t *) &tuple[13]) = th->th_dport; *((uint16_t *) &tuple[15]) = th->th_sport; break; case IPPROTO_UDP: uh = (struct udphdr *)(ip + 1); *((uint16_t *) &tuple[13]) = uh->uh_dport; *((uint16_t *) &tuple[15]) = uh->uh_sport; break; default: memset(&tuple[13], 0, 4); } hash = jenkins_hash(tuple, 17, HASHINIT) % fcount; return hash; } /* * Enqueue a packet into an appropriate queue according to * FQ-CoDe; algorithm. */ static int fq_pie_enqueue(struct dn_sch_inst *_si, struct dn_queue *_q, struct mbuf *m) { struct fq_pie_si *si; struct fq_pie_schk *schk; struct dn_sch_fq_pie_parms *param; struct dn_queue *mainq; struct fq_pie_flow *flows; int idx, drop, i, maxidx; mainq = (struct dn_queue *)(_si + 1); si = (struct fq_pie_si *)_si; flows = si->si_extra->flows; schk = (struct fq_pie_schk *)(si->_si.sched+1); param = &schk->cfg; /* classify a packet to queue number*/ idx = fq_pie_classify_flow(m, param->flows_cnt, si); /* enqueue packet into appropriate queue using PIE AQM. * Note: 'pie_enqueue' function returns 1 only when it unable to * add timestamp to packet (no limit check)*/ drop = pie_enqueue(&flows[idx], m, si); /* pie unable to timestamp a packet */ if (drop) return 1; /* If the flow (sub-queue) is not active ,then add it to tail of * new flows list, initialize and activate it. */ if (!flows[idx].active) { STAILQ_INSERT_TAIL(&si->newflows, &flows[idx], flowchain); flows[idx].deficit = param->quantum; fq_activate_pie(&flows[idx]); flows[idx].active = 1; } /* check the limit for all queues and remove a packet from the * largest one */ if (mainq->ni.length > schk->cfg.limit) { /* find first active flow */ for (maxidx = 0; maxidx < schk->cfg.flows_cnt; maxidx++) if (flows[maxidx].active) break; if (maxidx < schk->cfg.flows_cnt) { /* find the largest sub- queue */ for (i = maxidx + 1; i < schk->cfg.flows_cnt; i++) if (flows[i].active && flows[i].stats.length > flows[maxidx].stats.length) maxidx = i; pie_drop_head(&flows[maxidx], si); drop = 1; } } return drop; } /* * Dequeue a packet from an appropriate queue according to * FQ-CoDel algorithm. */ static struct mbuf * fq_pie_dequeue(struct dn_sch_inst *_si) { struct fq_pie_si *si; struct fq_pie_schk *schk; struct dn_sch_fq_pie_parms *param; struct fq_pie_flow *f; struct mbuf *mbuf; struct fq_pie_list *fq_pie_flowlist; si = (struct fq_pie_si *)_si; schk = (struct fq_pie_schk *)(si->_si.sched+1); param = &schk->cfg; do { /* select a list to start with */ if (STAILQ_EMPTY(&si->newflows)) fq_pie_flowlist = &si->oldflows; else fq_pie_flowlist = &si->newflows; /* Both new and old queue lists are empty, return NULL */ if (STAILQ_EMPTY(fq_pie_flowlist)) return NULL; f = STAILQ_FIRST(fq_pie_flowlist); while (f != NULL) { /* if there is no flow(sub-queue) deficit, increase deficit * by quantum, move the flow to the tail of old flows list * and try another flow. * Otherwise, the flow will be used for dequeue. */ if (f->deficit < 0) { f->deficit += param->quantum; STAILQ_REMOVE_HEAD(fq_pie_flowlist, flowchain); STAILQ_INSERT_TAIL(&si->oldflows, f, flowchain); } else break; f = STAILQ_FIRST(fq_pie_flowlist); } /* the new flows list is empty, try old flows list */ if (STAILQ_EMPTY(fq_pie_flowlist)) continue; /* Dequeue a packet from the selected flow */ mbuf = pie_dequeue(f, si); /* pie did not return a packet */ if (!mbuf) { /* If the selected flow belongs to new flows list, then move * it to the tail of old flows list. Otherwise, deactivate it and * remove it from the old list and */ if (fq_pie_flowlist == &si->newflows) { STAILQ_REMOVE_HEAD(fq_pie_flowlist, flowchain); STAILQ_INSERT_TAIL(&si->oldflows, f, flowchain); } else { f->active = 0; fq_deactivate_pie(&f->pst); STAILQ_REMOVE_HEAD(fq_pie_flowlist, flowchain); } /* start again */ continue; } /* we have a packet to return, * update flow deficit and return the packet*/ f->deficit -= mbuf->m_pkthdr.len; return mbuf; } while (1); /* unreachable point */ return NULL; } /* * Initialize fq_pie scheduler instance. * also, allocate memory for flows array. */ static int fq_pie_new_sched(struct dn_sch_inst *_si) { struct fq_pie_si *si; struct dn_queue *q; struct fq_pie_schk *schk; struct fq_pie_flow *flows; int i; si = (struct fq_pie_si *)_si; schk = (struct fq_pie_schk *)(_si->sched+1); if(si->si_extra) { D("si already configured!"); return 0; } /* init the main queue */ q = &si->main_q; set_oid(&q->ni.oid, DN_QUEUE, sizeof(*q)); q->_si = _si; q->fs = _si->sched->fs; /* allocate memory for scheduler instance extra vars */ si->si_extra = malloc(sizeof(struct fq_pie_si_extra), M_DUMMYNET, M_NOWAIT | M_ZERO); if (si->si_extra == NULL) { D("cannot allocate memory for fq_pie si extra vars"); return ENOMEM ; } /* allocate memory for flows array */ si->si_extra->flows = mallocarray(schk->cfg.flows_cnt, sizeof(struct fq_pie_flow), M_DUMMYNET, M_NOWAIT | M_ZERO); flows = si->si_extra->flows; if (flows == NULL) { free(si->si_extra, M_DUMMYNET); si->si_extra = NULL; D("cannot allocate memory for fq_pie flows"); return ENOMEM ; } /* init perturbation for this si */ si->perturbation = random(); si->si_extra->nr_active_q = 0; /* init the old and new flows lists */ STAILQ_INIT(&si->newflows); STAILQ_INIT(&si->oldflows); /* init the flows (sub-queues) */ for (i = 0; i < schk->cfg.flows_cnt; i++) { flows[i].pst.parms = &schk->cfg.pcfg; flows[i].psi_extra = si->si_extra; pie_init(&flows[i], schk); } fq_pie_desc.ref_count++; return 0; } /* * Free fq_pie scheduler instance. */ static int fq_pie_free_sched(struct dn_sch_inst *_si) { struct fq_pie_si *si; struct fq_pie_schk *schk; struct fq_pie_flow *flows; int i; si = (struct fq_pie_si *)_si; schk = (struct fq_pie_schk *)(_si->sched+1); flows = si->si_extra->flows; for (i = 0; i < schk->cfg.flows_cnt; i++) { pie_cleanup(&flows[i]); } si->si_extra = NULL; return 0; } /* * Configure FQ-PIE scheduler. * the configurations for the scheduler is passed fromipfw userland. */ static int fq_pie_config(struct dn_schk *_schk) { struct fq_pie_schk *schk; struct dn_extra_parms *ep; struct dn_sch_fq_pie_parms *fqp_cfg; schk = (struct fq_pie_schk *)(_schk+1); ep = (struct dn_extra_parms *) _schk->cfg; /* par array contains fq_pie configuration as follow * PIE: 0- qdelay_ref,1- tupdate, 2- max_burst * 3- max_ecnth, 4- alpha, 5- beta, 6- flags * FQ_PIE: 7- quantum, 8- limit, 9- flows */ if (ep && ep->oid.len ==sizeof(*ep) && ep->oid.subtype == DN_SCH_PARAMS) { fqp_cfg = &schk->cfg; if (ep->par[0] < 0) fqp_cfg->pcfg.qdelay_ref = fq_pie_sysctl.pcfg.qdelay_ref; else fqp_cfg->pcfg.qdelay_ref = ep->par[0]; if (ep->par[1] < 0) fqp_cfg->pcfg.tupdate = fq_pie_sysctl.pcfg.tupdate; else fqp_cfg->pcfg.tupdate = ep->par[1]; if (ep->par[2] < 0) fqp_cfg->pcfg.max_burst = fq_pie_sysctl.pcfg.max_burst; else fqp_cfg->pcfg.max_burst = ep->par[2]; if (ep->par[3] < 0) fqp_cfg->pcfg.max_ecnth = fq_pie_sysctl.pcfg.max_ecnth; else fqp_cfg->pcfg.max_ecnth = ep->par[3]; if (ep->par[4] < 0) fqp_cfg->pcfg.alpha = fq_pie_sysctl.pcfg.alpha; else fqp_cfg->pcfg.alpha = ep->par[4]; if (ep->par[5] < 0) fqp_cfg->pcfg.beta = fq_pie_sysctl.pcfg.beta; else fqp_cfg->pcfg.beta = ep->par[5]; if (ep->par[6] < 0) fqp_cfg->pcfg.flags = 0; else fqp_cfg->pcfg.flags = ep->par[6]; /* FQ configurations */ if (ep->par[7] < 0) fqp_cfg->quantum = fq_pie_sysctl.quantum; else fqp_cfg->quantum = ep->par[7]; if (ep->par[8] < 0) fqp_cfg->limit = fq_pie_sysctl.limit; else fqp_cfg->limit = ep->par[8]; if (ep->par[9] < 0) fqp_cfg->flows_cnt = fq_pie_sysctl.flows_cnt; else fqp_cfg->flows_cnt = ep->par[9]; /* Bound the configurations */ fqp_cfg->pcfg.qdelay_ref = BOUND_VAR(fqp_cfg->pcfg.qdelay_ref, 1, 5 * AQM_TIME_1S); fqp_cfg->pcfg.tupdate = BOUND_VAR(fqp_cfg->pcfg.tupdate, 1, 5 * AQM_TIME_1S); fqp_cfg->pcfg.max_burst = BOUND_VAR(fqp_cfg->pcfg.max_burst, 0, 5 * AQM_TIME_1S); fqp_cfg->pcfg.max_ecnth = BOUND_VAR(fqp_cfg->pcfg.max_ecnth, 0, PIE_SCALE); fqp_cfg->pcfg.alpha = BOUND_VAR(fqp_cfg->pcfg.alpha, 0, 7 * PIE_SCALE); fqp_cfg->pcfg.beta = BOUND_VAR(fqp_cfg->pcfg.beta, 0, 7 * PIE_SCALE); fqp_cfg->quantum = BOUND_VAR(fqp_cfg->quantum,1,9000); fqp_cfg->limit= BOUND_VAR(fqp_cfg->limit,1,20480); fqp_cfg->flows_cnt= BOUND_VAR(fqp_cfg->flows_cnt,1,65536); } else { D("Wrong parameters for fq_pie scheduler"); return 1; } return 0; } /* * Return FQ-PIE scheduler configurations * the configurations for the scheduler is passed to userland. */ static int fq_pie_getconfig (struct dn_schk *_schk, struct dn_extra_parms *ep) { struct fq_pie_schk *schk = (struct fq_pie_schk *)(_schk+1); struct dn_sch_fq_pie_parms *fqp_cfg; fqp_cfg = &schk->cfg; strcpy(ep->name, fq_pie_desc.name); ep->par[0] = fqp_cfg->pcfg.qdelay_ref; ep->par[1] = fqp_cfg->pcfg.tupdate; ep->par[2] = fqp_cfg->pcfg.max_burst; ep->par[3] = fqp_cfg->pcfg.max_ecnth; ep->par[4] = fqp_cfg->pcfg.alpha; ep->par[5] = fqp_cfg->pcfg.beta; ep->par[6] = fqp_cfg->pcfg.flags; ep->par[7] = fqp_cfg->quantum; ep->par[8] = fqp_cfg->limit; ep->par[9] = fqp_cfg->flows_cnt; return 0; } /* * FQ-PIE scheduler descriptor * contains the type of the scheduler, the name, the size of extra * data structures, and function pointers. */ static struct dn_alg fq_pie_desc = { _SI( .type = ) DN_SCHED_FQ_PIE, _SI( .name = ) "FQ_PIE", _SI( .flags = ) 0, _SI( .schk_datalen = ) sizeof(struct fq_pie_schk), _SI( .si_datalen = ) sizeof(struct fq_pie_si) - sizeof(struct dn_sch_inst), _SI( .q_datalen = ) 0, _SI( .enqueue = ) fq_pie_enqueue, _SI( .dequeue = ) fq_pie_dequeue, _SI( .config = ) fq_pie_config, /* new sched i.e. sched X config ...*/ _SI( .destroy = ) NULL, /*sched x delete */ _SI( .new_sched = ) fq_pie_new_sched, /* new schd instance */ _SI( .free_sched = ) fq_pie_free_sched, /* delete schd instance */ _SI( .new_fsk = ) NULL, _SI( .free_fsk = ) NULL, _SI( .new_queue = ) NULL, _SI( .free_queue = ) NULL, _SI( .getconfig = ) fq_pie_getconfig, _SI( .ref_count = ) 0 }; DECLARE_DNSCHED_MODULE(dn_fq_pie, &fq_pie_desc); diff --git a/sys/netpfil/ipfw/ip_dn_io.c b/sys/netpfil/ipfw/ip_dn_io.c index 1b39fcb0359f..f71d07ae1140 100644 --- a/sys/netpfil/ipfw/ip_dn_io.c +++ b/sys/netpfil/ipfw/ip_dn_io.c @@ -1,981 +1,973 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2010 Luigi Rizzo, Riccardo Panicucci, Universita` di Pisa * All rights reserved * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Dummynet portions related to packet handling. */ #include __FBSDID("$FreeBSD$"); #include "opt_inet6.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* IFNAMSIZ, struct ifaddr, ifq head, lock.h mutex.h */ #include /* NET_EPOCH_... */ #include #include #include #include /* ip_len, ip_off */ #include /* ip_output(), IP_FORWARDING */ #include #include #include /* various ether_* routines */ #include /* for ip6_input, ip6_output prototypes */ #include #include #include #include #ifdef NEW_AQM #include #endif #include /* * We keep a private variable for the simulation time, but we could * probably use an existing one ("softticks" in sys/kern/kern_timeout.c) * instead of dn_cfg.curr_time */ struct dn_parms dn_cfg; //VNET_DEFINE(struct dn_parms, _base_dn_cfg); static long tick_last; /* Last tick duration (usec). */ static long tick_delta; /* Last vs standard tick diff (usec). */ static long tick_delta_sum; /* Accumulated tick difference (usec).*/ static long tick_adjustment; /* Tick adjustments done. */ static long tick_lost; /* Lost(coalesced) ticks number. */ /* Adjusted vs non-adjusted curr_time difference (ticks). */ static long tick_diff; -static unsigned long io_pkt; -static unsigned long io_pkt_fast; - -#ifdef NEW_AQM -unsigned long io_pkt_drop; -#else -static unsigned long io_pkt_drop; -#endif /* * We use a heap to store entities for which we have pending timer events. * The heap is checked at every tick and all entities with expired events * are extracted. */ MALLOC_DEFINE(M_DUMMYNET, "dummynet", "dummynet heap"); extern void (*bridge_dn_p)(struct mbuf *, struct ifnet *); #ifdef SYSCTL_NODE /* * Because of the way the SYSBEGIN/SYSEND macros work on other * platforms, there should not be functions between them. * So keep the handlers outside the block. */ static int sysctl_hash_size(SYSCTL_HANDLER_ARGS) { int error, value; value = dn_cfg.hash_size; error = sysctl_handle_int(oidp, &value, 0, req); if (error != 0 || req->newptr == NULL) return (error); if (value < 16 || value > 65536) return (EINVAL); dn_cfg.hash_size = value; return (0); } static int sysctl_limits(SYSCTL_HANDLER_ARGS) { int error; long value; if (arg2 != 0) value = dn_cfg.slot_limit; else value = dn_cfg.byte_limit; error = sysctl_handle_long(oidp, &value, 0, req); if (error != 0 || req->newptr == NULL) return (error); if (arg2 != 0) { if (value < 1) return (EINVAL); dn_cfg.slot_limit = value; } else { if (value < 1500) return (EINVAL); dn_cfg.byte_limit = value; } return (0); } SYSBEGIN(f4) SYSCTL_DECL(_net_inet); SYSCTL_DECL(_net_inet_ip); #ifdef NEW_AQM SYSCTL_NODE(_net_inet_ip, OID_AUTO, dummynet, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "Dummynet"); #else static SYSCTL_NODE(_net_inet_ip, OID_AUTO, dummynet, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "Dummynet"); #endif /* wrapper to pass dn_cfg fields to SYSCTL_* */ //#define DC(x) (&(VNET_NAME(_base_dn_cfg).x)) #define DC(x) (&(dn_cfg.x)) /* parameters */ SYSCTL_PROC(_net_inet_ip_dummynet, OID_AUTO, hash_size, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, 0, 0, sysctl_hash_size, "I", "Default hash table size"); SYSCTL_PROC(_net_inet_ip_dummynet, OID_AUTO, pipe_slot_limit, CTLTYPE_LONG | CTLFLAG_RW | CTLFLAG_NEEDGIANT, 0, 1, sysctl_limits, "L", "Upper limit in slots for pipe queue."); SYSCTL_PROC(_net_inet_ip_dummynet, OID_AUTO, pipe_byte_limit, CTLTYPE_LONG | CTLFLAG_RW | CTLFLAG_NEEDGIANT, 0, 0, sysctl_limits, "L", "Upper limit in bytes for pipe queue."); SYSCTL_INT(_net_inet_ip_dummynet, OID_AUTO, io_fast, CTLFLAG_RW, DC(io_fast), 0, "Enable fast dummynet io."); SYSCTL_INT(_net_inet_ip_dummynet, OID_AUTO, debug, CTLFLAG_RW, DC(debug), 0, "Dummynet debug level"); /* RED parameters */ SYSCTL_INT(_net_inet_ip_dummynet, OID_AUTO, red_lookup_depth, CTLFLAG_RD, DC(red_lookup_depth), 0, "Depth of RED lookup table"); SYSCTL_INT(_net_inet_ip_dummynet, OID_AUTO, red_avg_pkt_size, CTLFLAG_RD, DC(red_avg_pkt_size), 0, "RED Medium packet size"); SYSCTL_INT(_net_inet_ip_dummynet, OID_AUTO, red_max_pkt_size, CTLFLAG_RD, DC(red_max_pkt_size), 0, "RED Max packet size"); /* time adjustment */ SYSCTL_LONG(_net_inet_ip_dummynet, OID_AUTO, tick_delta, CTLFLAG_RD, &tick_delta, 0, "Last vs standard tick difference (usec)."); SYSCTL_LONG(_net_inet_ip_dummynet, OID_AUTO, tick_delta_sum, CTLFLAG_RD, &tick_delta_sum, 0, "Accumulated tick difference (usec)."); SYSCTL_LONG(_net_inet_ip_dummynet, OID_AUTO, tick_adjustment, CTLFLAG_RD, &tick_adjustment, 0, "Tick adjustments done."); SYSCTL_LONG(_net_inet_ip_dummynet, OID_AUTO, tick_diff, CTLFLAG_RD, &tick_diff, 0, "Adjusted vs non-adjusted curr_time difference (ticks)."); SYSCTL_LONG(_net_inet_ip_dummynet, OID_AUTO, tick_lost, CTLFLAG_RD, &tick_lost, 0, "Number of ticks coalesced by dummynet taskqueue."); /* Drain parameters */ SYSCTL_UINT(_net_inet_ip_dummynet, OID_AUTO, expire, CTLFLAG_RW, DC(expire), 0, "Expire empty queues/pipes"); SYSCTL_UINT(_net_inet_ip_dummynet, OID_AUTO, expire_cycle, CTLFLAG_RD, DC(expire_cycle), 0, "Expire cycle for queues/pipes"); /* statistics */ SYSCTL_INT(_net_inet_ip_dummynet, OID_AUTO, schk_count, CTLFLAG_RD, DC(schk_count), 0, "Number of schedulers"); SYSCTL_INT(_net_inet_ip_dummynet, OID_AUTO, si_count, CTLFLAG_RD, DC(si_count), 0, "Number of scheduler instances"); SYSCTL_INT(_net_inet_ip_dummynet, OID_AUTO, fsk_count, CTLFLAG_RD, DC(fsk_count), 0, "Number of flowsets"); SYSCTL_INT(_net_inet_ip_dummynet, OID_AUTO, queue_count, CTLFLAG_RD, DC(queue_count), 0, "Number of queues"); SYSCTL_ULONG(_net_inet_ip_dummynet, OID_AUTO, io_pkt, - CTLFLAG_RD, &io_pkt, 0, + CTLFLAG_RD, DC(io_pkt), 0, "Number of packets passed to dummynet."); SYSCTL_ULONG(_net_inet_ip_dummynet, OID_AUTO, io_pkt_fast, - CTLFLAG_RD, &io_pkt_fast, 0, + CTLFLAG_RD, DC(io_pkt_fast), 0, "Number of packets bypassed dummynet scheduler."); SYSCTL_ULONG(_net_inet_ip_dummynet, OID_AUTO, io_pkt_drop, - CTLFLAG_RD, &io_pkt_drop, 0, + CTLFLAG_RD, DC(io_pkt_drop), 0, "Number of packets dropped by dummynet."); #undef DC SYSEND #endif static void dummynet_send(struct mbuf *); /* * Return the mbuf tag holding the dummynet state (it should * be the first one on the list). */ struct dn_pkt_tag * dn_tag_get(struct mbuf *m) { struct m_tag *mtag = m_tag_first(m); #ifdef NEW_AQM /* XXX: to skip ts m_tag. For Debugging only*/ if (mtag != NULL && mtag->m_tag_id == DN_AQM_MTAG_TS) { m_tag_delete(m,mtag); mtag = m_tag_first(m); D("skip TS tag"); } #endif KASSERT(mtag != NULL && mtag->m_tag_cookie == MTAG_ABI_COMPAT && mtag->m_tag_id == PACKET_TAG_DUMMYNET, ("packet on dummynet queue w/o dummynet tag!")); return (struct dn_pkt_tag *)(mtag+1); } #ifndef NEW_AQM static inline void mq_append(struct mq *q, struct mbuf *m) { #ifdef USERSPACE // buffers from netmap need to be copied // XXX note that the routine is not expected to fail ND("append %p to %p", m, q); if (m->m_flags & M_STACK) { struct mbuf *m_new; void *p; int l, ofs; ofs = m->m_data - m->__m_extbuf; // XXX allocate MGETHDR(m_new, M_NOWAIT, MT_DATA); ND("*** WARNING, volatile buf %p ext %p %d dofs %d m_new %p", m, m->__m_extbuf, m->__m_extlen, ofs, m_new); p = m_new->__m_extbuf; /* new pointer */ l = m_new->__m_extlen; /* new len */ if (l <= m->__m_extlen) { panic("extlen too large"); } *m_new = *m; // copy m_new->m_flags &= ~M_STACK; m_new->__m_extbuf = p; // point to new buffer _pkt_copy(m->__m_extbuf, p, m->__m_extlen); m_new->m_data = p + ofs; m = m_new; } #endif /* USERSPACE */ if (q->head == NULL) q->head = m; else q->tail->m_nextpkt = m; q->count++; q->tail = m; m->m_nextpkt = NULL; } #endif /* * Dispose a list of packet. Use a functions so if we need to do * more work, this is a central point to do it. */ void dn_free_pkts(struct mbuf *mnext) { struct mbuf *m; while ((m = mnext) != NULL) { mnext = m->m_nextpkt; FREE_PKT(m); } } static int red_drops (struct dn_queue *q, int len) { /* * RED algorithm * * RED calculates the average queue size (avg) using a low-pass filter * with an exponential weighted (w_q) moving average: * avg <- (1-w_q) * avg + w_q * q_size * where q_size is the queue length (measured in bytes or * packets). * * If q_size == 0, we compute the idle time for the link, and set * avg = (1 - w_q)^(idle/s) * where s is the time needed for transmitting a medium-sized packet. * * Now, if avg < min_th the packet is enqueued. * If avg > max_th the packet is dropped. Otherwise, the packet is * dropped with probability P function of avg. */ struct dn_fsk *fs = q->fs; int64_t p_b = 0; /* Queue in bytes or packets? */ uint32_t q_size = (fs->fs.flags & DN_QSIZE_BYTES) ? q->ni.len_bytes : q->ni.length; /* Average queue size estimation. */ if (q_size != 0) { /* Queue is not empty, avg <- avg + (q_size - avg) * w_q */ int diff = SCALE(q_size) - q->avg; int64_t v = SCALE_MUL((int64_t)diff, (int64_t)fs->w_q); q->avg += (int)v; } else { /* * Queue is empty, find for how long the queue has been * empty and use a lookup table for computing * (1 - * w_q)^(idle_time/s) where s is the time to send a * (small) packet. * XXX check wraps... */ if (q->avg) { u_int t = div64((dn_cfg.curr_time - q->q_time), fs->lookup_step); q->avg = (t < fs->lookup_depth) ? SCALE_MUL(q->avg, fs->w_q_lookup[t]) : 0; } } /* Should i drop? */ if (q->avg < fs->min_th) { q->count = -1; return (0); /* accept packet */ } if (q->avg >= fs->max_th) { /* average queue >= max threshold */ if (fs->fs.flags & DN_IS_ECN) return (1); if (fs->fs.flags & DN_IS_GENTLE_RED) { /* * According to Gentle-RED, if avg is greater than * max_th the packet is dropped with a probability * p_b = c_3 * avg - c_4 * where c_3 = (1 - max_p) / max_th * c_4 = 1 - 2 * max_p */ p_b = SCALE_MUL((int64_t)fs->c_3, (int64_t)q->avg) - fs->c_4; } else { q->count = -1; return (1); } } else if (q->avg > fs->min_th) { if (fs->fs.flags & DN_IS_ECN) return (1); /* * We compute p_b using the linear dropping function * p_b = c_1 * avg - c_2 * where c_1 = max_p / (max_th - min_th) * c_2 = max_p * min_th / (max_th - min_th) */ p_b = SCALE_MUL((int64_t)fs->c_1, (int64_t)q->avg) - fs->c_2; } if (fs->fs.flags & DN_QSIZE_BYTES) p_b = div64((p_b * len) , fs->max_pkt_size); if (++q->count == 0) q->random = random() & 0xffff; else { /* * q->count counts packets arrived since last drop, so a greater * value of q->count means a greater packet drop probability. */ if (SCALE_MUL(p_b, SCALE((int64_t)q->count)) > q->random) { q->count = 0; /* After a drop we calculate a new random value. */ q->random = random() & 0xffff; return (1); /* drop */ } } /* End of RED algorithm. */ return (0); /* accept */ } /* * ECN/ECT Processing (partially adopted from altq) */ #ifndef NEW_AQM static #endif int ecn_mark(struct mbuf* m) { struct ip *ip; ip = (struct ip *)mtodo(m, dn_tag_get(m)->iphdr_off); switch (ip->ip_v) { case IPVERSION: { uint16_t old; if ((ip->ip_tos & IPTOS_ECN_MASK) == IPTOS_ECN_NOTECT) return (0); /* not-ECT */ if ((ip->ip_tos & IPTOS_ECN_MASK) == IPTOS_ECN_CE) return (1); /* already marked */ /* * ecn-capable but not marked, * mark CE and update checksum */ old = *(uint16_t *)ip; ip->ip_tos |= IPTOS_ECN_CE; ip->ip_sum = cksum_adjust(ip->ip_sum, old, *(uint16_t *)ip); return (1); } #ifdef INET6 case (IPV6_VERSION >> 4): { struct ip6_hdr *ip6 = (struct ip6_hdr *)ip; u_int32_t flowlabel; flowlabel = ntohl(ip6->ip6_flow); if ((flowlabel >> 28) != 6) return (0); /* version mismatch! */ if ((flowlabel & (IPTOS_ECN_MASK << 20)) == (IPTOS_ECN_NOTECT << 20)) return (0); /* not-ECT */ if ((flowlabel & (IPTOS_ECN_MASK << 20)) == (IPTOS_ECN_CE << 20)) return (1); /* already marked */ /* * ecn-capable but not marked, mark CE */ flowlabel |= (IPTOS_ECN_CE << 20); ip6->ip6_flow = htonl(flowlabel); return (1); } #endif } return (0); } /* * Enqueue a packet in q, subject to space and queue management policy * (whose parameters are in q->fs). * Update stats for the queue and the scheduler. * Return 0 on success, 1 on drop. The packet is consumed anyways. */ int dn_enqueue(struct dn_queue *q, struct mbuf* m, int drop) { struct dn_fs *f; struct dn_flow *ni; /* stats for scheduler instance */ uint64_t len; if (q->fs == NULL || q->_si == NULL) { printf("%s fs %p si %p, dropping\n", __FUNCTION__, q->fs, q->_si); FREE_PKT(m); return 1; } f = &(q->fs->fs); ni = &q->_si->ni; len = m->m_pkthdr.len; /* Update statistics, then check reasons to drop pkt. */ q->ni.tot_bytes += len; q->ni.tot_pkts++; ni->tot_bytes += len; ni->tot_pkts++; if (drop) goto drop; if (f->plr && random() < f->plr) goto drop; #ifdef NEW_AQM /* Call AQM enqueue function */ if (q->fs->aqmfp) return q->fs->aqmfp->enqueue(q ,m); #endif if (f->flags & DN_IS_RED && red_drops(q, m->m_pkthdr.len)) { if (!(f->flags & DN_IS_ECN) || !ecn_mark(m)) goto drop; } if (f->flags & DN_QSIZE_BYTES) { if (q->ni.len_bytes > f->qsize) goto drop; } else if (q->ni.length >= f->qsize) { goto drop; } mq_append(&q->mq, m); q->ni.length++; q->ni.len_bytes += len; ni->length++; ni->len_bytes += len; return (0); drop: - io_pkt_drop++; + dn_cfg.io_pkt_drop++; q->ni.drops++; ni->drops++; FREE_PKT(m); return (1); } /* * Fetch packets from the delay line which are due now. If there are * leftover packets, reinsert the delay line in the heap. * Runs under scheduler lock. */ static void transmit_event(struct mq *q, struct delay_line *dline, uint64_t now) { struct mbuf *m; struct dn_pkt_tag *pkt = NULL; dline->oid.subtype = 0; /* not in heap */ while ((m = dline->mq.head) != NULL) { pkt = dn_tag_get(m); if (!DN_KEY_LEQ(pkt->output_time, now)) break; dline->mq.head = m->m_nextpkt; dline->mq.count--; mq_append(q, m); } if (m != NULL) { dline->oid.subtype = 1; /* in heap */ heap_insert(&dn_cfg.evheap, pkt->output_time, dline); } } /* * Convert the additional MAC overheads/delays into an equivalent * number of bits for the given data rate. The samples are * in milliseconds so we need to divide by 1000. */ static uint64_t extra_bits(struct mbuf *m, struct dn_schk *s) { int index; uint64_t bits; struct dn_profile *pf = s->profile; if (!pf || pf->samples_no == 0) return 0; index = random() % pf->samples_no; bits = div64((uint64_t)pf->samples[index] * s->link.bandwidth, 1000); if (index >= pf->loss_level) { struct dn_pkt_tag *dt = dn_tag_get(m); if (dt) dt->dn_dir = DIR_DROP; } return bits; } /* * Send traffic from a scheduler instance due by 'now'. * Return a pointer to the head of the queue. */ static struct mbuf * serve_sched(struct mq *q, struct dn_sch_inst *si, uint64_t now) { struct mq def_q; struct dn_schk *s = si->sched; struct mbuf *m = NULL; int delay_line_idle = (si->dline.mq.head == NULL); int done, bw; if (q == NULL) { q = &def_q; q->head = NULL; } bw = s->link.bandwidth; si->kflags &= ~DN_ACTIVE; if (bw > 0) si->credit += (now - si->sched_time) * bw; else si->credit = 0; si->sched_time = now; done = 0; while (si->credit >= 0 && (m = s->fp->dequeue(si)) != NULL) { uint64_t len_scaled; done++; len_scaled = (bw == 0) ? 0 : hz * (m->m_pkthdr.len * 8 + extra_bits(m, s)); si->credit -= len_scaled; /* Move packet in the delay line */ dn_tag_get(m)->output_time = dn_cfg.curr_time + s->link.delay ; mq_append(&si->dline.mq, m); } /* * If credit >= 0 the instance is idle, mark time. * Otherwise put back in the heap, and adjust the output * time of the last inserted packet, m, which was too early. */ if (si->credit >= 0) { si->idle_time = now; } else { uint64_t t; KASSERT (bw > 0, ("bw=0 and credit<0 ?")); t = div64(bw - 1 - si->credit, bw); if (m) dn_tag_get(m)->output_time += t; si->kflags |= DN_ACTIVE; heap_insert(&dn_cfg.evheap, now + t, si); } if (delay_line_idle && done) transmit_event(q, &si->dline, now); return q->head; } /* * The timer handler for dummynet. Time is computed in ticks, but * but the code is tolerant to the actual rate at which this is called. * Once complete, the function reschedules itself for the next tick. */ void dummynet_task(void *context, int pending) { struct timeval t; struct mq q = { NULL, NULL }; /* queue to accumulate results */ CURVNET_SET((struct vnet *)context); DN_BH_WLOCK(); /* Update number of lost(coalesced) ticks. */ tick_lost += pending - 1; getmicrouptime(&t); /* Last tick duration (usec). */ tick_last = (t.tv_sec - dn_cfg.prev_t.tv_sec) * 1000000 + (t.tv_usec - dn_cfg.prev_t.tv_usec); /* Last tick vs standard tick difference (usec). */ tick_delta = (tick_last * hz - 1000000) / hz; /* Accumulated tick difference (usec). */ tick_delta_sum += tick_delta; dn_cfg.prev_t = t; /* * Adjust curr_time if the accumulated tick difference is * greater than the 'standard' tick. Since curr_time should * be monotonically increasing, we do positive adjustments * as required, and throttle curr_time in case of negative * adjustment. */ dn_cfg.curr_time++; if (tick_delta_sum - tick >= 0) { int diff = tick_delta_sum / tick; dn_cfg.curr_time += diff; tick_diff += diff; tick_delta_sum %= tick; tick_adjustment++; } else if (tick_delta_sum + tick <= 0) { dn_cfg.curr_time--; tick_diff--; tick_delta_sum += tick; tick_adjustment++; } /* serve pending events, accumulate in q */ for (;;) { struct dn_id *p; /* generic parameter to handler */ if (dn_cfg.evheap.elements == 0 || DN_KEY_LT(dn_cfg.curr_time, HEAP_TOP(&dn_cfg.evheap)->key)) break; p = HEAP_TOP(&dn_cfg.evheap)->object; heap_extract(&dn_cfg.evheap, NULL); if (p->type == DN_SCH_I) { serve_sched(&q, (struct dn_sch_inst *)p, dn_cfg.curr_time); } else { /* extracted a delay line */ transmit_event(&q, (struct delay_line *)p, dn_cfg.curr_time); } } if (dn_cfg.expire && ++dn_cfg.expire_cycle >= dn_cfg.expire) { dn_cfg.expire_cycle = 0; dn_drain_scheduler(); dn_drain_queue(); } dn_reschedule(); DN_BH_WUNLOCK(); if (q.head != NULL) dummynet_send(q.head); CURVNET_RESTORE(); } /* * forward a chain of packets to the proper destination. * This runs outside the dummynet lock. */ static void dummynet_send(struct mbuf *m) { struct mbuf *n; NET_EPOCH_ASSERT(); for (; m != NULL; m = n) { struct ifnet *ifp = NULL; /* gcc 3.4.6 complains */ struct m_tag *tag; int dst; n = m->m_nextpkt; m->m_nextpkt = NULL; tag = m_tag_first(m); if (tag == NULL) { /* should not happen */ dst = DIR_DROP; } else { struct dn_pkt_tag *pkt = dn_tag_get(m); /* extract the dummynet info, rename the tag * to carry reinject info. */ if (pkt->dn_dir == (DIR_OUT | PROTO_LAYER2) && pkt->ifp == NULL) { dst = DIR_DROP; } else { dst = pkt->dn_dir; ifp = pkt->ifp; tag->m_tag_cookie = MTAG_IPFW_RULE; tag->m_tag_id = 0; } } switch (dst) { case DIR_OUT: ip_output(m, NULL, NULL, IP_FORWARDING, NULL, NULL); break ; case DIR_IN : netisr_dispatch(NETISR_IP, m); break; #ifdef INET6 case DIR_IN | PROTO_IPV6: netisr_dispatch(NETISR_IPV6, m); break; case DIR_OUT | PROTO_IPV6: ip6_output(m, NULL, NULL, IPV6_FORWARDING, NULL, NULL, NULL); break; #endif case DIR_FWD | PROTO_IFB: /* DN_TO_IFB_FWD: */ if (bridge_dn_p != NULL) ((*bridge_dn_p)(m, ifp)); else printf("dummynet: if_bridge not loaded\n"); break; case DIR_IN | PROTO_LAYER2: /* DN_TO_ETH_DEMUX: */ /* * The Ethernet code assumes the Ethernet header is * contiguous in the first mbuf header. * Insure this is true. */ if (m->m_len < ETHER_HDR_LEN && (m = m_pullup(m, ETHER_HDR_LEN)) == NULL) { printf("dummynet/ether: pullup failed, " "dropping packet\n"); break; } ether_demux(m->m_pkthdr.rcvif, m); break; case DIR_OUT | PROTO_LAYER2: /* DN_TO_ETH_OUT: */ ether_output_frame(ifp, m); break; case DIR_DROP: /* drop the packet after some time */ FREE_PKT(m); break; default: printf("dummynet: bad switch %d!\n", dst); FREE_PKT(m); break; } } } static inline int tag_mbuf(struct mbuf *m, int dir, struct ip_fw_args *fwa) { struct dn_pkt_tag *dt; struct m_tag *mtag; mtag = m_tag_get(PACKET_TAG_DUMMYNET, sizeof(*dt), M_NOWAIT | M_ZERO); if (mtag == NULL) return 1; /* Cannot allocate packet header. */ m_tag_prepend(m, mtag); /* Attach to mbuf chain. */ dt = (struct dn_pkt_tag *)(mtag + 1); dt->rule = fwa->rule; dt->rule.info &= IPFW_ONEPASS; /* only keep this info */ dt->dn_dir = dir; dt->ifp = fwa->flags & IPFW_ARGS_OUT ? fwa->ifp : NULL; /* dt->output tame is updated as we move through */ dt->output_time = dn_cfg.curr_time; dt->iphdr_off = (dir & PROTO_LAYER2) ? ETHER_HDR_LEN : 0; return 0; } /* * dummynet hook for packets. * We use the argument to locate the flowset fs and the sched_set sch * associated to it. The we apply flow_mask and sched_mask to * determine the queue and scheduler instances. */ int dummynet_io(struct mbuf **m0, struct ip_fw_args *fwa) { struct mbuf *m = *m0; struct dn_fsk *fs = NULL; struct dn_sch_inst *si; struct dn_queue *q = NULL; /* default */ int fs_id, dir; fs_id = (fwa->rule.info & IPFW_INFO_MASK) + ((fwa->rule.info & IPFW_IS_PIPE) ? 2*DN_MAX_ID : 0); /* XXXGL: convert args to dir */ if (fwa->flags & IPFW_ARGS_IN) dir = DIR_IN; else dir = DIR_OUT; if (fwa->flags & IPFW_ARGS_ETHER) dir |= PROTO_LAYER2; else if (fwa->flags & IPFW_ARGS_IP6) dir |= PROTO_IPV6; DN_BH_WLOCK(); - io_pkt++; + dn_cfg.io_pkt++; /* we could actually tag outside the lock, but who cares... */ if (tag_mbuf(m, dir, fwa)) goto dropit; if (dn_cfg.busy) { /* if the upper half is busy doing something expensive, * lets queue the packet and move forward */ mq_append(&dn_cfg.pending, m); m = *m0 = NULL; /* consumed */ goto done; /* already active, nothing to do */ } /* XXX locate_flowset could be optimised with a direct ref. */ fs = dn_ht_find(dn_cfg.fshash, fs_id, 0, NULL); if (fs == NULL) goto dropit; /* This queue/pipe does not exist! */ if (fs->sched == NULL) /* should not happen */ goto dropit; /* find scheduler instance, possibly applying sched_mask */ si = ipdn_si_find(fs->sched, &(fwa->f_id)); if (si == NULL) goto dropit; /* * If the scheduler supports multiple queues, find the right one * (otherwise it will be ignored by enqueue). */ if (fs->sched->fp->flags & DN_MULTIQUEUE) { q = ipdn_q_find(fs, si, &(fwa->f_id)); if (q == NULL) goto dropit; } if (fs->sched->fp->enqueue(si, q, m)) { /* packet was dropped by enqueue() */ m = *m0 = NULL; /* dn_enqueue already increases io_pkt_drop */ - io_pkt_drop--; + dn_cfg.io_pkt_drop--; goto dropit; } if (si->kflags & DN_ACTIVE) { m = *m0 = NULL; /* consumed */ goto done; /* already active, nothing to do */ } /* compute the initial allowance */ if (si->idle_time < dn_cfg.curr_time) { /* Do this only on the first packet on an idle pipe */ struct dn_link *p = &fs->sched->link; si->sched_time = dn_cfg.curr_time; si->credit = dn_cfg.io_fast ? p->bandwidth : 0; if (p->burst) { uint64_t burst = (dn_cfg.curr_time - si->idle_time) * p->bandwidth; if (burst > p->burst) burst = p->burst; si->credit += burst; } } /* pass through scheduler and delay line */ m = serve_sched(NULL, si, dn_cfg.curr_time); /* optimization -- pass it back to ipfw for immediate send */ /* XXX Don't call dummynet_send() if scheduler return the packet * just enqueued. This avoid a lock order reversal. * */ if (/*dn_cfg.io_fast &&*/ m == *m0 && (dir & PROTO_LAYER2) == 0 ) { /* fast io, rename the tag * to carry reinject info. */ struct m_tag *tag = m_tag_first(m); tag->m_tag_cookie = MTAG_IPFW_RULE; tag->m_tag_id = 0; - io_pkt_fast++; + dn_cfg.io_pkt_fast++; if (m->m_nextpkt != NULL) { printf("dummynet: fast io: pkt chain detected!\n"); m->m_nextpkt = NULL; } m = NULL; } else { *m0 = NULL; } done: DN_BH_WUNLOCK(); if (m) dummynet_send(m); return 0; dropit: - io_pkt_drop++; + dn_cfg.io_pkt_drop++; DN_BH_WUNLOCK(); if (m) FREE_PKT(m); *m0 = NULL; return (fs && (fs->fs.flags & DN_NOERROR)) ? 0 : ENOBUFS; } diff --git a/sys/netpfil/ipfw/ip_dn_private.h b/sys/netpfil/ipfw/ip_dn_private.h index 24765bc09b0e..38c6ff1201d5 100644 --- a/sys/netpfil/ipfw/ip_dn_private.h +++ b/sys/netpfil/ipfw/ip_dn_private.h @@ -1,497 +1,502 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2010 Luigi Rizzo, Riccardo Panicucci, Universita` di Pisa * All rights reserved * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * internal dummynet APIs. * * $FreeBSD$ */ #ifndef _IP_DN_PRIVATE_H #define _IP_DN_PRIVATE_H /* debugging support * use ND() to remove debugging, D() to print a line, * DX(level, ...) to print above a certain level * If you redefine D() you are expected to redefine all. */ #ifndef D #define ND(fmt, ...) do {} while (0) #define D1(fmt, ...) do {} while (0) #define D(fmt, ...) printf("%-10s " fmt "\n", \ __FUNCTION__, ## __VA_ARGS__) #define DX(lev, fmt, ...) do { \ if (dn_cfg.debug > lev) D(fmt, ## __VA_ARGS__); } while (0) #endif MALLOC_DECLARE(M_DUMMYNET); #ifndef __linux__ #define div64(a, b) ((int64_t)(a) / (int64_t)(b)) #endif #define DN_LOCK_INIT() do { \ mtx_init(&dn_cfg.uh_mtx, "dn_uh", NULL, MTX_DEF); \ mtx_init(&dn_cfg.bh_mtx, "dn_bh", NULL, MTX_DEF); \ } while (0) #define DN_LOCK_DESTROY() do { \ mtx_destroy(&dn_cfg.uh_mtx); \ mtx_destroy(&dn_cfg.bh_mtx); \ } while (0) #if 0 /* not used yet */ #define DN_UH_RLOCK() mtx_lock(&dn_cfg.uh_mtx) #define DN_UH_RUNLOCK() mtx_unlock(&dn_cfg.uh_mtx) #define DN_UH_WLOCK() mtx_lock(&dn_cfg.uh_mtx) #define DN_UH_WUNLOCK() mtx_unlock(&dn_cfg.uh_mtx) #define DN_UH_LOCK_ASSERT() mtx_assert(&dn_cfg.uh_mtx, MA_OWNED) #endif #define DN_BH_RLOCK() mtx_lock(&dn_cfg.uh_mtx) #define DN_BH_RUNLOCK() mtx_unlock(&dn_cfg.uh_mtx) #define DN_BH_WLOCK() mtx_lock(&dn_cfg.uh_mtx) #define DN_BH_WUNLOCK() mtx_unlock(&dn_cfg.uh_mtx) #define DN_BH_LOCK_ASSERT() mtx_assert(&dn_cfg.uh_mtx, MA_OWNED) SLIST_HEAD(dn_schk_head, dn_schk); SLIST_HEAD(dn_sch_inst_head, dn_sch_inst); SLIST_HEAD(dn_fsk_head, dn_fsk); SLIST_HEAD(dn_queue_head, dn_queue); SLIST_HEAD(dn_alg_head, dn_alg); #ifdef NEW_AQM SLIST_HEAD(dn_aqm_head, dn_aqm); /* for new AQMs */ #endif struct mq { /* a basic queue of packets*/ struct mbuf *head, *tail; int count; }; static inline void set_oid(struct dn_id *o, int type, int len) { o->type = type; o->len = len; o->subtype = 0; } /* * configuration and global data for a dummynet instance * * When a configuration is modified from userland, 'id' is incremented * so we can use the value to check for stale pointers. */ struct dn_parms { uint32_t id; /* configuration version */ /* defaults (sysctl-accessible) */ int red_lookup_depth; int red_avg_pkt_size; int red_max_pkt_size; int hash_size; int max_hash_size; long byte_limit; /* max queue sizes */ long slot_limit; int io_fast; int debug; /* timekeeping */ struct timeval prev_t; /* last time dummynet_tick ran */ struct dn_heap evheap; /* scheduled events */ /* counters of objects -- used for reporting space */ int schk_count; int si_count; int fsk_count; int queue_count; + /* packet counters */ + unsigned long io_pkt; + unsigned long io_pkt_fast; + unsigned long io_pkt_drop; + /* ticks and other stuff */ uint64_t curr_time; /* flowsets and schedulers are in hash tables, with 'hash_size' * buckets. fshash is looked up at every packet arrival * so better be generous if we expect many entries. */ struct dn_ht *fshash; struct dn_ht *schedhash; /* list of flowsets without a scheduler -- use sch_chain */ struct dn_fsk_head fsu; /* list of unlinked flowsets */ struct dn_alg_head schedlist; /* list of algorithms */ #ifdef NEW_AQM struct dn_aqm_head aqmlist; /* list of AQMs */ #endif /* Store the fs/sch to scan when draining. The value is the * bucket number of the hash table. Expire can be disabled * with net.inet.ip.dummynet.expire=0, or it happens every * expire ticks. **/ int drain_fs; int drain_sch; uint32_t expire; uint32_t expire_cycle; /* tick count */ int init_done; /* if the upper half is busy doing something long, * can set the busy flag and we will enqueue packets in * a queue for later processing. */ int busy; struct mq pending; #ifdef _KERNEL /* * This file is normally used in the kernel, unless we do * some userland tests, in which case we do not need a mtx. * uh_mtx arbitrates between system calls and also * protects fshash, schedhash and fsunlinked. * These structures are readonly for the lower half. * bh_mtx protects all other structures which may be * modified upon packet arrivals */ #if defined( __linux__ ) || defined( _WIN32 ) spinlock_t uh_mtx; spinlock_t bh_mtx; #else struct mtx uh_mtx; struct mtx bh_mtx; #endif #endif /* _KERNEL */ }; /* * Delay line, contains all packets on output from a link. * Every scheduler instance has one. */ struct delay_line { struct dn_id oid; struct dn_sch_inst *si; struct mq mq; }; /* * The kernel side of a flowset. It is linked in a hash table * of flowsets, and in a list of children of their parent scheduler. * qht is either the queue or (if HAVE_MASK) a hash table queues. * Note that the mask to use is the (flow_mask|sched_mask), which * changes as we attach/detach schedulers. So we store it here. * * XXX If we want to add scheduler-specific parameters, we need to * put them in external storage because the scheduler may not be * available when the fsk is created. */ struct dn_fsk { /* kernel side of a flowset */ struct dn_fs fs; SLIST_ENTRY(dn_fsk) fsk_next; /* hash chain for fshash */ struct ipfw_flow_id fsk_mask; /* qht is a hash table of queues, or just a single queue * a bit in fs.flags tells us which one */ struct dn_ht *qht; struct dn_schk *sched; /* Sched we are linked to */ SLIST_ENTRY(dn_fsk) sch_chain; /* list of fsk attached to sched */ /* bucket index used by drain routine to drain queues for this * flowset */ int drain_bucket; /* Parameter realted to RED / GRED */ /* original values are in dn_fs*/ int w_q ; /* queue weight (scaled) */ int max_th ; /* maximum threshold for queue (scaled) */ int min_th ; /* minimum threshold for queue (scaled) */ int max_p ; /* maximum value for p_b (scaled) */ u_int c_1 ; /* max_p/(max_th-min_th) (scaled) */ u_int c_2 ; /* max_p*min_th/(max_th-min_th) (scaled) */ u_int c_3 ; /* for GRED, (1-max_p)/max_th (scaled) */ u_int c_4 ; /* for GRED, 1 - 2*max_p (scaled) */ u_int * w_q_lookup ; /* lookup table for computing (1-w_q)^t */ u_int lookup_depth ; /* depth of lookup table */ int lookup_step ; /* granularity inside the lookup table */ int lookup_weight ; /* equal to (1-w_q)^t / (1-w_q)^(t+1) */ int avg_pkt_size ; /* medium packet size */ int max_pkt_size ; /* max packet size */ #ifdef NEW_AQM struct dn_aqm *aqmfp; /* Pointer to AQM functions */ void *aqmcfg; /* configuration parameters for AQM */ #endif }; /* * A queue is created as a child of a flowset unless it belongs to * a !MULTIQUEUE scheduler. It is normally in a hash table in the * flowset. fs always points to the parent flowset. * si normally points to the sch_inst, unless the flowset has been * detached from the scheduler -- in this case si == NULL and we * should not enqueue. */ struct dn_queue { struct dn_flow ni; /* oid, flow_id, stats */ struct mq mq; /* packets queue */ struct dn_sch_inst *_si; /* owner scheduler instance */ SLIST_ENTRY(dn_queue) q_next; /* hash chain list for qht */ struct dn_fsk *fs; /* parent flowset. */ /* RED parameters */ int avg; /* average queue length est. (scaled) */ int count; /* arrivals since last RED drop */ int random; /* random value (scaled) */ uint64_t q_time; /* start of queue idle time */ #ifdef NEW_AQM void *aqm_status; /* per-queue status variables*/ #endif }; /* * The kernel side of a scheduler. Contains the userland config, * a link, pointer to extra config arguments from command line, * kernel flags, and a pointer to the scheduler methods. * It is stored in a hash table, and holds a list of all * flowsets and scheduler instances. * XXX sch must be at the beginning, see schk_hash(). */ struct dn_schk { struct dn_sch sch; struct dn_alg *fp; /* Pointer to scheduler functions */ struct dn_link link; /* The link, embedded */ struct dn_profile *profile; /* delay profile, if any */ struct dn_id *cfg; /* extra config arguments */ SLIST_ENTRY(dn_schk) schk_next; /* hash chain for schedhash */ struct dn_fsk_head fsk_list; /* all fsk linked to me */ struct dn_fsk *fs; /* Flowset for !MULTIQUEUE */ /* bucket index used by the drain routine to drain the scheduler * instance for this flowset. */ int drain_bucket; /* Hash table of all instances (through sch.sched_mask) * or single instance if no mask. Always valid. */ struct dn_ht *siht; }; /* * Scheduler instance. * Contains variables and all queues relative to a this instance. * This struct is created a runtime. */ struct dn_sch_inst { struct dn_flow ni; /* oid, flowid and stats */ SLIST_ENTRY(dn_sch_inst) si_next; /* hash chain for siht */ struct delay_line dline; struct dn_schk *sched; /* the template */ int kflags; /* DN_ACTIVE */ int64_t credit; /* bits I can transmit (more or less). */ uint64_t sched_time; /* time link was scheduled in ready_heap */ uint64_t idle_time; /* start of scheduler instance idle time */ /* q_count is the number of queues that this instance is using. * The counter is incremented or decremented when * a reference from the queue is created or deleted. * It is used to make sure that a scheduler instance can be safely * deleted by the drain routine. See notes below. */ int q_count; }; /* * NOTE about object drain. * The system will automatically (XXX check when) drain queues and * scheduler instances when they are idle. * A queue is idle when it has no packets; an instance is idle when * it is not in the evheap heap, and the corresponding delay line is empty. * A queue can be safely deleted when it is idle because of the scheduler * function xxx_free_queue() will remove any references to it. * An instance can be only deleted when no queues reference it. To be sure * of that, a counter (q_count) stores the number of queues that are pointing * to the instance. * * XXX * Order of scan: * - take all flowset in a bucket for the flowset hash table * - take all queues in a bucket for the flowset * - increment the queue bucket * - scan next flowset bucket * Nothing is done if a bucket contains no entries. * * The same schema is used for sceduler instances */ /* kernel-side flags. Linux has DN_DELETE in fcntl.h */ enum { /* 1 and 2 are reserved for the SCAN flags */ DN_DESTROY = 0x0004, /* destroy */ DN_DELETE_FS = 0x0008, /* destroy flowset */ DN_DETACH = 0x0010, DN_ACTIVE = 0x0020, /* object is in evheap */ DN_F_DLINE = 0x0040, /* object is a delay line */ DN_DEL_SAFE = 0x0080, /* delete a queue only if no longer needed * by scheduler */ DN_QHT_IS_Q = 0x0100, /* in flowset, qht is a single queue */ }; /* * Packets processed by dummynet have an mbuf tag associated with * them that carries their dummynet state. * Outside dummynet, only the 'rule' field is relevant, and it must * be at the beginning of the structure. */ struct dn_pkt_tag { struct ipfw_rule_ref rule; /* matching rule */ /* second part, dummynet specific */ int dn_dir; /* action when packet comes out.*/ /* see ip_fw_private.h */ uint64_t output_time; /* when the pkt is due for delivery*/ struct ifnet *ifp; /* interface, for ip_output */ struct _ip6dn_args ip6opt; /* XXX ipv6 options */ uint16_t iphdr_off; /* IP header offset for mtodo() */ }; /* * Possible values for dn_dir. XXXGL: this needs to be reviewed * and converted to same values ip_fw_args.flags use. */ enum { DIR_OUT = 0, DIR_IN = 1, DIR_FWD = 2, DIR_DROP = 3, PROTO_LAYER2 = 0x4, /* set for layer 2 */ PROTO_IPV4 = 0x08, PROTO_IPV6 = 0x10, PROTO_IFB = 0x0c, /* layer2 + ifbridge */ }; extern struct dn_parms dn_cfg; //VNET_DECLARE(struct dn_parms, _base_dn_cfg); //#define dn_cfg VNET(_base_dn_cfg) int dummynet_io(struct mbuf **, struct ip_fw_args *); void dummynet_task(void *context, int pending); void dn_reschedule(void); struct dn_pkt_tag * dn_tag_get(struct mbuf *m); struct dn_queue *ipdn_q_find(struct dn_fsk *, struct dn_sch_inst *, struct ipfw_flow_id *); struct dn_sch_inst *ipdn_si_find(struct dn_schk *, struct ipfw_flow_id *); /* * copy_range is a template for requests for ranges of pipes/queues/scheds. * The number of ranges is variable and can be derived by o.len. * As a default, we use a small number of entries so that the struct * fits easily on the stack and is sufficient for most common requests. */ #define DEFAULT_RANGES 5 struct copy_range { struct dn_id o; uint32_t r[ 2 * DEFAULT_RANGES ]; }; struct copy_args { char **start; char *end; int flags; int type; struct copy_range *extra; /* extra filtering */ }; struct sockopt; int ip_dummynet_compat(struct sockopt *sopt); int dummynet_get(struct sockopt *sopt, void **compat); int dn_c_copy_q (void *_ni, void *arg); int dn_c_copy_pipe(struct dn_schk *s, struct copy_args *a, int nq); int dn_c_copy_fs(struct dn_fsk *f, struct copy_args *a, int nq); int dn_compat_copy_queue(struct copy_args *a, void *_o); int dn_compat_copy_pipe(struct copy_args *a, void *_o); int copy_data_helper_compat(void *_o, void *_arg); int dn_compat_calc_size(void); int do_config(void *p, int l); /* function to drain idle object */ void dn_drain_scheduler(void); void dn_drain_queue(void); #ifdef NEW_AQM int ecn_mark(struct mbuf* m); /* moved from ip_dn_io.c to here to be available for AQMs modules*/ static inline void mq_append(struct mq *q, struct mbuf *m) { #ifdef USERSPACE // buffers from netmap need to be copied // XXX note that the routine is not expected to fail ND("append %p to %p", m, q); if (m->m_flags & M_STACK) { struct mbuf *m_new; void *p; int l, ofs; ofs = m->m_data - m->__m_extbuf; // XXX allocate MGETHDR(m_new, M_NOWAIT, MT_DATA); ND("*** WARNING, volatile buf %p ext %p %d dofs %d m_new %p", m, m->__m_extbuf, m->__m_extlen, ofs, m_new); p = m_new->__m_extbuf; /* new pointer */ l = m_new->__m_extlen; /* new len */ if (l <= m->__m_extlen) { panic("extlen too large"); } *m_new = *m; // copy m_new->m_flags &= ~M_STACK; m_new->__m_extbuf = p; // point to new buffer _pkt_copy(m->__m_extbuf, p, m->__m_extlen); m_new->m_data = p + ofs; m = m_new; } #endif /* USERSPACE */ if (q->head == NULL) q->head = m; else q->tail->m_nextpkt = m; q->count++; q->tail = m; m->m_nextpkt = NULL; } #endif /* NEW_AQM */ #endif /* _IP_DN_PRIVATE_H */