NetBSD/sys/altq/altq_wfq.c

743 lines
16 KiB
C

/* $NetBSD: altq_wfq.c,v 1.23 2021/09/21 14:30:15 christos Exp $ */
/* $KAME: altq_wfq.c,v 1.14 2005/04/13 03:44:25 suz Exp $ */
/*
* Copyright (C) 1997-2002
* Sony Computer Science Laboratories Inc. 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 SONY CSL 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 SONY CSL 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.
*/
/*
* March 27, 1997. Written by Hiroshi Kyusojin of Keio University
* (kyu@mt.cs.keio.ac.jp).
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: altq_wfq.c,v 1.23 2021/09/21 14:30:15 christos Exp $");
#ifdef _KERNEL_OPT
#include "opt_altq.h"
#include "opt_inet.h"
#endif
#ifdef ALTQ_WFQ
#include <sys/param.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/uio.h>
#include <sys/socket.h>
#include <sys/systm.h>
#include <sys/proc.h>
#include <sys/errno.h>
#include <sys/time.h>
#include <sys/kernel.h>
#include <sys/kauth.h>
#include <net/if.h>
#include <net/if_types.h>
#include <netinet/in.h>
#include <altq/altq.h>
#include <altq/altq_conf.h>
#include <altq/altq_wfq.h>
#ifdef ALTQ3_COMPAT
/*
#define WFQ_DEBUG
*/
static int wfq_setenable(struct wfq_interface *, int);
static int wfq_ifattach(struct wfq_interface *);
static int wfq_ifdetach(struct wfq_interface *);
static int wfq_ifenqueue(struct ifaltq *, struct mbuf *);
static u_long wfq_hash(struct flowinfo *, int);
static inline u_long wfq_hashbydstaddr(struct flowinfo *, int);
static inline u_long wfq_hashbysrcaddr(struct flowinfo *, int);
static inline u_long wfq_hashbysrcport(struct flowinfo *, int);
static wfq *wfq_maxqueue(wfq_state_t *);
static struct mbuf *wfq_ifdequeue(struct ifaltq *, int);
static int wfq_getqid(struct wfq_getqid *);
static int wfq_setweight(struct wfq_setweight *);
static int wfq_getstats(struct wfq_getstats *);
static int wfq_config(struct wfq_conf *);
static int wfq_request(struct ifaltq *, int, void *);
static int wfq_flush(struct ifaltq *);
static void *wfq_classify(void *, struct mbuf *, int);
/* global value : pointer to wfq queue list */
static wfq_state_t *wfq_list = NULL;
static int
wfq_setenable(struct wfq_interface *ifacep, int flag)
{
wfq_state_t *wfqp;
int error = 0;
if ((wfqp = altq_lookup(ifacep->wfq_ifacename, ALTQT_WFQ)) == NULL)
return (EBADF);
switch(flag){
case ENABLE:
error = altq_enable(wfqp->ifq);
break;
case DISABLE:
error = altq_disable(wfqp->ifq);
break;
}
return error;
}
static int
wfq_ifattach(struct wfq_interface *ifacep)
{
int error = 0, i;
struct ifnet *ifp;
wfq_state_t *new_wfqp;
wfq *queue;
if ((ifp = ifunit(ifacep->wfq_ifacename)) == NULL) {
#ifdef WFQ_DEBUG
printf("wfq_ifattach()...no ifp found\n");
#endif
return (ENXIO);
}
if (!ALTQ_IS_READY(&ifp->if_snd)) {
#ifdef WFQ_DEBUG
printf("wfq_ifattach()...altq is not ready\n");
#endif
return (ENXIO);
}
/* allocate and initialize wfq_state_t */
new_wfqp = malloc(sizeof(wfq_state_t), M_DEVBUF, M_WAITOK|M_ZERO);
if (new_wfqp == NULL)
return (ENOMEM);
queue = malloc(sizeof(wfq) * DEFAULT_QSIZE, M_DEVBUF, M_WAITOK|M_ZERO);
if (queue == NULL) {
free(new_wfqp, M_DEVBUF);
return (ENOMEM);
}
/* keep the ifq */
new_wfqp->ifq = &ifp->if_snd;
new_wfqp->nums = DEFAULT_QSIZE;
new_wfqp->hwm = HWM;
new_wfqp->bytes = 0;
new_wfqp->rrp = NULL;
new_wfqp->queue = queue;
new_wfqp->hash_func = wfq_hashbydstaddr;
new_wfqp->fbmask = FIMB4_DADDR;
for (i = 0; i < new_wfqp->nums; i++, queue++) {
queue->next = queue->prev = NULL;
queue->head = queue->tail = NULL;
queue->bytes = queue->quota = 0;
queue->weight = 100;
}
/*
* set WFQ to this ifnet structure.
*/
if ((error = altq_attach(&ifp->if_snd, ALTQT_WFQ, new_wfqp,
wfq_ifenqueue, wfq_ifdequeue, wfq_request,
new_wfqp, wfq_classify)) != 0) {
free(queue, M_DEVBUF);
free(new_wfqp, M_DEVBUF);
return (error);
}
new_wfqp->next = wfq_list;
wfq_list = new_wfqp;
return (error);
}
static int
wfq_ifdetach(struct wfq_interface *ifacep)
{
int error = 0;
wfq_state_t *wfqp;
if ((wfqp = altq_lookup(ifacep->wfq_ifacename, ALTQT_WFQ)) == NULL)
return (EBADF);
/* free queued mbuf */
wfq_flush(wfqp->ifq);
/* remove WFQ from the ifnet structure. */
(void)altq_disable(wfqp->ifq);
(void)altq_detach(wfqp->ifq);
/* remove from the wfqstate list */
if (wfq_list == wfqp)
wfq_list = wfqp->next;
else {
wfq_state_t *wp = wfq_list;
do {
if (wp->next == wfqp) {
wp->next = wfqp->next;
break;
}
} while ((wp = wp->next) != NULL);
}
/* deallocate wfq_state_t */
free(wfqp->queue, M_DEVBUF);
free(wfqp, M_DEVBUF);
return (error);
}
static int
wfq_request(struct ifaltq *ifq, int req, void *arg)
{
wfq_state_t *wfqp = (wfq_state_t *)ifq->altq_disc;
switch (req) {
case ALTRQ_PURGE:
wfq_flush(wfqp->ifq);
break;
}
return (0);
}
static int
wfq_flush(struct ifaltq *ifq)
{
struct mbuf *mp;
while ((mp = wfq_ifdequeue(ifq, ALTDQ_REMOVE)) != NULL)
m_freem(mp);
if (ALTQ_IS_ENABLED(ifq))
ifq->ifq_len = 0;
return 0;
}
static void *
wfq_classify(void *clfier, struct mbuf *m, int af)
{
wfq_state_t *wfqp = (wfq_state_t *)clfier;
struct flowinfo flow;
altq_extractflow(m, af, &flow, wfqp->fbmask);
return (&wfqp->queue[(*wfqp->hash_func)(&flow, wfqp->nums)]);
}
static int
wfq_ifenqueue(struct ifaltq *ifq, struct mbuf *mp)
{
wfq_state_t *wfqp;
wfq *queue;
int byte, error = 0;
wfqp = (wfq_state_t *)ifq->altq_disc;
mp->m_nextpkt = NULL;
/* grab a queue selected by classifier */
if ((queue = mp->m_pkthdr.pattr_class) == NULL)
queue = &wfqp->queue[0];
if (queue->tail == NULL)
queue->head = mp;
else
queue->tail->m_nextpkt = mp;
queue->tail = mp;
byte = mp->m_pkthdr.len;
queue->bytes += byte;
wfqp->bytes += byte;
ifq->ifq_len++;
if (queue->next == NULL) {
/* this queue gets active. add the queue to the active list */
if (wfqp->rrp == NULL){
/* no queue in the active list */
queue->next = queue->prev = queue;
wfqp->rrp = queue;
WFQ_ADDQUOTA(queue);
} else {
/* insert the queue at the tail of the active list */
queue->prev = wfqp->rrp->prev;
wfqp->rrp->prev->next = queue;
wfqp->rrp->prev = queue;
queue->next = wfqp->rrp;
queue->quota = 0;
}
}
/* check overflow. if the total size exceeds the high water mark,
drop packets from the longest queue. */
while (wfqp->bytes > wfqp->hwm) {
wfq *drop_queue = wfq_maxqueue(wfqp);
/* drop the packet at the head. */
mp = drop_queue->head;
if ((drop_queue->head = mp->m_nextpkt) == NULL)
drop_queue->tail = NULL;
mp->m_nextpkt = NULL;
byte = mp->m_pkthdr.len;
drop_queue->bytes -= byte;
PKTCNTR_ADD(&drop_queue->drop_cnt, byte);
wfqp->bytes -= byte;
m_freem(mp);
ifq->ifq_len--;
if(drop_queue == queue)
/* the queue for this flow is selected to drop */
error = ENOBUFS;
}
return error;
}
static u_long
wfq_hash(struct flowinfo *flow, int n)
{
u_long val = 0;
if (flow != NULL) {
if (flow->fi_family == AF_INET) {
struct flowinfo_in *fp = (struct flowinfo_in *)flow;
u_long val2;
val = fp->fi_dst.s_addr ^ fp->fi_src.s_addr;
val = val ^ (val >> 8) ^ (val >> 16) ^ (val >> 24);
val2 = fp->fi_dport ^ fp->fi_sport ^ fp->fi_proto;
val2 = val2 ^ (val2 >> 8);
val = val ^ val2;
}
#ifdef INET6
else if (flow->fi_family == AF_INET6) {
struct flowinfo_in6 *fp6 = (struct flowinfo_in6 *)flow;
val = ntohl(fp6->fi6_flowlabel);
}
#endif
}
return (val % n);
}
static inline u_long
wfq_hashbydstaddr(struct flowinfo *flow, int n)
{
u_long val = 0;
if (flow != NULL) {
if (flow->fi_family == AF_INET) {
struct flowinfo_in *fp = (struct flowinfo_in *)flow;
val = fp->fi_dst.s_addr;
val = val ^ (val >> 8) ^ (val >> 16) ^ (val >> 24);
}
#ifdef INET6
else if (flow->fi_family == AF_INET6) {
struct flowinfo_in6 *fp6 = (struct flowinfo_in6 *)flow;
val = ntohl(fp6->fi6_flowlabel);
}
#endif
}
return (val % n);
}
static inline u_long
wfq_hashbysrcaddr(struct flowinfo *flow, int n)
{
u_long val = 0;
if (flow != NULL) {
if (flow->fi_family == AF_INET) {
struct flowinfo_in *fp = (struct flowinfo_in *)flow;
val = fp->fi_src.s_addr;
val = val ^ (val >> 8) ^ (val >> 16) ^ (val >> 24);
}
#ifdef INET6
else if (flow->fi_family == AF_INET6) {
struct flowinfo_in6 *fp6 = (struct flowinfo_in6 *)flow;
val = ntohl(fp6->fi6_flowlabel);
}
#endif
}
return (val % n);
}
static inline u_long
wfq_hashbysrcport(struct flowinfo *flow, int n)
{
u_long val = 0;
if (flow != NULL) {
if (flow->fi_family == AF_INET) {
struct flowinfo_in *fp = (struct flowinfo_in *)flow;
val = fp->fi_sport;
}
#ifdef INET6
else if (flow->fi_family == AF_INET6) {
struct flowinfo_in6 *fp6 = (struct flowinfo_in6 *)flow;
val = fp6->fi6_sport;
}
#endif
}
val = val ^ (val >> 8);
return (val % n);
}
static wfq *
wfq_maxqueue(wfq_state_t *wfqp)
{
int byte, max_byte = 0;
wfq *queue, *max_queue = NULL;
if((queue = wfqp->rrp) == NULL)
/* never happens */
return NULL;
do{
if ((byte = queue->bytes * 100 / queue->weight) > max_byte) {
max_queue = queue;
max_byte = byte;
}
} while ((queue = queue->next) != wfqp->rrp);
return max_queue;
}
static struct mbuf *
wfq_ifdequeue(struct ifaltq *ifq, int op)
{
wfq_state_t *wfqp;
wfq *queue;
struct mbuf *mp;
int byte;
wfqp = (wfq_state_t *)ifq->altq_disc;
if ((wfqp->bytes == 0) || ((queue = wfqp->rrp) == NULL))
/* no packet in the queues */
return NULL;
while (1) {
if (queue->quota > 0) {
if (queue->bytes <= 0) {
/* this queue no longer has packet.
remove the queue from the active list. */
if (queue->next == queue){
/* no other active queue
-- this case never happens in
this algorithm. */
queue->next = queue->prev = NULL;
wfqp->rrp = NULL;
return NULL;
} else {
queue->prev->next = queue->next;
queue->next->prev = queue->prev;
/* the round-robin pointer points
to this queue, advance the rrp */
wfqp->rrp = queue->next;
queue->next = queue->prev = NULL;
queue = wfqp->rrp;
WFQ_ADDQUOTA(queue);
continue;
}
}
/* dequeue a packet from this queue */
mp = queue->head;
if (op == ALTDQ_REMOVE) {
if((queue->head = mp->m_nextpkt) == NULL)
queue->tail = NULL;
byte = mp->m_pkthdr.len;
mp->m_nextpkt = NULL;
queue->quota -= byte;
queue->bytes -= byte;
PKTCNTR_ADD(&queue->xmit_cnt, byte);
wfqp->bytes -= byte;
if (ALTQ_IS_ENABLED(ifq))
ifq->ifq_len--;
}
return mp;
/* if the queue gets empty by this dequeueing,
the queue will be removed from the active list
at the next round */
}
/* advance the round-robin pointer */
queue = wfqp->rrp = queue->next;
WFQ_ADDQUOTA(queue);
}
}
static int
wfq_getqid(struct wfq_getqid *gqidp)
{
wfq_state_t *wfqp;
if ((wfqp = altq_lookup(gqidp->iface.wfq_ifacename, ALTQT_WFQ))
== NULL)
return (EBADF);
gqidp->qid = (*wfqp->hash_func)(&gqidp->flow, wfqp->nums);
return 0;
}
static int
wfq_setweight(struct wfq_setweight *swp)
{
wfq_state_t *wfqp;
wfq *queue;
int old;
if (swp->weight < 0)
return (EINVAL);
if ((wfqp = altq_lookup(swp->iface.wfq_ifacename, ALTQT_WFQ)) == NULL)
return (EBADF);
if (swp->qid < 0 || swp->qid >= wfqp->nums)
return (EINVAL);
queue = &wfqp->queue[swp->qid];
old = queue->weight;
queue->weight = swp->weight;
swp->weight = old;
return 0;
}
static int
wfq_getstats(struct wfq_getstats *gsp)
{
wfq_state_t *wfqp;
wfq *queue;
queue_stats *stats;
if ((wfqp = altq_lookup(gsp->iface.wfq_ifacename, ALTQT_WFQ)) == NULL)
return (EBADF);
if (gsp->qid < 0 || gsp->qid >= wfqp->nums)
return (EINVAL);
queue = &wfqp->queue[gsp->qid];
stats = &gsp->stats;
stats->bytes = queue->bytes;
stats->weight = queue->weight;
stats->xmit_cnt = queue->xmit_cnt;
stats->drop_cnt = queue->drop_cnt;
return 0;
}
static int
wfq_config(struct wfq_conf *cf)
{
wfq_state_t *wfqp;
wfq *queue;
int i, error = 0;
if ((wfqp = altq_lookup(cf->iface.wfq_ifacename, ALTQT_WFQ)) == NULL)
return (EBADF);
if(cf->nqueues <= 0 || MAX_QSIZE < cf->nqueues)
cf->nqueues = DEFAULT_QSIZE;
if (cf->nqueues != wfqp->nums) {
/* free queued mbuf */
wfq_flush(wfqp->ifq);
free(wfqp->queue, M_DEVBUF);
queue = malloc(sizeof(wfq) * cf->nqueues, M_DEVBUF,
M_WAITOK|M_ZERO);
if (queue == NULL)
return (ENOMEM);
wfqp->nums = cf->nqueues;
wfqp->bytes = 0;
wfqp->rrp = NULL;
wfqp->queue = queue;
for (i = 0; i < wfqp->nums; i++, queue++) {
queue->next = queue->prev = NULL;
queue->head = queue->tail = NULL;
queue->bytes = queue->quota = 0;
queue->weight = 100;
}
}
if (cf->qlimit != 0)
wfqp->hwm = cf->qlimit;
switch (cf->hash_policy) {
case WFQ_HASH_DSTADDR:
wfqp->hash_func = wfq_hashbydstaddr;
wfqp->fbmask = FIMB4_DADDR;
#ifdef INET6
wfqp->fbmask |= FIMB6_FLABEL; /* use flowlabel for ipv6 */
#endif
break;
case WFQ_HASH_SRCPORT:
wfqp->hash_func = wfq_hashbysrcport;
wfqp->fbmask = FIMB4_SPORT;
#ifdef INET6
wfqp->fbmask |= FIMB6_SPORT;
#endif
break;
case WFQ_HASH_FULL:
wfqp->hash_func = wfq_hash;
wfqp->fbmask = FIMB4_ALL;
#ifdef INET6
wfqp->fbmask |= FIMB6_FLABEL; /* use flowlabel for ipv6 */
#endif
break;
case WFQ_HASH_SRCADDR:
wfqp->hash_func = wfq_hashbysrcaddr;
wfqp->fbmask = FIMB4_DADDR;
#ifdef INET6
wfqp->fbmask |= FIMB6_FLABEL; /* use flowlabel for ipv6 */
#endif
break;
default:
error = EINVAL;
break;
}
return error;
}
/*
* wfq device interface
*/
altqdev_decl(wfq);
int
wfqopen(dev_t dev, int flag, int fmt,
struct lwp *l)
{
return 0;
}
int
wfqclose(dev_t dev, int flag, int fmt,
struct lwp *l)
{
struct ifnet *ifp;
struct wfq_interface iface;
wfq_state_t *wfqp;
int s;
s = splnet();
while ((wfqp = wfq_list) != NULL) {
ifp = wfqp->ifq->altq_ifp;
snprintf(iface.wfq_ifacename, sizeof(iface.wfq_ifacename),
"%s", ifp->if_xname);
wfq_ifdetach(&iface);
}
splx(s);
return 0;
}
int
wfqioctl(dev_t dev, ioctlcmd_t cmd, void *addr, int flag,
struct lwp *l)
{
int error = 0;
int s;
/* check cmd for superuser only */
switch (cmd) {
case WFQ_GET_QID:
case WFQ_GET_STATS:
break;
default:
if ((error = kauth_authorize_network(l->l_cred,
KAUTH_NETWORK_ALTQ, KAUTH_REQ_NETWORK_ALTQ_WFQ, NULL,
NULL, NULL)) != 0)
return (error);
break;
}
s = splnet();
switch (cmd) {
case WFQ_ENABLE:
error = wfq_setenable((struct wfq_interface *)addr, ENABLE);
break;
case WFQ_DISABLE:
error = wfq_setenable((struct wfq_interface *)addr, DISABLE);
break;
case WFQ_IF_ATTACH:
error = wfq_ifattach((struct wfq_interface *)addr);
break;
case WFQ_IF_DETACH:
error = wfq_ifdetach((struct wfq_interface *)addr);
break;
case WFQ_GET_QID:
error = wfq_getqid((struct wfq_getqid *)addr);
break;
case WFQ_SET_WEIGHT:
error = wfq_setweight((struct wfq_setweight *)addr);
break;
case WFQ_GET_STATS:
error = wfq_getstats((struct wfq_getstats *)addr);
break;
case WFQ_CONFIG:
error = wfq_config((struct wfq_conf *)addr);
break;
default:
error = EINVAL;
break;
}
splx(s);
return error;
}
#ifdef KLD_MODULE
static struct altqsw wfq_sw =
{"wfq", wfqopen, wfqclose, wfqioctl};
ALTQ_MODULE(altq_wfq, ALTQT_WFQ, &wfq_sw);
#endif /* KLD_MODULE */
#endif /* ALTQ3_COMPAT */
#endif /* ALTQ_WFQ */