M1: compat layer + pf.ko skeleton builds against Linux 7.3.4

- pfk.h: BSD types/ioctl encoding (OpenBSD bit layout), locks (NET_LOCK=rwsem),
  pools, timeouts, mbuf shim over sk_buff, ifnet registry, toeplitz/siphash
- pfk_mod/pfk_hooks: misc /dev/pf (0600), IPv4 netfilter hook skeleton,
  DIOCGETSTATUS answered (M1 skeleton; engine ioctls land with pf_ioctl.c)
- vendorize/stage-port scripts: deterministic upstream staging with seds
  (struct renames, IFF_*->PFIFF_*, sa_len drops) logged to docs/porting-notes
- fixes: modules_prepare+Module.symvers for out-of-tree builds
This commit is contained in:
lgcosta committed 2026-11-11 12:09:34 -03:00
1 parent ba81284bd3
commit da2e9b679a
87 files changed
+47256 -3

No files matched your search

File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+871
View File
@@ -0,0 +1,871 @@
/* PFLINUX port of OpenBSD 8.0 net/fq_codel.c — see docs/porting-notes.md */
#include <pfk.h>
/* #define FQCODEL_DEBUG 1 */
#ifdef FQCODEL_DEBUG
#define DPRINTF(x...) printf(x)
#else
#define DPRINTF(x...)
#endif
struct codel {
struct mbuf_list q;
unsigned int dropping:1; /* Dropping state */
unsigned int backlog:31; /* Number of bytes in the queue */
unsigned short drops; /* Free running counter of drops */
unsigned short ldrops; /* Value from the previous run */
int64_t start; /* The moment queue was above target */
int64_t next; /* Next interval */
int64_t delay; /* Delay incurred by the last packet */
};
struct codel_params {
int64_t target;
int64_t interval;
int64_t grace;
int quantum;
uint32_t *intervals;
};
void codel_initparams(struct codel_params *, unsigned int,
unsigned int, int);
void codel_freeparams(struct codel_params *);
void codel_enqueue(struct codel *, int64_t, struct mbuf *);
struct mbuf *codel_dequeue(struct codel *, struct codel_params *, int64_t,
struct mbuf_list *, uint64_t *, uint64_t *);
struct mbuf *codel_commit(struct codel *, struct mbuf *);
void codel_purge(struct codel *, struct mbuf_list *);
struct flow {
struct codel cd;
int active:1;
int deficit:31;
#ifdef FQCODEL_DEBUG
uint16_t id;
#endif
SIMPLEQ_ENTRY(flow) flowentry;
};
SIMPLEQ_HEAD(flowq, flow);
struct fqcodel {
struct flowq newq;
struct flowq oldq;
struct flow *flows;
unsigned int qlength;
struct ifnet *ifp;
struct codel_params cparams;
unsigned int nflows;
unsigned int qlimit;
int quantum;
unsigned int flags;
#define FQCF_FIXED_QUANTUM 0x1
/* stats */
struct fqcodel_pktcntr xmit_cnt;
struct fqcodel_pktcntr drop_cnt;
struct mbuf_list pending_drops;
};
unsigned int fqcodel_idx(unsigned int, const struct mbuf *);
void *fqcodel_alloc(unsigned int, void *);
void fqcodel_free(unsigned int, void *);
struct mbuf *fqcodel_if_enq(struct ifqueue *, struct mbuf *);
struct mbuf *fqcodel_if_deq_begin(struct ifqueue *, void **);
void fqcodel_if_deq_commit(struct ifqueue *, struct mbuf *, void *);
void fqcodel_if_purge(struct ifqueue *, struct mbuf_list *);
struct mbuf *fqcodel_enq(struct fqcodel *, struct mbuf *);
struct mbuf *fqcodel_deq_begin(struct fqcodel *, void **,
struct mbuf_list *);
void fqcodel_deq_commit(struct fqcodel *, struct mbuf *, void *);
void fqcodel_purge(struct fqcodel *, struct mbuf_list *);
/*
* ifqueue glue.
*/
static const struct ifq_ops fqcodel_ops = {
fqcodel_idx,
fqcodel_if_enq,
fqcodel_if_deq_begin,
fqcodel_if_deq_commit,
fqcodel_if_purge,
fqcodel_alloc,
fqcodel_free
};
const struct ifq_ops *const ifq_fqcodel_ops = &fqcodel_ops;
void *fqcodel_pf_alloc(struct ifnet *);
int fqcodel_pf_addqueue(void *, struct pf_queuespec *);
void fqcodel_pf_free(void *);
int fqcodel_pf_qstats(struct pf_queuespec *, void *, int *);
unsigned int fqcodel_pf_qlength(void *);
struct mbuf *fqcodel_pf_enqueue(void *, struct mbuf *);
struct mbuf *fqcodel_pf_deq_begin(void *, void **, struct mbuf_list *);
void fqcodel_pf_deq_commit(void *, struct mbuf *, void *);
void fqcodel_pf_purge(void *, struct mbuf_list *);
/*
* pf queue glue.
*/
static const struct pfq_ops fqcodel_pf_ops = {
fqcodel_pf_alloc,
fqcodel_pf_addqueue,
fqcodel_pf_free,
fqcodel_pf_qstats,
fqcodel_pf_qlength,
fqcodel_pf_enqueue,
fqcodel_pf_deq_begin,
fqcodel_pf_deq_commit,
fqcodel_pf_purge
};
const struct pfq_ops *const pfq_fqcodel_ops = &fqcodel_pf_ops;
/* Default aggregate queue depth */
static const unsigned int fqcodel_qlimit = 1024;
/*
* CoDel implementation
*/
/* Delay target, 5ms */
static const int64_t codel_target = 5000000;
/* First 399 "100 / sqrt(x)" intervals, ns precision */
static const uint32_t codel_intervals[] = {
100000000, 70710678, 57735027, 50000000, 44721360, 40824829, 37796447,
35355339, 33333333, 31622777, 30151134, 28867513, 27735010, 26726124,
25819889, 25000000, 24253563, 23570226, 22941573, 22360680, 21821789,
21320072, 20851441, 20412415, 20000000, 19611614, 19245009, 18898224,
18569534, 18257419, 17960530, 17677670, 17407766, 17149859, 16903085,
16666667, 16439899, 16222142, 16012815, 15811388, 15617376, 15430335,
15249857, 15075567, 14907120, 14744196, 14586499, 14433757, 14285714,
14142136, 14002801, 13867505, 13736056, 13608276, 13483997, 13363062,
13245324, 13130643, 13018891, 12909944, 12803688, 12700013, 12598816,
12500000, 12403473, 12309149, 12216944, 12126781, 12038585, 11952286,
11867817, 11785113, 11704115, 11624764, 11547005, 11470787, 11396058,
11322770, 11250879, 11180340, 11111111, 11043153, 10976426, 10910895,
10846523, 10783277, 10721125, 10660036, 10599979, 10540926, 10482848,
10425721, 10369517, 10314212, 10259784, 10206207, 10153462, 10101525,
10050378, 10000000, 9950372, 9901475, 9853293, 9805807, 9759001,
9712859, 9667365, 9622504, 9578263, 9534626, 9491580, 9449112,
9407209, 9365858, 9325048, 9284767, 9245003, 9205746, 9166985,
9128709, 9090909, 9053575, 9016696, 8980265, 8944272, 8908708,
8873565, 8838835, 8804509, 8770580, 8737041, 8703883, 8671100,
8638684, 8606630, 8574929, 8543577, 8512565, 8481889, 8451543,
8421519, 8391814, 8362420, 8333333, 8304548, 8276059, 8247861,
8219949, 8192319, 8164966, 8137885, 8111071, 8084521, 8058230,
8032193, 8006408, 7980869, 7955573, 7930516, 7905694, 7881104,
7856742, 7832604, 7808688, 7784989, 7761505, 7738232, 7715167,
7692308, 7669650, 7647191, 7624929, 7602859, 7580980, 7559289,
7537784, 7516460, 7495317, 7474351, 7453560, 7432941, 7412493,
7392213, 7372098, 7352146, 7332356, 7312724, 7293250, 7273930,
7254763, 7235746, 7216878, 7198158, 7179582, 7161149, 7142857,
7124705, 7106691, 7088812, 7071068, 7053456, 7035975, 7018624,
7001400, 6984303, 6967330, 6950480, 6933752, 6917145, 6900656,
6884284, 6868028, 6851887, 6835859, 6819943, 6804138, 6788442,
6772855, 6757374, 6741999, 6726728, 6711561, 6696495, 6681531,
6666667, 6651901, 6637233, 6622662, 6608186, 6593805, 6579517,
6565322, 6551218, 6537205, 6523281, 6509446, 6495698, 6482037,
6468462, 6454972, 6441566, 6428243, 6415003, 6401844, 6388766,
6375767, 6362848, 6350006, 6337243, 6324555, 6311944, 6299408,
6286946, 6274558, 6262243, 6250000, 6237829, 6225728, 6213698,
6201737, 6189845, 6178021, 6166264, 6154575, 6142951, 6131393,
6119901, 6108472, 6097108, 6085806, 6074567, 6063391, 6052275,
6041221, 6030227, 6019293, 6008418, 5997601, 5986843, 5976143,
5965500, 5954913, 5944383, 5933908, 5923489, 5913124, 5902813,
5892557, 5882353, 5872202, 5862104, 5852057, 5842062, 5832118,
5822225, 5812382, 5802589, 5792844, 5783149, 5773503, 5763904,
5754353, 5744850, 5735393, 5725983, 5716620, 5707301, 5698029,
5688801, 5679618, 5670480, 5661385, 5652334, 5643326, 5634362,
5625440, 5616560, 5607722, 5598925, 5590170, 5581456, 5572782,
5564149, 5555556, 5547002, 5538488, 5530013, 5521576, 5513178,
5504819, 5496497, 5488213, 5479966, 5471757, 5463584, 5455447,
5447347, 5439283, 5431254, 5423261, 5415304, 5407381, 5399492,
5391639, 5383819, 5376033, 5368281, 5360563, 5352877, 5345225,
5337605, 5330018, 5322463, 5314940, 5307449, 5299989, 5292561,
5285164, 5277798, 5270463, 5263158, 5255883, 5248639, 5241424,
5234239, 5227084, 5219958, 5212860, 5205792, 5198752, 5191741,
5184758, 5177804, 5170877, 5163978, 5157106, 5150262, 5143445,
5136655, 5129892, 5123155, 5116445, 5109761, 5103104, 5096472,
5089866, 5083286, 5076731, 5070201, 5063697, 5057217, 5050763,
5044333, 5037927, 5031546, 5025189, 5018856, 5012547, 5006262
};
void
codel_initparams(struct codel_params *cp, unsigned int target,
unsigned int interval, int quantum)
{
uint64_t mult;
unsigned int i;
/*
* Update observation intervals table according to the configured
* initial interval value.
*/
if (interval > codel_intervals[0]) {
/*
* Select either specified target or 5% of an interval
* (RFC 8289, section 4.3).
*/
cp->target = MAX(target, interval / 20);
cp->interval = interval;
/* The coefficient is scaled up by a 1000 */
mult = ((uint64_t)cp->interval * 1000) / codel_intervals[0];
/* Prepare table of intervals */
cp->intervals = mallocarray(nitems(codel_intervals),
sizeof(codel_intervals[0]), M_DEVBUF, M_WAITOK | M_ZERO);
for (i = 0; i < nitems(codel_intervals); i++)
cp->intervals[i] = ((uint64_t)codel_intervals[i] *
mult) / 1000;
} else {
cp->target = MAX(target, codel_target);
cp->interval = codel_intervals[0];
cp->intervals = (uint32_t *)codel_intervals;
}
cp->quantum = quantum;
/* Grace period for delta reuse (RFC 8289, section 5.5) */
cp->grace = 16 * cp->interval;
}
void
codel_freeparams(struct codel_params *cp)
{
if (cp->intervals != codel_intervals)
free(cp->intervals, M_DEVBUF, nitems(codel_intervals) *
sizeof(codel_intervals[0]));
cp->intervals = NULL;
}
static inline unsigned int
codel_backlog(struct codel *cd)
{
return (cd->backlog);
}
static inline unsigned int
codel_qlength(struct codel *cd)
{
return (ml_len(&cd->q));
}
static inline int64_t
codel_delay(struct codel *cd)
{
return (cd->delay);
}
void
codel_enqueue(struct codel *cd, int64_t now, struct mbuf *m)
{
m->m_pkthdr.ph_timestamp = now;
ml_enqueue(&cd->q, m);
cd->backlog += m->m_pkthdr.len;
}
/*
* Select the next interval according to the number of drops
* in the current one relative to the provided timestamp.
*/
static inline void
control_law(struct codel *cd, struct codel_params *cp, int64_t rts)
{
unsigned int idx;
KASSERT(cd->drops > 0);
idx = min(cd->drops - 1, nitems(codel_intervals) - 1);
cd->next = rts + cp->intervals[idx];
}
/*
* Pick the next enqueued packet and determine the queueing delay
* as well as whether or not it's a good candidate for dropping
* from the queue.
*
* The decision whether to drop the packet or not is made based
* on the queueing delay target of 5ms and on the current queue
* length in bytes which shouldn't be less than the amount of data
* that arrives in a typical interarrival time (MTU-sized packets
* arriving spaced by the amount of time it takes to send such a
* packet on the bottleneck).
*/
static inline struct mbuf *
codel_next_packet(struct codel *cd, struct codel_params *cp, int64_t now,
int *drop)
{
struct mbuf *m;
*drop = 0;
m = MBUF_LIST_FIRST(&cd->q);
if (m == NULL) {
KASSERT(cd->backlog == 0);
/* Empty queue, reset interval */
cd->start = 0;
return (NULL);
}
KASSERT(cd->backlog >= m->m_pkthdr.len);
if (now - m->m_pkthdr.ph_timestamp < cp->target ||
cd->backlog - m->m_pkthdr.len <= cp->quantum) {
/*
* The minimum delay decreased below the target, reset
* the current observation interval.
*/
cd->start = 0;
return (m);
}
if (cd->start == 0) {
/*
* This is the first packet to be delayed for more than
* the target, start the first observation interval after
* a single RTT and see if the minimum delay goes below
* the target within the interval, otherwise punish the
* next packet.
*/
cd->start = now + cp->interval;
} else if (now >= cd->start) {
*drop = 1;
}
return (m);
}
enum { INITIAL, ACCEPTING, FIRSTDROP, DROPPING, CONTROL, RECOVERY };
static inline int
codel_state_change(struct codel *cd, int64_t now, struct mbuf *m, int drop,
int state)
{
if (state == FIRSTDROP)
return (ACCEPTING);
if (cd->dropping) {
if (!drop)
return (RECOVERY);
else if (now >= cd->next)
return (state == DROPPING ? CONTROL : DROPPING);
} else if (drop)
return (FIRSTDROP);
if (m == NULL)
return (RECOVERY);
return (ACCEPTING);
}
struct mbuf *
codel_dequeue(struct codel *cd, struct codel_params *cp, int64_t now,
struct mbuf_list *free_ml, uint64_t *dpkts, uint64_t *dbytes)
{
struct mbuf *m;
unsigned short delta;
int drop, state, done = 0;
state = INITIAL;
while (!done) {
m = codel_next_packet(cd, cp, now, &drop);
state = codel_state_change(cd, now, m, drop, state);
switch (state) {
case FIRSTDROP:
m = codel_commit(cd, m);
ml_enqueue(free_ml, m);
*dpkts += 1;
*dbytes += m->m_pkthdr.len;
cd->dropping = 1;
/*
* If we're still within the grace period and not
* meeting our minimal delay target we treat this
* as a continuation of the previous observation
* interval and shrink it further. Otherwise we
* start from the initial one.
*/
delta = cd->drops - cd->ldrops;
if (delta > 1 && (now < cd->next ||
now - cd->next < cp->grace))
cd->drops = delta;
else
cd->drops = 1;
control_law(cd, cp, now);
cd->ldrops = cd->drops;
/* fetches the next packet and goes to ACCEPTING */
break;
case DROPPING:
m = codel_commit(cd, m);
ml_enqueue(free_ml, m);
cd->drops++;
*dpkts += 1;
*dbytes += m->m_pkthdr.len;
/* fetches the next packet and goes to CONTROL */
break;
case CONTROL:
if (drop) {
control_law(cd, cp, cd->next);
continue;
}
/* FALLTHROUGH */
case RECOVERY:
cd->dropping = 0;
/* FALLTHROUGH */
case ACCEPTING:
done = 1;
break;
}
}
if (m != NULL)
cd->delay = now - m->m_pkthdr.ph_timestamp;
return (m);
}
struct mbuf *
codel_commit(struct codel *cd, struct mbuf *m)
{
struct mbuf *n;
n = ml_dequeue(&cd->q);
if (m)
KASSERT(n == m);
KASSERT(n != NULL);
KASSERT(cd->backlog >= n->m_pkthdr.len);
cd->backlog -= n->m_pkthdr.len;
return (n);
}
void
codel_purge(struct codel *cd, struct mbuf_list *ml)
{
ml_enlist(ml, &cd->q);
cd->backlog = 0;
}
/*
* FQ-CoDel implementation
*/
static inline struct flow *
classify_flow(struct fqcodel *fqc, struct mbuf *m)
{
unsigned int index = 0;
if (m->m_pkthdr.csum_flags & M_FLOWID)
index = m->m_pkthdr.ph_flowid % fqc->nflows;
DPRINTF("%s: %u\n", __func__, index);
return (&fqc->flows[index]);
}
struct mbuf *
fqcodel_enq(struct fqcodel *fqc, struct mbuf *m)
{
struct flow *flow;
unsigned int backlog = 0;
int64_t now;
int i, ndrop;
flow = classify_flow(fqc, m);
if (flow == NULL)
return (m);
now = nsecuptime();
codel_enqueue(&flow->cd, now, m);
fqc->qlength++;
if (!flow->active) {
SIMPLEQ_INSERT_TAIL(&fqc->newq, flow, flowentry);
flow->deficit = fqc->quantum;
flow->active = 1;
DPRINTF("%s: flow %u active deficit %d\n", __func__,
flow->id, flow->deficit);
}
/*
* Flush pending_drops first to let PF account them individually.
* When batch dropping (below), we queue multiple packets but can
* only return one per enqueue call to maintain the interface
* contract.
*/
if (!ml_empty(&fqc->pending_drops))
return (ml_dequeue(&fqc->pending_drops));
/*
* If total queue length exceeds the limit, find the flow with the
* largest backlog and drop up to half of its packets, with a
* maximum of 64, from the head. Implements RFC 8290, section 4.1
* batch drop to handle overload efficiently. Dropped packets are
* queued in pending_drops.
*/
if (fqc->qlength > fqc->qlimit) {
for (i = 0; i < fqc->nflows; i++) {
if (codel_backlog(&fqc->flows[i].cd) > backlog) {
flow = &fqc->flows[i];
backlog = codel_backlog(&flow->cd);
}
}
KASSERT(flow != NULL);
ndrop = MIN(MAX(codel_qlength(&flow->cd) / 2, 1), 64);
for (i = 0; i < ndrop; i++) {
m = codel_commit(&flow->cd, NULL);
if (m == NULL)
break;
fqc->drop_cnt.packets++;
fqc->drop_cnt.bytes += m->m_pkthdr.len;
fqc->qlength--;
ml_enqueue(&fqc->pending_drops, m);
}
DPRINTF("%s: batch-dropped %d/%d pkts from flow %u\n", __func__,
i, ndrop, flow->id);
return (ml_dequeue(&fqc->pending_drops));
}
return (NULL);
}
static inline struct flowq *
select_queue(struct fqcodel *fqc)
{
struct flowq *fq = NULL;
if (!SIMPLEQ_EMPTY(&fqc->newq))
fq = &fqc->newq;
else if (!SIMPLEQ_EMPTY(&fqc->oldq))
fq = &fqc->oldq;
return (fq);
}
static inline struct flow *
first_flow(struct fqcodel *fqc, struct flowq **fq)
{
struct flow *flow;
while ((*fq = select_queue(fqc)) != NULL) {
while ((flow = SIMPLEQ_FIRST(*fq)) != NULL) {
if (flow->deficit <= 0) {
flow->deficit += fqc->quantum;
SIMPLEQ_REMOVE_HEAD(*fq, flowentry);
SIMPLEQ_INSERT_TAIL(&fqc->oldq, flow,
flowentry);
DPRINTF("%s: flow %u deficit %d\n", __func__,
flow->id, flow->deficit);
} else
return (flow);
}
}
return (NULL);
}
static inline struct flow *
next_flow(struct fqcodel *fqc, struct flow *flow, struct flowq **fq)
{
SIMPLEQ_REMOVE_HEAD(*fq, flowentry);
if (*fq == &fqc->newq && !SIMPLEQ_EMPTY(&fqc->oldq)) {
/* A packet was dropped, starve the queue */
SIMPLEQ_INSERT_TAIL(&fqc->oldq, flow, flowentry);
DPRINTF("%s: flow %u ->oldq deficit %d\n", __func__,
flow->id, flow->deficit);
} else {
/* A packet was dropped on a starved queue, disable it */
flow->active = 0;
DPRINTF("%s: flow %u inactive deficit %d\n", __func__,
flow->id, flow->deficit);
}
return (first_flow(fqc, fq));
}
struct mbuf *
fqcodel_deq_begin(struct fqcodel *fqc, void **cookiep,
struct mbuf_list *free_ml)
{
struct mbuf_list ml = MBUF_LIST_INITIALIZER();
struct flowq *fq;
struct flow *flow;
struct mbuf *m;
int64_t now;
if ((fqc->flags & FQCF_FIXED_QUANTUM) == 0)
fqc->quantum = fqc->ifp->if_mtu + max_linkhdr;
now = nsecuptime();
for (flow = first_flow(fqc, &fq); flow != NULL;
flow = next_flow(fqc, flow, &fq)) {
m = codel_dequeue(&flow->cd, &fqc->cparams, now, &ml,
&fqc->drop_cnt.packets, &fqc->drop_cnt.bytes);
KASSERT(fqc->qlength >= ml_len(&ml));
fqc->qlength -= ml_len(&ml);
ml_enlist(free_ml, &ml);
if (m != NULL) {
flow->deficit -= m->m_pkthdr.len;
DPRINTF("%s: flow %u deficit %d\n", __func__,
flow->id, flow->deficit);
*cookiep = flow;
return (m);
}
}
return (NULL);
}
void
fqcodel_deq_commit(struct fqcodel *fqc, struct mbuf *m, void *cookie)
{
struct flow *flow = cookie;
KASSERT(fqc->qlength > 0);
fqc->qlength--;
fqc->xmit_cnt.packets++;
fqc->xmit_cnt.bytes += m->m_pkthdr.len;
(void)codel_commit(&flow->cd, m);
}
void
fqcodel_purge(struct fqcodel *fqc, struct mbuf_list *ml)
{
unsigned int i;
for (i = 0; i < fqc->nflows; i++)
codel_purge(&fqc->flows[i].cd, ml);
ml_enlist(ml, &fqc->pending_drops);
fqc->qlength = 0;
}
struct mbuf *
fqcodel_if_enq(struct ifqueue *ifq, struct mbuf *m)
{
return fqcodel_enq(ifq->ifq_q, m);
}
struct mbuf *
fqcodel_if_deq_begin(struct ifqueue *ifq, void **cookiep)
{
struct mbuf_list free_ml = MBUF_LIST_INITIALIZER();
struct mbuf *m;
m = fqcodel_deq_begin(ifq->ifq_q, cookiep, &free_ml);
ifq_mfreeml(ifq, &free_ml);
return (m);
}
void
fqcodel_if_deq_commit(struct ifqueue *ifq, struct mbuf *m, void *cookie)
{
return fqcodel_deq_commit(ifq->ifq_q, m, cookie);
}
void
fqcodel_if_purge(struct ifqueue *ifq, struct mbuf_list *ml)
{
return fqcodel_purge(ifq->ifq_q, ml);
}
void *
fqcodel_pf_alloc(struct ifnet *ifp)
{
struct fqcodel *fqc;
fqc = malloc(sizeof(struct fqcodel), M_DEVBUF, M_WAITOK | M_ZERO);
SIMPLEQ_INIT(&fqc->newq);
SIMPLEQ_INIT(&fqc->oldq);
ml_init(&fqc->pending_drops);
return (fqc);
}
int
fqcodel_pf_addqueue(void *arg, struct pf_queuespec *qs)
{
struct ifnet *ifp = qs->kif->pfik_ifp;
struct fqcodel *fqc = arg;
if (qs->flowqueue.flows == 0 || qs->flowqueue.flows > 0xffff)
return (EINVAL);
fqc->nflows = qs->flowqueue.flows;
fqc->quantum = qs->flowqueue.quantum;
if (qs->qlimit > 0)
fqc->qlimit = qs->qlimit;
else
fqc->qlimit = fqcodel_qlimit;
if (fqc->quantum > 0)
fqc->flags |= FQCF_FIXED_QUANTUM;
else
fqc->quantum = ifp->if_mtu + max_linkhdr;
codel_initparams(&fqc->cparams, qs->flowqueue.target,
qs->flowqueue.interval, fqc->quantum);
fqc->flows = mallocarray(fqc->nflows, sizeof(struct flow),
M_DEVBUF, M_WAITOK | M_ZERO);
#ifdef FQCODEL_DEBUG
{
unsigned int i;
for (i = 0; i < fqc->nflows; i++)
fqc->flows[i].id = i;
}
#endif
fqc->ifp = ifp;
DPRINTF("fq-codel on %s: %d queues %d deep, quantum %d target %llums "
"interval %llums\n", ifp->if_xname, fqc->nflows, fqc->qlimit,
fqc->quantum, fqc->cparams.target / 1000000,
fqc->cparams.interval / 1000000);
return (0);
}
void
fqcodel_pf_free(void *arg)
{
struct fqcodel *fqc = arg;
ml_purge(&fqc->pending_drops);
codel_freeparams(&fqc->cparams);
free(fqc->flows, M_DEVBUF, fqc->nflows * sizeof(struct flow));
free(fqc, M_DEVBUF, sizeof(struct fqcodel));
}
int
fqcodel_pf_qstats(struct pf_queuespec *qs, void *ubuf, int *nbytes)
{
struct ifnet *ifp = qs->kif->pfik_ifp;
struct fqcodel_stats stats;
struct fqcodel *fqc;
int64_t delay;
unsigned int i;
int error = 0;
if (ifp == NULL)
return (EBADF);
if (*nbytes < sizeof(stats))
return (EINVAL);
memset(&stats, 0, sizeof(stats));
/* XXX: multi-q? */
fqc = ifq_q_enter(&ifp->if_snd, ifq_fqcodel_ops);
if (fqc == NULL)
return (EBADF);
stats.xmit_cnt = fqc->xmit_cnt;
stats.drop_cnt = fqc->drop_cnt;
stats.qlength = ifq_len(&ifp->if_snd);
stats.qlimit = fqc->qlimit;
stats.flows = 0;
stats.delaysum = stats.delaysumsq = 0;
for (i = 0; i < fqc->nflows; i++) {
if (codel_qlength(&fqc->flows[i].cd) == 0)
continue;
/* Scale down to microseconds to avoid overflows */
delay = codel_delay(&fqc->flows[i].cd) / 1000;
stats.delaysum += delay;
stats.delaysumsq += delay * delay;
stats.flows++;
}
ifq_q_leave(&ifp->if_snd, fqc);
if ((error = copyout((caddr_t)&stats, ubuf, sizeof(stats))) != 0)
return (error);
*nbytes = sizeof(stats);
return (0);
}
unsigned int
fqcodel_pf_qlength(void *fqc)
{
return ((struct fqcodel *)fqc)->qlength;
}
struct mbuf *
fqcodel_pf_enqueue(void *fqc, struct mbuf *m)
{
return fqcodel_enq(fqc, m);
}
struct mbuf *
fqcodel_pf_deq_begin(void *fqc, void **cookiep, struct mbuf_list *free_ml)
{
return fqcodel_deq_begin(fqc, cookiep, free_ml);
}
void
fqcodel_pf_deq_commit(void *fqc, struct mbuf *m, void *cookie)
{
return fqcodel_deq_commit(fqc, m, cookie);
}
void
fqcodel_pf_purge(void *fqc, struct mbuf_list *ml)
{
return fqcodel_purge(fqc, ml);
}
unsigned int
fqcodel_idx(unsigned int nqueues, const struct mbuf *m)
{
return (0);
}
void *
fqcodel_alloc(unsigned int idx, void *arg)
{
/* Allocation is done in fqcodel_pf_alloc */
return (arg);
}
void
fqcodel_free(unsigned int idx, void *arg)
{
fqcodel_pf_free(arg);
}
+1667
View File
File diff suppressed because it is too large. Load diff
+193
View File
@@ -0,0 +1,193 @@
/* PFLINUX port of OpenBSD 8.0 net/if_pflog.c — see docs/porting-notes.md */
#include <pfk.h>
#define PFLOGMTU (32768 + MHLEN + MLEN)
#ifdef PFLOGDEBUG
#define DPRINTF(x) do { if (pflogdebug) printf x ; } while (0)
#else
#define DPRINTF(x)
#endif
void pflogattach(int);
int pflogoutput(struct ifnet *, struct mbuf *, struct sockaddr *,
struct rtentry *);
int pflogioctl(struct ifnet *, u_long, caddr_t);
int pflog_clone_create(struct if_clone *, int);
int pflog_clone_destroy(struct ifnet *);
struct pflog_softc *pflog_getif(int);
struct if_clone pflog_cloner =
IF_CLONE_INITIALIZER("pflog", pflog_clone_create, pflog_clone_destroy);
LIST_HEAD(, pflog_softc) pflog_ifs = LIST_HEAD_INITIALIZER(pflog_ifs);
void
pflogattach(int npflog)
{
if_clone_attach(&pflog_cloner);
}
int
pflog_clone_create(struct if_clone *ifc, int unit)
{
struct ifnet *ifp;
struct pflog_softc *pflogif;
pflogif = malloc(sizeof(*pflogif), M_DEVBUF, M_WAITOK|M_ZERO);
pflogif->sc_unit = unit;
ifp = &pflogif->sc_if;
snprintf(ifp->if_xname, sizeof ifp->if_xname, "pflog%d", unit);
ifp->if_softc = pflogif;
ifp->if_mtu = PFLOGMTU;
ifp->if_ioctl = pflogioctl;
ifp->if_output = pflogoutput;
ifp->if_xflags = IFXF_CLONED;
ifp->if_type = IFT_PFLOG;
ifp->if_hdrlen = PFLOG_HDRLEN;
if_attach(ifp);
if_alloc_sadl(ifp);
#if NBPFILTER > 0
bpfattach(&pflogif->sc_if.if_bpf, ifp, DLT_PFLOG, PFLOG_HDRLEN);
#endif
NET_LOCK();
LIST_INSERT_HEAD(&pflog_ifs, pflogif, sc_entry);
NET_UNLOCK();
return (0);
}
int
pflog_clone_destroy(struct ifnet *ifp)
{
struct pflog_softc *pflogif = ifp->if_softc;
NET_LOCK();
LIST_REMOVE(pflogif, sc_entry);
NET_UNLOCK();
if_detach(ifp);
free(pflogif, M_DEVBUF, sizeof(*pflogif));
return (0);
}
int
pflogoutput(struct ifnet *ifp, struct mbuf *m, struct sockaddr *dst,
struct rtentry *rt)
{
m_freem(m); /* drop packet */
return (EAFNOSUPPORT);
}
int
pflogioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
{
switch (cmd) {
case SIOCSIFFLAGS:
if (ifp->if_flags & PFIFF_UP)
ifp->if_flags |= PFIFF_RUNNING;
else
ifp->if_flags &= ~PFIFF_RUNNING;
break;
default:
return (ENOTTY);
}
return (0);
}
struct pflog_softc *
pflog_getif(int unit)
{
struct pflog_softc *pflogif;
NET_ASSERT_LOCKED();
LIST_FOREACH(pflogif, &pflog_ifs, sc_entry) {
if (pflogif->sc_unit == unit)
break;
}
return pflogif;
}
int
pflog_packet(struct pf_pdesc *pd, u_int8_t reason, struct pf_rule *rm,
struct pf_rule *am, struct pf_ruleset *ruleset, struct pf_rule *trigger)
{
#if NBPFILTER > 0
struct pflog_softc *pflogif;
struct ifnet *ifn;
caddr_t if_bpf;
struct pfloghdr hdr;
if (rm == NULL || pd == NULL || pd->kif == NULL || pd->m == NULL)
return (-1);
if (trigger == NULL)
trigger = rm;
pflogif = pflog_getif(trigger->logif);
if (pflogif == NULL)
return (0);
ifn = &pflogif->sc_if;
if_bpf = ifn->if_bpf;
if (!if_bpf)
return (0);
bzero(&hdr, sizeof(hdr));
hdr.length = PFLOG_REAL_HDRLEN;
/* Default rule does not pass packets dropped for other reasons. */
hdr.action = (rm->nr == (u_int32_t)-1 && reason != PFRES_MATCH) ?
PF_DROP : rm->action;
hdr.reason = reason;
memcpy(hdr.ifname, pd->kif->pfik_name, sizeof(hdr.ifname));
if (am == NULL) {
hdr.rulenr = htonl(rm->nr);
hdr.subrulenr = -1;
} else {
hdr.rulenr = htonl(am->nr);
hdr.subrulenr = htonl(rm->nr);
if (ruleset != NULL && ruleset->anchor != NULL)
strlcpy(hdr.ruleset, ruleset->anchor->name,
sizeof(hdr.ruleset));
}
if (trigger->log & PF_LOG_USER && !pd->lookup.done)
pd->lookup.done = pf_socket_lookup(pd);
if (trigger->log & PF_LOG_USER && pd->lookup.done > 0) {
hdr.uid = pd->lookup.uid;
hdr.pid = pd->lookup.pid;
} else {
hdr.uid = -1;
hdr.pid = NO_PID;
}
hdr.rule_uid = rm->cuid;
hdr.rule_pid = rm->cpid;
hdr.dir = pd->dir;
hdr.af = pd->af;
if (pd->src != NULL && pd->dst != NULL) {
if (pd->af != pd->naf ||
pf_addr_compare(pd->src, &pd->nsaddr, pd->naf) != 0 ||
pf_addr_compare(pd->dst, &pd->ndaddr, pd->naf) != 0 ||
pd->osport != pd->nsport ||
pd->odport != pd->ndport) {
hdr.rewritten = 1;
}
}
hdr.naf = pd->naf;
pf_addrcpy(&hdr.saddr, &pd->nsaddr, pd->naf);
pf_addrcpy(&hdr.daddr, &pd->ndaddr, pd->naf);
hdr.sport = pd->nsport;
hdr.dport = pd->ndport;
ifn->if_opackets++;
ifn->if_obytes += pd->m->m_pkthdr.len;
bpf_mtap_hdr(if_bpf, &hdr, sizeof(hdr), pd->m, BPF_DIRECTION_OUT);
#endif
return (0);
}
+983
View File
@@ -0,0 +1,983 @@
/* PFLINUX port of OpenBSD 8.0 net/ifq.c — see docs/porting-notes.md */
#include <pfk.h>
#endif
/*
* priq glue
*/
unsigned int priq_idx(unsigned int, const struct mbuf *);
struct mbuf *priq_enq(struct ifqueue *, struct mbuf *);
struct mbuf *priq_deq_begin(struct ifqueue *, void **);
void priq_deq_commit(struct ifqueue *, struct mbuf *, void *);
void priq_purge(struct ifqueue *, struct mbuf_list *);
void *priq_alloc(unsigned int, void *);
void priq_free(unsigned int, void *);
const struct ifq_ops priq_ops = {
priq_idx,
priq_enq,
priq_deq_begin,
priq_deq_commit,
priq_purge,
priq_alloc,
priq_free,
};
const struct ifq_ops * const ifq_priq_ops = &priq_ops;
/*
* priq internal structures
*/
struct priq {
struct mbuf_list pq_lists[IFQ_NQUEUES];
};
/*
* ifqueue serialiser
*/
void ifq_start_task(void *);
void ifq_restart_task(void *);
void ifq_bundle_task(void *);
static inline void
ifq_run_start(struct ifqueue *ifq)
{
ifq_serialize(ifq, &ifq->ifq_start);
}
void
ifq_serialize(struct ifqueue *ifq, struct task *t)
{
struct task work;
if (ISSET(t->t_flags, TASK_ONQUEUE))
return;
mtx_enter(&ifq->ifq_task_mtx);
if (!ISSET(t->t_flags, TASK_ONQUEUE)) {
SET(t->t_flags, TASK_ONQUEUE);
TAILQ_INSERT_TAIL(&ifq->ifq_task_list, t, t_entry);
}
if (ifq->ifq_serializer == NULL) {
ifq->ifq_serializer = curcpu();
while ((t = TAILQ_FIRST(&ifq->ifq_task_list)) != NULL) {
TAILQ_REMOVE(&ifq->ifq_task_list, t, t_entry);
CLR(t->t_flags, TASK_ONQUEUE);
work = *t; /* copy to caller to avoid races */
mtx_leave(&ifq->ifq_task_mtx);
(*work.t_func)(work.t_arg);
mtx_enter(&ifq->ifq_task_mtx);
}
ifq->ifq_serializer = NULL;
}
mtx_leave(&ifq->ifq_task_mtx);
}
void
ifq_start(struct ifqueue *ifq)
{
if (ifq_len(ifq) >= min(ifq->ifq_if->if_txmit, ifq->ifq_maxlen)) {
task_del(ifq->ifq_softnet, &ifq->ifq_bundle);
ifq_run_start(ifq);
} else
task_add(ifq->ifq_softnet, &ifq->ifq_bundle);
}
void
ifq_start_task(void *p)
{
struct ifqueue *ifq = p;
struct ifnet *ifp = ifq->ifq_if;
if (!ISSET(ifp->if_flags, PFIFF_RUNNING) ||
ifq_empty(ifq) || ifq_is_oactive(ifq))
return;
ifp->if_qstart(ifq);
}
void
ifq_set_oactive(struct ifqueue *ifq)
{
if (ifq->ifq_oactive)
return;
mtx_enter(&ifq->ifq_mtx);
if (!ifq->ifq_oactive) {
ifq->ifq_oactive = 1;
ifq->ifq_oactives++;
}
mtx_leave(&ifq->ifq_mtx);
}
void
ifq_deq_set_oactive(struct ifqueue *ifq)
{
MUTEX_ASSERT_LOCKED(&ifq->ifq_mtx);
if (!ifq->ifq_oactive) {
ifq->ifq_oactive = 1;
ifq->ifq_oactives++;
}
}
void
ifq_restart_task(void *p)
{
struct ifqueue *ifq = p;
struct ifnet *ifp = ifq->ifq_if;
ifq_clr_oactive(ifq);
ifp->if_qstart(ifq);
}
void
ifq_bundle_task(void *p)
{
struct ifqueue *ifq = p;
ifq_run_start(ifq);
}
void
ifq_barrier(struct ifqueue *ifq)
{
struct cond c = COND_INITIALIZER();
struct task t = TASK_INITIALIZER(cond_signal_handler, &c);
task_del(ifq->ifq_softnet, &ifq->ifq_bundle);
if (ifq->ifq_serializer == NULL)
return;
ifq_serialize(ifq, &t);
cond_wait(&c, "ifqbar");
}
/*
* ifqueue mbuf queue API
*/
#if NKSTAT > 0
struct ifq_kstat_data {
struct kstat_kv kd_packets;
struct kstat_kv kd_bytes;
struct kstat_kv kd_qdrops;
struct kstat_kv kd_errors;
struct kstat_kv kd_qlen;
struct kstat_kv kd_maxqlen;
struct kstat_kv kd_oactive;
struct kstat_kv kd_oactives;
};
static const struct ifq_kstat_data ifq_kstat_tpl = {
KSTAT_KV_UNIT_INITIALIZER("packets",
KSTAT_KV_T_COUNTER64, KSTAT_KV_U_PACKETS),
KSTAT_KV_UNIT_INITIALIZER("bytes",
KSTAT_KV_T_COUNTER64, KSTAT_KV_U_BYTES),
KSTAT_KV_UNIT_INITIALIZER("qdrops",
KSTAT_KV_T_COUNTER64, KSTAT_KV_U_PACKETS),
KSTAT_KV_UNIT_INITIALIZER("errors",
KSTAT_KV_T_COUNTER64, KSTAT_KV_U_PACKETS),
KSTAT_KV_UNIT_INITIALIZER("qlen",
KSTAT_KV_T_UINT32, KSTAT_KV_U_PACKETS),
KSTAT_KV_UNIT_INITIALIZER("maxqlen",
KSTAT_KV_T_UINT32, KSTAT_KV_U_PACKETS),
KSTAT_KV_INITIALIZER("oactive", KSTAT_KV_T_BOOL),
KSTAT_KV_INITIALIZER("oactives", KSTAT_KV_T_COUNTER32),
};
int
ifq_kstat_copy(struct kstat *ks, void *dst)
{
struct ifqueue *ifq = ks->ks_softc;
struct ifq_kstat_data *kd = dst;
*kd = ifq_kstat_tpl;
kstat_kv_u64(&kd->kd_packets) = ifq->ifq_packets;
kstat_kv_u64(&kd->kd_bytes) = ifq->ifq_bytes;
kstat_kv_u64(&kd->kd_qdrops) = ifq->ifq_qdrops;
kstat_kv_u64(&kd->kd_errors) = ifq->ifq_errors;
kstat_kv_u32(&kd->kd_qlen) = ifq->ifq_len;
kstat_kv_u32(&kd->kd_maxqlen) = ifq->ifq_maxlen;
kstat_kv_bool(&kd->kd_oactive) = ifq->ifq_oactive;
kstat_kv_u32(&kd->kd_oactives) = ifq->ifq_oactives;
return (0);
}
#endif
void
ifq_init(struct ifqueue *ifq, struct ifnet *ifp, unsigned int idx)
{
ifq->ifq_if = ifp;
ifq->ifq_softnet = net_tq(idx);
ifq->ifq_softc = NULL;
mtx_init(&ifq->ifq_mtx, IPL_NET);
/* default to priq */
ifq->ifq_ops = &priq_ops;
ifq->ifq_q = priq_ops.ifqop_alloc(idx, NULL);
ml_init(&ifq->ifq_free);
ifq->ifq_len = 0;
ifq->ifq_packets = 0;
ifq->ifq_bytes = 0;
ifq->ifq_qdrops = 0;
ifq->ifq_errors = 0;
ifq->ifq_mcasts = 0;
mtx_init(&ifq->ifq_task_mtx, IPL_NET);
TAILQ_INIT(&ifq->ifq_task_list);
ifq->ifq_serializer = NULL;
task_set(&ifq->ifq_bundle, ifq_bundle_task, ifq);
task_set(&ifq->ifq_start, ifq_start_task, ifq);
task_set(&ifq->ifq_restart, ifq_restart_task, ifq);
if (ifq->ifq_maxlen == 0)
ifq_init_maxlen(ifq, IFQ_MAXLEN);
ifq->ifq_idx = idx;
#if NKSTAT > 0
/* XXX xname vs driver name and unit */
ifq->ifq_kstat = kstat_create(ifp->if_xname, 0,
"txq", ifq->ifq_idx, KSTAT_T_KV, 0);
KASSERT(ifq->ifq_kstat != NULL);
kstat_set_mutex(ifq->ifq_kstat, &ifq->ifq_mtx);
ifq->ifq_kstat->ks_softc = ifq;
ifq->ifq_kstat->ks_datalen = sizeof(ifq_kstat_tpl);
ifq->ifq_kstat->ks_copy = ifq_kstat_copy;
kstat_install(ifq->ifq_kstat);
#endif
}
void
ifq_attach(struct ifqueue *ifq, const struct ifq_ops *newops, void *opsarg)
{
struct mbuf_list ml = MBUF_LIST_INITIALIZER();
struct mbuf_list free_ml = MBUF_LIST_INITIALIZER();
struct mbuf *m;
const struct ifq_ops *oldops;
void *newq, *oldq;
newq = newops->ifqop_alloc(ifq->ifq_idx, opsarg);
mtx_enter(&ifq->ifq_mtx);
ifq->ifq_ops->ifqop_purge(ifq, &ml);
ifq->ifq_len = 0;
oldops = ifq->ifq_ops;
oldq = ifq->ifq_q;
ifq->ifq_ops = newops;
ifq->ifq_q = newq;
while ((m = ml_dequeue(&ml)) != NULL) {
m = ifq->ifq_ops->ifqop_enq(ifq, m);
if (m != NULL) {
ifq->ifq_qdrops++;
ml_enqueue(&free_ml, m);
} else
ifq->ifq_len++;
}
mtx_leave(&ifq->ifq_mtx);
oldops->ifqop_free(ifq->ifq_idx, oldq);
ml_purge(&free_ml);
}
void
ifq_destroy(struct ifqueue *ifq)
{
struct mbuf_list ml = MBUF_LIST_INITIALIZER();
#if NKSTAT > 0
kstat_destroy(ifq->ifq_kstat);
#endif
NET_ASSERT_UNLOCKED();
taskq_del_barrier(ifq->ifq_softnet, &ifq->ifq_bundle);
/* don't need to lock because this is the last use of the ifq */
ifq->ifq_ops->ifqop_purge(ifq, &ml);
ifq->ifq_ops->ifqop_free(ifq->ifq_idx, ifq->ifq_q);
ml_purge(&ml);
}
void
ifq_add_data(struct ifqueue *ifq, struct if_data *data)
{
mtx_enter(&ifq->ifq_mtx);
data->ifi_opackets += ifq->ifq_packets;
data->ifi_obytes += ifq->ifq_bytes;
data->ifi_oqdrops += ifq->ifq_qdrops;
data->ifi_omcasts += ifq->ifq_mcasts;
/* ifp->if_data.ifi_oerrors */
mtx_leave(&ifq->ifq_mtx);
}
int
ifq_enqueue(struct ifqueue *ifq, struct mbuf *m)
{
struct mbuf *dm;
mtx_enter(&ifq->ifq_mtx);
dm = ifq->ifq_ops->ifqop_enq(ifq, m);
if (dm != m) {
ifq->ifq_packets++;
ifq->ifq_bytes += m->m_pkthdr.len;
if (ISSET(m->m_flags, M_MCAST))
ifq->ifq_mcasts++;
}
if (dm == NULL)
ifq->ifq_len++;
else
ifq->ifq_qdrops++;
mtx_leave(&ifq->ifq_mtx);
if (dm != NULL)
m_freem(dm);
return (dm == m ? ENOBUFS : 0);
}
static inline void
ifq_deq_enter(struct ifqueue *ifq)
{
mtx_enter(&ifq->ifq_mtx);
}
static inline void
ifq_deq_leave(struct ifqueue *ifq)
{
struct mbuf_list ml;
ml = ifq->ifq_free;
ml_init(&ifq->ifq_free);
mtx_leave(&ifq->ifq_mtx);
if (!ml_empty(&ml))
ml_purge(&ml);
}
struct mbuf *
ifq_deq_begin(struct ifqueue *ifq)
{
struct mbuf *m = NULL;
void *cookie;
ifq_deq_enter(ifq);
if (ifq->ifq_len == 0 ||
(m = ifq->ifq_ops->ifqop_deq_begin(ifq, &cookie)) == NULL) {
ifq_deq_leave(ifq);
return (NULL);
}
m->m_pkthdr.ph_cookie = cookie;
return (m);
}
void
ifq_deq_commit(struct ifqueue *ifq, struct mbuf *m)
{
void *cookie;
KASSERT(m != NULL);
cookie = m->m_pkthdr.ph_cookie;
ifq->ifq_ops->ifqop_deq_commit(ifq, m, cookie);
ifq->ifq_len--;
ifq_deq_leave(ifq);
}
void
ifq_deq_rollback(struct ifqueue *ifq, struct mbuf *m)
{
KASSERT(m != NULL);
ifq_deq_leave(ifq);
}
struct mbuf *
ifq_dequeue(struct ifqueue *ifq)
{
struct mbuf *m;
m = ifq_deq_begin(ifq);
if (m == NULL)
return (NULL);
ifq_deq_commit(ifq, m);
return (m);
}
int
ifq_deq_sleep(struct ifqueue *ifq, struct mbuf **mp, int nbio, int priority,
const char *wmesg, volatile unsigned int *sleeping,
volatile unsigned int *alive)
{
struct mbuf *m;
void *cookie;
int error = 0;
ifq_deq_enter(ifq);
if (ifq->ifq_len == 0 && nbio)
error = EWOULDBLOCK;
else {
for (;;) {
m = ifq->ifq_ops->ifqop_deq_begin(ifq, &cookie);
if (m != NULL) {
ifq->ifq_ops->ifqop_deq_commit(ifq, m, cookie);
ifq->ifq_len--;
*mp = m;
break;
}
(*sleeping)++;
error = msleep_nsec(ifq, &ifq->ifq_mtx,
priority, wmesg, INFSLP);
(*sleeping)--;
if (error != 0)
break;
if (!(*alive)) {
error = EIO;
break;
}
}
}
ifq_deq_leave(ifq);
return (error);
}
int
ifq_hdatalen(struct ifqueue *ifq)
{
struct mbuf *m;
int len = 0;
if (ifq_empty(ifq))
return (0);
m = ifq_deq_begin(ifq);
if (m != NULL) {
len = m->m_pkthdr.len;
ifq_deq_rollback(ifq, m);
}
return (len);
}
void
ifq_init_maxlen(struct ifqueue *ifq, unsigned int maxlen)
{
/* this is not MP safe, use only during attach */
ifq->ifq_maxlen = maxlen;
}
unsigned int
ifq_purge(struct ifqueue *ifq)
{
struct mbuf_list ml = MBUF_LIST_INITIALIZER();
unsigned int rv;
mtx_enter(&ifq->ifq_mtx);
ifq->ifq_ops->ifqop_purge(ifq, &ml);
rv = ifq->ifq_len;
ifq->ifq_len = 0;
ifq->ifq_qdrops += rv;
mtx_leave(&ifq->ifq_mtx);
KASSERT(rv == ml_len(&ml));
ml_purge(&ml);
return (rv);
}
void *
ifq_q_enter(struct ifqueue *ifq, const struct ifq_ops *ops)
{
mtx_enter(&ifq->ifq_mtx);
if (ifq->ifq_ops == ops)
return (ifq->ifq_q);
mtx_leave(&ifq->ifq_mtx);
return (NULL);
}
void
ifq_q_leave(struct ifqueue *ifq, void *q)
{
KASSERT(q == ifq->ifq_q);
mtx_leave(&ifq->ifq_mtx);
}
void
ifq_mfreem(struct ifqueue *ifq, struct mbuf *m)
{
MUTEX_ASSERT_LOCKED(&ifq->ifq_mtx);
ifq->ifq_len--;
ifq->ifq_qdrops++;
ml_enqueue(&ifq->ifq_free, m);
}
void
ifq_mfreeml(struct ifqueue *ifq, struct mbuf_list *ml)
{
MUTEX_ASSERT_LOCKED(&ifq->ifq_mtx);
ifq->ifq_len -= ml_len(ml);
ifq->ifq_qdrops += ml_len(ml);
ml_enlist(&ifq->ifq_free, ml);
}
/*
* ifiq
*/
#if NKSTAT > 0
struct ifiq_kstat_data {
struct kstat_kv kd_packets;
struct kstat_kv kd_bytes;
struct kstat_kv kd_fdrops;
struct kstat_kv kd_qdrops;
struct kstat_kv kd_errors;
struct kstat_kv kd_qlen;
struct kstat_kv kd_enqueues;
struct kstat_kv kd_dequeues;
};
static const struct ifiq_kstat_data ifiq_kstat_tpl = {
KSTAT_KV_UNIT_INITIALIZER("packets",
KSTAT_KV_T_COUNTER64, KSTAT_KV_U_PACKETS),
KSTAT_KV_UNIT_INITIALIZER("bytes",
KSTAT_KV_T_COUNTER64, KSTAT_KV_U_BYTES),
KSTAT_KV_UNIT_INITIALIZER("fdrops",
KSTAT_KV_T_COUNTER64, KSTAT_KV_U_PACKETS),
KSTAT_KV_UNIT_INITIALIZER("qdrops",
KSTAT_KV_T_COUNTER64, KSTAT_KV_U_PACKETS),
KSTAT_KV_UNIT_INITIALIZER("errors",
KSTAT_KV_T_COUNTER64, KSTAT_KV_U_PACKETS),
KSTAT_KV_UNIT_INITIALIZER("qlen",
KSTAT_KV_T_UINT32, KSTAT_KV_U_PACKETS),
KSTAT_KV_INITIALIZER("enqueues",
KSTAT_KV_T_COUNTER64),
KSTAT_KV_INITIALIZER("dequeues",
KSTAT_KV_T_COUNTER64),
};
int
ifiq_kstat_copy(struct kstat *ks, void *dst)
{
struct ifiqueue *ifiq = ks->ks_softc;
struct ifiq_kstat_data *kd = dst;
*kd = ifiq_kstat_tpl;
kstat_kv_u64(&kd->kd_packets) = ifiq->ifiq_packets;
kstat_kv_u64(&kd->kd_bytes) = ifiq->ifiq_bytes;
kstat_kv_u64(&kd->kd_fdrops) = ifiq->ifiq_fdrops;
kstat_kv_u64(&kd->kd_qdrops) = ifiq->ifiq_qdrops;
kstat_kv_u64(&kd->kd_errors) = ifiq->ifiq_errors;
kstat_kv_u32(&kd->kd_qlen) = ml_len(&ifiq->ifiq_ml);
kstat_kv_u64(&kd->kd_enqueues) = ifiq->ifiq_enqueues;
kstat_kv_u64(&kd->kd_dequeues) = ifiq->ifiq_dequeues;
return (0);
}
#endif
static void ifiq_process(void *);
void
ifiq_init(struct ifiqueue *ifiq, struct ifnet *ifp, unsigned int idx)
{
ifiq->ifiq_if = ifp;
ifiq->ifiq_bpfp = NULL;
ifiq->ifiq_softnet = net_tq(idx);
ifiq->ifiq_softc = NULL;
mtx_init(&ifiq->ifiq_mtx, IPL_NET);
ml_init(&ifiq->ifiq_ml);
task_set(&ifiq->ifiq_task, ifiq_process, ifiq);
ifiq->ifiq_pressure = 0;
ifiq->ifiq_packets = 0;
ifiq->ifiq_bytes = 0;
ifiq->ifiq_fdrops = 0;
ifiq->ifiq_qdrops = 0;
ifiq->ifiq_errors = 0;
ifiq->ifiq_idx = idx;
#if NKSTAT > 0
/* XXX xname vs driver name and unit */
ifiq->ifiq_kstat = kstat_create(ifp->if_xname, 0,
"rxq", ifiq->ifiq_idx, KSTAT_T_KV, 0);
KASSERT(ifiq->ifiq_kstat != NULL);
kstat_set_mutex(ifiq->ifiq_kstat, &ifiq->ifiq_mtx);
ifiq->ifiq_kstat->ks_softc = ifiq;
ifiq->ifiq_kstat->ks_datalen = sizeof(ifiq_kstat_tpl);
ifiq->ifiq_kstat->ks_copy = ifiq_kstat_copy;
kstat_install(ifiq->ifiq_kstat);
#endif
}
void
ifiq_destroy(struct ifiqueue *ifiq)
{
#if NKSTAT > 0
kstat_destroy(ifiq->ifiq_kstat);
#endif
NET_ASSERT_UNLOCKED();
taskq_del_barrier(ifiq->ifiq_softnet, &ifiq->ifiq_task);
/* don't need to lock because this is the last use of the ifiq */
ml_purge(&ifiq->ifiq_ml);
}
unsigned int ifiq_maxlen_drop = 2048 * 5;
unsigned int ifiq_maxlen_return = 2048 * 3;
int
ifiq_input(struct ifiqueue *ifiq, struct mbuf_list *ml)
{
struct ifnet *ifp = ifiq->ifiq_if;
struct mbuf *m;
uint64_t packets;
uint64_t bytes = 0;
uint64_t fdrops = 0;
unsigned int len;
#if NBPFILTER > 0
caddr_t *ifiq_bpfp, ifiq_bpf = NULL, if_bpf;
#endif
if (ml_empty(ml))
return (0);
MBUF_LIST_FOREACH(ml, m) {
m->m_pkthdr.ph_ifidx = ifp->if_index;
m->m_pkthdr.ph_rtableid = ifp->if_rdomain;
bytes += m->m_pkthdr.len;
}
packets = ml_len(ml);
#if NBPFILTER > 0
ifiq_bpfp = READ_ONCE(ifiq->ifiq_bpfp);
if (ifiq_bpfp != NULL)
ifiq_bpf = READ_ONCE(*ifiq_bpfp);
if_bpf = READ_ONCE(ifp->if_bpf);
if (ifiq_bpf || if_bpf) {
struct mbuf_list ml0 = *ml;
ml_init(ml);
while ((m = ml_dequeue(&ml0)) != NULL) {
int drop = 0;
if (ifiq_bpf &&
(*ifp->if_bpf_mtap)(ifiq_bpf, m, BPF_DIRECTION_IN))
drop = 1;
if (if_bpf &&
(*ifp->if_bpf_mtap)(if_bpf, m, BPF_DIRECTION_IN))
drop = 1;
if (drop) {
m_freem(m);
fdrops++;
} else
ml_enqueue(ml, m);
}
if (ml_empty(ml)) {
mtx_enter(&ifiq->ifiq_mtx);
ifiq->ifiq_packets += packets;
ifiq->ifiq_bytes += bytes;
ifiq->ifiq_fdrops += fdrops;
mtx_leave(&ifiq->ifiq_mtx);
return (0);
}
}
#endif
mtx_enter(&ifiq->ifiq_mtx);
ifiq->ifiq_packets += packets;
ifiq->ifiq_bytes += bytes;
ifiq->ifiq_fdrops += fdrops;
len = ml_len(&ifiq->ifiq_ml);
if (__predict_true(!ISSET(ifp->if_xflags, IFXF_MONITOR))) {
if (len > ifiq_maxlen_drop)
ifiq->ifiq_qdrops += ml_len(ml);
else {
ifiq->ifiq_enqueues++;
ml_enlist(&ifiq->ifiq_ml, ml);
}
}
mtx_leave(&ifiq->ifiq_mtx);
if (ml_empty(ml))
task_add(ifiq->ifiq_softnet, &ifiq->ifiq_task);
else
ml_purge(ml);
return (len > ifiq_maxlen_return);
}
void
ifiq_add_data(struct ifiqueue *ifiq, struct if_data *data)
{
mtx_enter(&ifiq->ifiq_mtx);
data->ifi_ipackets += ifiq->ifiq_packets;
data->ifi_ibytes += ifiq->ifiq_bytes;
data->ifi_iqdrops += ifiq->ifiq_qdrops;
mtx_leave(&ifiq->ifiq_mtx);
}
int
ifiq_enqueue_qlim(struct ifiqueue *ifiq, struct mbuf *m, unsigned int qlim)
{
struct ifnet *ifp = ifiq->ifiq_if;
unsigned int len;
#if NBPFILTER > 0
caddr_t if_bpf = ifp->if_bpf;
#endif
m->m_pkthdr.ph_ifidx = ifp->if_index;
m->m_pkthdr.ph_rtableid = ifp->if_rdomain;
#if NBPFILTER > 0
if_bpf = ifp->if_bpf;
if (if_bpf) {
if ((*ifp->if_bpf_mtap)(if_bpf, m, BPF_DIRECTION_IN)) {
mtx_enter(&ifiq->ifiq_mtx);
ifiq->ifiq_packets++;
ifiq->ifiq_bytes += m->m_pkthdr.len;
ifiq->ifiq_fdrops++;
mtx_leave(&ifiq->ifiq_mtx);
m_freem(m);
return (0);
}
}
#endif
mtx_enter(&ifiq->ifiq_mtx);
ifiq->ifiq_packets++;
ifiq->ifiq_bytes += m->m_pkthdr.len;
if (qlim && ((len = ml_len(&ifiq->ifiq_ml) >= qlim))) {
ifiq->ifiq_qdrops++;
} else {
ifiq->ifiq_enqueues++;
ml_enqueue(&ifiq->ifiq_ml, m);
m = NULL;
}
mtx_leave(&ifiq->ifiq_mtx);
if (m) {
m_freem(m);
return (0);
}
task_add(ifiq->ifiq_softnet, &ifiq->ifiq_task);
return (0);
}
static void
ifiq_process(void *arg)
{
struct ifiqueue *ifiq = arg;
struct mbuf_list ml;
if (ifiq_empty(ifiq))
return;
mtx_enter(&ifiq->ifiq_mtx);
ifiq->ifiq_dequeues++;
ml = ifiq->ifiq_ml;
ml_init(&ifiq->ifiq_ml);
mtx_leave(&ifiq->ifiq_mtx);
if_input_process(ifiq->ifiq_if, &ml, ifiq->ifiq_idx);
}
#ifndef SMALL_KERNEL
int
net_ifiq_sysctl(int *name, u_int namelen, void *oldp, size_t *oldlenp,
void *newp, size_t newlen)
{
int error = EOPNOTSUPP;
/* pressure is disabled for 6.6-release */
#if 0
int val;
if (namelen != 1)
return (EISDIR);
switch (name[0]) {
case NET_LINK_IFRXQ_PRESSURE_RETURN:
val = ifiq_pressure_return;
error = sysctl_int(oldp, oldlenp, newp, newlen, &val);
if (error != 0)
return (error);
if (val < 1 || val > ifiq_pressure_drop)
return (EINVAL);
ifiq_pressure_return = val;
break;
case NET_LINK_IFRXQ_PRESSURE_DROP:
val = ifiq_pressure_drop;
error = sysctl_int(oldp, oldlenp, newp, newlen, &val);
if (error != 0)
return (error);
if (ifiq_pressure_return > val)
return (EINVAL);
ifiq_pressure_drop = val;
break;
default:
error = EOPNOTSUPP;
break;
}
#endif
return (error);
}
#endif /* SMALL_KERNEL */
/*
* priq implementation
*/
unsigned int
priq_idx(unsigned int nqueues, const struct mbuf *m)
{
unsigned int flow = 0;
if (ISSET(m->m_pkthdr.csum_flags, M_FLOWID))
flow = m->m_pkthdr.ph_flowid;
return (flow % nqueues);
}
void *
priq_alloc(unsigned int idx, void *null)
{
struct priq *pq;
int i;
pq = malloc(sizeof(struct priq), M_DEVBUF, M_WAITOK);
for (i = 0; i < IFQ_NQUEUES; i++)
ml_init(&pq->pq_lists[i]);
return (pq);
}
void
priq_free(unsigned int idx, void *pq)
{
free(pq, M_DEVBUF, sizeof(struct priq));
}
struct mbuf *
priq_enq(struct ifqueue *ifq, struct mbuf *m)
{
struct priq *pq;
struct mbuf_list *pl;
struct mbuf *n = NULL;
unsigned int prio;
pq = ifq->ifq_q;
KASSERT(m->m_pkthdr.pf.prio <= IFQ_MAXPRIO);
/* Find a lower priority queue to drop from */
if (ifq_len(ifq) >= ifq->ifq_maxlen) {
for (prio = 0; prio < m->m_pkthdr.pf.prio; prio++) {
pl = &pq->pq_lists[prio];
if (ml_len(pl) > 0) {
n = ml_dequeue(pl);
goto enqueue;
}
}
/*
* There's no lower priority queue that we can
* drop from so don't enqueue this one.
*/
return (m);
}
enqueue:
pl = &pq->pq_lists[m->m_pkthdr.pf.prio];
ml_enqueue(pl, m);
return (n);
}
struct mbuf *
priq_deq_begin(struct ifqueue *ifq, void **cookiep)
{
struct priq *pq = ifq->ifq_q;
struct mbuf_list *pl;
unsigned int prio = nitems(pq->pq_lists);
struct mbuf *m;
do {
pl = &pq->pq_lists[--prio];
m = MBUF_LIST_FIRST(pl);
if (m != NULL) {
*cookiep = pl;
return (m);
}
} while (prio > 0);
return (NULL);
}
void
priq_deq_commit(struct ifqueue *ifq, struct mbuf *m, void *cookie)
{
struct mbuf_list *pl = cookie;
KASSERT(MBUF_LIST_FIRST(pl) == m);
ml_dequeue(pl);
}
void
priq_purge(struct ifqueue *ifq, struct mbuf_list *ml)
{
struct priq *pq = ifq->ifq_q;
struct mbuf_list *pl;
unsigned int prio = nitems(pq->pq_lists);
do {
pl = &pq->pq_lists[--prio];
ml_enlist(ml, pl);
} while (prio > 0);
}
+318
View File
@@ -0,0 +1,318 @@
/* PFLINUX port of OpenBSD 8.0 netinet/ip_divert.c — see docs/porting-notes.md */
#include <pfk.h>
/*
* Locks used to protect data:
* a atomic
*/
struct inpcbtable divbtable;
struct cpumem *divcounters;
u_int divert_sendspace = DIVERT_SENDSPACE; /* [a] */
u_int divert_recvspace = DIVERT_RECVSPACE; /* [a] */
const struct sysctl_bounded_args divertctl_vars[] = {
{ DIVERTCTL_RECVSPACE, &divert_recvspace, 0, SB_MAX },
{ DIVERTCTL_SENDSPACE, &divert_sendspace, 0, SB_MAX },
};
const struct pr_usrreqs divert_usrreqs = {
.pru_attach = divert_attach,
.pru_detach = divert_detach,
.pru_bind = divert_bind,
.pru_shutdown = divert_shutdown,
.pru_send = divert_send,
.pru_control = in_control,
.pru_sockaddr = in_sockaddr,
.pru_peeraddr = in_peeraddr,
};
int divert_output(struct inpcb *, struct mbuf *, struct mbuf *,
struct mbuf *);
void
divert_init(void)
{
in_pcbinit(&divbtable, DIVERT_HASHSIZE);
divcounters = counters_alloc(divs_ncounters);
}
int
divert_output(struct inpcb *inp, struct mbuf *m, struct mbuf *nam,
struct mbuf *control)
{
struct sockaddr_in *sin;
int error, min_hdrlen, off, dir;
struct pf_ip *ip;
m_freem(control);
if ((error = in_nam2sin(nam, &sin)))
goto fail;
if (m->m_pkthdr.len > IP_MAXPACKET) {
error = EMSGSIZE;
goto fail;
}
m = rip_chkhdr(m, NULL);
if (m == NULL) {
error = EINVAL;
goto fail;
}
ip = mtod(m, struct pf_ip *);
off = ip->ip_hl << 2;
dir = (sin->sin_addr.s_addr == INADDR_ANY ? PF_OUT : PF_IN);
switch (ip->ip_p) {
case IPPROTO_TCP:
min_hdrlen = sizeof(struct pf_tcphdr);
m->m_pkthdr.csum_flags |= M_TCP_CSUM_OUT;
break;
case IPPROTO_UDP:
min_hdrlen = sizeof(struct pf_udphdr);
m->m_pkthdr.csum_flags |= M_UDP_CSUM_OUT;
break;
case IPPROTO_ICMP:
min_hdrlen = ICMP_MINLEN;
m->m_pkthdr.csum_flags |= M_ICMP_CSUM_OUT;
break;
default:
min_hdrlen = 0;
break;
}
if (min_hdrlen && m->m_pkthdr.len < off + min_hdrlen) {
error = EINVAL;
goto fail;
}
m->m_pkthdr.pf.flags |= PF_TAG_DIVERTED_PACKET;
if (dir == PF_IN) {
struct rtentry *rt;
struct ifnet *ifp;
rt = rtalloc(sintosa(sin), 0, inp->inp_rtableid);
if (!rtisvalid(rt) || !ISSET(rt->rt_flags, RTF_LOCAL)) {
rtfree(rt);
error = EADDRNOTAVAIL;
goto fail;
}
m->m_pkthdr.ph_ifidx = rt->rt_ifidx;
rtfree(rt);
/*
* Recalculate IP and protocol checksums for the inbound packet
* since the userspace application may have modified the packet
* prior to reinjection.
*/
in_hdr_cksum_out(m, NULL);
in_proto_cksum_out(m, NULL);
ifp = if_get(m->m_pkthdr.ph_ifidx);
if (ifp == NULL) {
error = ENETDOWN;
goto fail;
}
ipv4_input(ifp, m, NULL);
if_put(ifp);
} else {
m->m_pkthdr.ph_rtableid = inp->inp_rtableid;
error = ip_output(m, NULL, &inp->inp_route,
IP_ALLOWBROADCAST | IP_RAWOUTPUT, NULL, NULL, 0);
}
divstat_inc(divs_opackets);
return (error);
fail:
m_freem(m);
divstat_inc(divs_errors);
return (error);
}
void
divert_packet(struct mbuf *m, int dir, u_int16_t divert_port)
{
struct inpcb *inp = NULL;
struct socket *so;
struct sockaddr_in sin;
u_int rdomain;
divstat_inc(divs_ipackets);
if (m->m_len < sizeof(struct pf_ip) &&
(m = m_pullup(m, sizeof(struct pf_ip))) == NULL) {
divstat_inc(divs_errors);
goto bad;
}
rdomain = rtable_l2(m->m_pkthdr.ph_rtableid);
mtx_enter(&divbtable.inpt_mtx);
TAILQ_FOREACH(inp, &divbtable.inpt_queue, inp_queue) {
if (inp->inp_lport != divert_port ||
rtable_l2(inp->inp_rtableid) != rdomain) {
continue;
}
in_pcbref(inp);
break;
}
mtx_leave(&divbtable.inpt_mtx);
if (inp == NULL) {
divstat_inc(divs_noport);
goto bad;
}
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin./* PFLINUX sin_len */;
if (dir == PF_IN) {
struct ifaddr *ifa;
struct ifnet *ifp;
ifp = if_get(m->m_pkthdr.ph_ifidx);
if (ifp == NULL) {
divstat_inc(divs_errors);
goto bad;
}
TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) {
if (ifa->ifa_addr->sa_family != AF_INET)
continue;
sin.sin_addr = satosin(ifa->ifa_addr)->sin_addr;
break;
}
if_put(ifp);
} else {
/*
* Calculate IP and protocol checksums for outbound packet
* diverted to userland. pf rule diverts before cksum offload.
*/
in_hdr_cksum_out(m, NULL);
in_proto_cksum_out(m, NULL);
}
so = inp->inp_socket;
mtx_enter(&so->so_rcv.sb_mtx);
if (sbappendaddr(&so->so_rcv, sintosa(&sin), m, NULL) == 0) {
mtx_leave(&so->so_rcv.sb_mtx);
divstat_inc(divs_fullsock);
goto bad;
}
mtx_leave(&so->so_rcv.sb_mtx);
sorwakeup(so);
in_pcbunref(inp);
return;
bad:
in_pcbunref(inp);
m_freem(m);
}
int
divert_attach(struct socket *so, int proto, int wait)
{
int error;
if (so->so_pcb != NULL)
return EINVAL;
if ((so->so_state & SS_PRIV) == 0)
return EACCES;
error = soreserve(so, atomic_load_int(&divert_sendspace),
atomic_load_int(&divert_recvspace));
if (error)
return error;
error = in_pcballoc(so, &divbtable, wait);
if (error)
return error;
sotoinpcb(so)->inp_flags |= INP_HDRINCL;
return (0);
}
int
divert_detach(struct socket *so)
{
struct inpcb *inp = sotoinpcb(so);
soassertlocked(so);
if (inp == NULL)
return (EINVAL);
in_pcbdetach(inp);
return (0);
}
int
divert_bind(struct socket *so, struct mbuf *addr, struct proc *p)
{
struct inpcb *inp = sotoinpcb(so);
soassertlocked(so);
return in_pcbbind(inp, addr, p);
}
int
divert_shutdown(struct socket *so)
{
soassertlocked(so);
socantsendmore(so);
return (0);
}
int
divert_send(struct socket *so, struct mbuf *m, struct mbuf *addr,
struct mbuf *control)
{
struct inpcb *inp = sotoinpcb(so);
soassertlocked(so);
return (divert_output(inp, m, addr, control));
}
int
divert_sysctl_divstat(void *oldp, size_t *oldlenp, void *newp)
{
uint64_t counters[divs_ncounters];
struct divstat divstat;
u_long *words = (u_long *)&divstat;
int i;
CTASSERT(sizeof(divstat) == (nitems(counters) * sizeof(u_long)));
memset(&divstat, 0, sizeof divstat);
counters_read(divcounters, counters, nitems(counters), NULL);
for (i = 0; i < nitems(counters); i++)
words[i] = (u_long)counters[i];
return (sysctl_rdstruct(oldp, oldlenp, newp,
&divstat, sizeof(divstat)));
}
/*
* Sysctl for divert variables.
*/
int
divert_sysctl(int *name, u_int namelen, void *oldp, size_t *oldlenp, void *newp,
size_t newlen)
{
/* All sysctl names at this level are terminal. */
if (namelen != 1)
return (ENOTDIR);
switch (name[0]) {
case DIVERTCTL_STATS:
return (divert_sysctl_divstat(oldp, oldlenp, newp));
default:
return (sysctl_bounded_arr(divertctl_vars,
nitems(divertctl_vars), name, namelen, oldp, oldlenp,
newp, newlen));
}
/* NOTREACHED */
}
+9101
View File
File diff suppressed because it is too large. Load diff
+910
View File
@@ -0,0 +1,910 @@
/* PFLINUX port of OpenBSD 8.0 net/pf_if.c — see docs/porting-notes.md */
#include <pfk.h>
#define isupper(c) ((c) >= 'A' && (c) <= 'Z')
#define islower(c) ((c) >= 'a' && (c) <= 'z')
#define isalpha(c) (isupper(c)||islower(c))
struct pfi_kif *pfi_all = NULL;
struct pool pfi_addr_pl;
struct pfi_ifhead pfi_ifs;
long pfi_update = 1;
struct pfr_addr *pfi_buffer;
int pfi_buffer_cnt;
int pfi_buffer_max;
void pfi_kif_update(struct pfi_kif *);
void pfi_dynaddr_update(struct pfi_dynaddr *dyn);
void pfi_table_update(struct pfr_ktable *, struct pfi_kif *,
u_int8_t, int);
void pfi_kifaddr_update(void *);
void pfi_instance_add(struct ifnet *, u_int8_t, int);
void pfi_address_add(struct sockaddr *, sa_family_t, u_int8_t);
int pfi_if_compare(struct pfi_kif *, struct pfi_kif *);
int pfi_skip_if(const char *, struct pfi_kif *);
int pfi_unmask(void *);
void pfi_group_change(const char *);
RB_PROTOTYPE(pfi_ifhead, pfi_kif, pfik_tree, pfi_if_compare);
RB_GENERATE(pfi_ifhead, pfi_kif, pfik_tree, pfi_if_compare);
#define PFI_BUFFER_MAX 0x10000
#define PFI_MTYPE M_PF
struct pfi_kif *
pfi_kif_alloc(const char *kif_name, int mflags)
{
struct pfi_kif *kif;
kif = malloc(sizeof(*pfi_all), PFI_MTYPE, mflags|M_ZERO);
if (kif == NULL)
return (NULL);
strlcpy(kif->pfik_name, kif_name, sizeof(kif->pfik_name));
kif->pfik_tzero = gettime();
TAILQ_INIT(&kif->pfik_dynaddrs);
if (!strcmp(kif->pfik_name, "any")) {
/* both so it works in the ioctl and the regular case */
kif->pfik_flags |= PFI_IFLAG_ANY;
kif->pfik_flags_new |= PFI_IFLAG_ANY;
}
return (kif);
}
void
pfi_kif_free(struct pfi_kif *kif)
{
if (kif == NULL)
return;
if (kif->pfik_rules || kif->pfik_states || kif->pfik_routes ||
kif->pfik_srcnodes || kif->pfik_flagrefs)
panic("kif is still alive");
free(kif, PFI_MTYPE, sizeof(*kif));
}
void
pfi_initialize(void)
{
/*
* The first time we arrive here is during kernel boot,
* when if_attachsetup() for the first time. No locking
* is needed in this case, because it's granted there
* is a single thread, which sets pfi_all global var.
*/
if (pfi_all != NULL) /* already initialized */
return;
pool_init(&pfi_addr_pl, sizeof(struct pfi_dynaddr), 0, IPL_SOFTNET, 0,
"pfiaddrpl", NULL);
pfi_buffer_max = 64;
pfi_buffer = mallocarray(pfi_buffer_max, sizeof(*pfi_buffer),
PFI_MTYPE, M_WAITOK);
pfi_all = pfi_kif_alloc(IFG_ALL, M_WAITOK);
if (RB_INSERT(pfi_ifhead, &pfi_ifs, pfi_all) != NULL)
panic("IFG_ALL kif found already");
}
struct pfi_kif *
pfi_kif_find(const char *kif_name)
{
struct pfi_kif_cmp s;
PF_ASSERT_LOCKED();
memset(&s, 0, sizeof(s));
strlcpy(s.pfik_name, kif_name, sizeof(s.pfik_name));
return (RB_FIND(pfi_ifhead, &pfi_ifs, (struct pfi_kif *)&s));
}
struct pfi_kif *
pfi_kif_get(const char *kif_name, struct pfi_kif **prealloc)
{
struct pfi_kif *kif;
PF_ASSERT_LOCKED();
if ((kif = pfi_kif_find(kif_name)))
return (kif);
/* create new one */
if ((prealloc == NULL) || (*prealloc == NULL)) {
kif = pfi_kif_alloc(kif_name, M_NOWAIT);
if (kif == NULL)
return (NULL);
} else {
kif = *prealloc;
*prealloc = NULL;
}
RB_INSERT(pfi_ifhead, &pfi_ifs, kif);
return (kif);
}
void
pfi_kif_ref(struct pfi_kif *kif, enum pfi_kif_refs what)
{
PF_ASSERT_LOCKED();
switch (what) {
case PFI_KIF_REF_RULE:
kif->pfik_rules++;
break;
case PFI_KIF_REF_STATE:
kif->pfik_states++;
break;
case PFI_KIF_REF_ROUTE:
kif->pfik_routes++;
break;
case PFI_KIF_REF_SRCNODE:
kif->pfik_srcnodes++;
break;
case PFI_KIF_REF_FLAG:
kif->pfik_flagrefs++;
break;
default:
panic("pfi_kif_ref with unknown type");
}
}
void
pfi_kif_unref(struct pfi_kif *kif, enum pfi_kif_refs what)
{
if (kif == NULL)
return;
PF_ASSERT_LOCKED();
switch (what) {
case PFI_KIF_REF_NONE:
break;
case PFI_KIF_REF_RULE:
if (kif->pfik_rules <= 0) {
DPFPRINTF(LOG_ERR,
"pfi_kif_unref (%s): rules refcount <= 0",
kif->pfik_name);
return;
}
kif->pfik_rules--;
break;
case PFI_KIF_REF_STATE:
if (kif->pfik_states <= 0) {
DPFPRINTF(LOG_ERR,
"pfi_kif_unref (%s): state refcount <= 0",
kif->pfik_name);
return;
}
kif->pfik_states--;
break;
case PFI_KIF_REF_ROUTE:
if (kif->pfik_routes <= 0) {
DPFPRINTF(LOG_ERR,
"pfi_kif_unref (%s): route refcount <= 0",
kif->pfik_name);
return;
}
kif->pfik_routes--;
break;
case PFI_KIF_REF_SRCNODE:
if (kif->pfik_srcnodes <= 0) {
DPFPRINTF(LOG_ERR,
"pfi_kif_unref (%s): src-node refcount <= 0",
kif->pfik_name);
return;
}
kif->pfik_srcnodes--;
break;
case PFI_KIF_REF_FLAG:
if (kif->pfik_flagrefs <= 0) {
DPFPRINTF(LOG_ERR,
"pfi_kif_unref (%s): flags refcount <= 0",
kif->pfik_name);
return;
}
kif->pfik_flagrefs--;
break;
default:
panic("pfi_kif_unref (%s) with unknown type", kif->pfik_name);
}
if (kif->pfik_ifp != NULL || kif->pfik_group != NULL || kif == pfi_all)
return;
if (kif->pfik_rules || kif->pfik_states || kif->pfik_routes ||
kif->pfik_srcnodes || kif->pfik_flagrefs)
return;
RB_REMOVE(pfi_ifhead, &pfi_ifs, kif);
free(kif, PFI_MTYPE, sizeof(*kif));
}
int
pfi_kif_match(struct pfi_kif *rule_kif, struct pfi_kif *packet_kif)
{
struct ifg_list *p;
if (rule_kif == NULL || rule_kif == packet_kif)
return (1);
if (rule_kif->pfik_group != NULL)
TAILQ_FOREACH(p, &packet_kif->pfik_ifp->if_groups, ifgl_next)
if (p->ifgl_group == rule_kif->pfik_group)
return (1);
if (rule_kif->pfik_flags & PFI_IFLAG_ANY && packet_kif->pfik_ifp &&
!(packet_kif->pfik_ifp->if_flags & PFIFF_LOOPBACK))
return (1);
return (0);
}
void
pfi_attach_ifnet(struct ifnet *ifp)
{
struct pfi_kif *kif;
struct task *t;
PF_LOCK();
pfi_initialize();
pfi_update++;
if ((kif = pfi_kif_get(ifp->if_xname, NULL)) == NULL)
panic("%s: pfi_kif_get failed", __func__);
kif->pfik_ifp = ifp;
ifp->if_pf_kif = (caddr_t)kif;
t = malloc(sizeof(*t), PFI_MTYPE, M_WAITOK);
task_set(t, pfi_kifaddr_update, kif);
if_addrhook_add(ifp, t);
kif->pfik_ah_cookie = t;
pfi_kif_update(kif);
PF_UNLOCK();
}
void
pfi_detach_ifnet(struct ifnet *ifp)
{
struct pfi_kif *kif;
struct task *t;
if ((kif = (struct pfi_kif *)ifp->if_pf_kif) == NULL)
return;
PF_LOCK();
pfi_update++;
t = kif->pfik_ah_cookie;
kif->pfik_ah_cookie = NULL;
if_addrhook_del(ifp, t);
free(t, PFI_MTYPE, sizeof(*t));
pfi_kif_update(kif);
kif->pfik_ifp = NULL;
ifp->if_pf_kif = NULL;
pfi_kif_unref(kif, PFI_KIF_REF_NONE);
PF_UNLOCK();
}
void
pfi_attach_ifgroup(struct ifg_group *ifg)
{
struct pfi_kif *kif;
PF_LOCK();
pfi_initialize();
pfi_update++;
if ((kif = pfi_kif_get(ifg->ifg_group, NULL)) == NULL)
panic("%s: pfi_kif_get failed", __func__);
kif->pfik_group = ifg;
ifg->ifg_pf_kif = (caddr_t)kif;
PF_UNLOCK();
}
void
pfi_detach_ifgroup(struct ifg_group *ifg)
{
struct pfi_kif *kif;
if ((kif = (struct pfi_kif *)ifg->ifg_pf_kif) == NULL)
return;
PF_LOCK();
pfi_update++;
kif->pfik_group = NULL;
ifg->ifg_pf_kif = NULL;
pfi_kif_unref(kif, PFI_KIF_REF_NONE);
PF_UNLOCK();
}
void
pfi_group_change(const char *group)
{
struct pfi_kif *kif;
pfi_update++;
if ((kif = pfi_kif_get(group, NULL)) == NULL)
panic("%s: pfi_kif_get failed", __func__);
pfi_kif_update(kif);
}
void
pfi_group_delmember(const char *group)
{
PF_LOCK();
pfi_group_change(group);
pfi_xcommit();
PF_UNLOCK();
}
void
pfi_group_addmember(const char *group)
{
PF_LOCK();
pfi_group_change(group);
pfi_xcommit();
PF_UNLOCK();
}
int
pfi_match_addr(struct pfi_dynaddr *dyn, struct pf_addr *a, sa_family_t af)
{
switch (af) {
case AF_INET:
switch (dyn->pfid_acnt4) {
case 0:
return (0);
case 1:
return (pf_match_addr(0, &dyn->pfid_addr4,
&dyn->pfid_mask4, a, AF_INET));
default:
return (pfr_match_addr(dyn->pfid_kt, a, AF_INET));
}
break;
#ifdef INET6
case AF_INET6:
switch (dyn->pfid_acnt6) {
case 0:
return (0);
case 1:
return (pf_match_addr(0, &dyn->pfid_addr6,
&dyn->pfid_mask6, a, AF_INET6));
default:
return (pfr_match_addr(dyn->pfid_kt, a, AF_INET6));
}
break;
#endif /* INET6 */
default:
return (0);
}
}
int
pfi_dynaddr_setup(struct pf_addr_wrap *aw, sa_family_t af, int wait)
{
struct pfi_dynaddr *dyn;
char tblname[PF_TABLE_NAME_SIZE];
struct pf_ruleset *ruleset = NULL;
int rv = 0;
if (aw->type != PF_ADDR_DYNIFTL)
return (0);
if ((dyn = pool_get(&pfi_addr_pl, wait|PR_LIMITFAIL|PR_ZERO)) == NULL)
return (1);
if (!strcmp(aw->v.ifname, "self"))
dyn->pfid_kif = pfi_kif_get(IFG_ALL, NULL);
else
dyn->pfid_kif = pfi_kif_get(aw->v.ifname, NULL);
if (dyn->pfid_kif == NULL) {
rv = 1;
goto _bad;
}
pfi_kif_ref(dyn->pfid_kif, PFI_KIF_REF_RULE);
dyn->pfid_net = pfi_unmask(&aw->v.a.mask);
if (af == AF_INET && dyn->pfid_net == 32)
dyn->pfid_net = 128;
strlcpy(tblname, aw->v.ifname, sizeof(tblname));
if (aw->iflags & PFI_AFLAG_NETWORK)
strlcat(tblname, ":network", sizeof(tblname));
if (aw->iflags & PFI_AFLAG_BROADCAST)
strlcat(tblname, ":broadcast", sizeof(tblname));
if (aw->iflags & PFI_AFLAG_PEER)
strlcat(tblname, ":peer", sizeof(tblname));
if (aw->iflags & PFI_AFLAG_NOALIAS)
strlcat(tblname, ":0", sizeof(tblname));
if (dyn->pfid_net != 128)
snprintf(tblname + strlen(tblname),
sizeof(tblname) - strlen(tblname), "/%d", dyn->pfid_net);
if ((ruleset = pf_find_or_create_ruleset(PF_RESERVED_ANCHOR)) == NULL) {
rv = 1;
goto _bad;
}
if ((dyn->pfid_kt = pfr_attach_table(ruleset, tblname, wait)) == NULL) {
rv = 1;
goto _bad;
}
dyn->pfid_kt->pfrkt_flags |= PFR_TFLAG_ACTIVE;
dyn->pfid_iflags = aw->iflags;
dyn->pfid_af = af;
TAILQ_INSERT_TAIL(&dyn->pfid_kif->pfik_dynaddrs, dyn, entry);
aw->p.dyn = dyn;
pfi_kif_update(dyn->pfid_kif);
return (0);
_bad:
if (dyn->pfid_kt != NULL)
pfr_detach_table(dyn->pfid_kt);
if (ruleset != NULL)
pf_remove_if_empty_ruleset(ruleset);
if (dyn->pfid_kif != NULL)
pfi_kif_unref(dyn->pfid_kif, PFI_KIF_REF_RULE);
pool_put(&pfi_addr_pl, dyn);
return (rv);
}
void
pfi_kif_update(struct pfi_kif *kif)
{
struct ifg_list *ifgl;
struct pfi_dynaddr *p;
/* update all dynaddr */
TAILQ_FOREACH(p, &kif->pfik_dynaddrs, entry)
pfi_dynaddr_update(p);
/* again for all groups kif is member of */
if (kif->pfik_ifp != NULL)
TAILQ_FOREACH(ifgl, &kif->pfik_ifp->if_groups, ifgl_next)
pfi_kif_update((struct pfi_kif *)
ifgl->ifgl_group->ifg_pf_kif);
}
void
pfi_dynaddr_update(struct pfi_dynaddr *dyn)
{
struct pfi_kif *kif;
struct pfr_ktable *kt;
if (dyn == NULL || dyn->pfid_kif == NULL || dyn->pfid_kt == NULL)
panic("pfi_dynaddr_update");
kif = dyn->pfid_kif;
kt = dyn->pfid_kt;
if (kt->pfrkt_larg != pfi_update) {
/* this table needs to be brought up-to-date */
pfi_table_update(kt, kif, dyn->pfid_net, dyn->pfid_iflags);
kt->pfrkt_larg = pfi_update;
}
pfr_dynaddr_update(kt, dyn);
}
void
pfi_table_update(struct pfr_ktable *kt, struct pfi_kif *kif, u_int8_t net, int flags)
{
int e, size2 = 0;
struct ifg_member *ifgm;
pfi_buffer_cnt = 0;
if (kif->pfik_ifp != NULL)
pfi_instance_add(kif->pfik_ifp, net, flags);
else if (kif->pfik_group != NULL)
TAILQ_FOREACH(ifgm, &kif->pfik_group->ifg_members, ifgm_next)
pfi_instance_add(ifgm->ifgm_ifp, net, flags);
if ((e = pfr_set_addrs(&kt->pfrkt_t, pfi_buffer, pfi_buffer_cnt, &size2,
NULL, NULL, NULL, 0, PFR_TFLAG_ALLMASK)))
DPFPRINTF(LOG_ERR,
"pfi_table_update: cannot set %d new addresses "
"into table %s: %d", pfi_buffer_cnt, kt->pfrkt_name, e);
}
void
pfi_instance_add(struct ifnet *ifp, u_int8_t net, int flags)
{
struct ifaddr *ifa;
int got4 = 0, got6 = 0;
int net2, af;
if (ifp == NULL)
return;
TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) {
if (ifa->ifa_addr == NULL)
continue;
af = ifa->ifa_addr->sa_family;
if (af != AF_INET && af != AF_INET6)
continue;
if ((flags & PFI_AFLAG_BROADCAST) && af == AF_INET6)
continue;
if ((flags & PFI_AFLAG_BROADCAST) &&
!(ifp->if_flags & PFIFF_BROADCAST))
continue;
if ((flags & PFI_AFLAG_PEER) &&
!(ifp->if_flags & PFIFF_POINTOPOINT))
continue;
if ((flags & PFI_AFLAG_NETWORK) && af == AF_INET6 &&
IN6_IS_ADDR_LINKLOCAL(
&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr))
continue;
if (flags & PFI_AFLAG_NOALIAS) {
if (af == AF_INET && got4)
continue;
if (af == AF_INET6 && got6)
continue;
}
if (af == AF_INET)
got4 = 1;
else if (af == AF_INET6)
got6 = 1;
net2 = net;
if (net2 == 128 && (flags & PFI_AFLAG_NETWORK)) {
if (af == AF_INET)
net2 = pfi_unmask(&((struct sockaddr_in *)
ifa->ifa_netmask)->sin_addr);
else if (af == AF_INET6)
net2 = pfi_unmask(&((struct sockaddr_in6 *)
ifa->ifa_netmask)->sin6_addr);
}
if (af == AF_INET && net2 > 32)
net2 = 32;
if (flags & PFI_AFLAG_BROADCAST)
pfi_address_add(ifa->ifa_broadaddr, af, net2);
else if (flags & PFI_AFLAG_PEER)
pfi_address_add(ifa->ifa_dstaddr, af, net2);
else
pfi_address_add(ifa->ifa_addr, af, net2);
}
}
void
pfi_address_add(struct sockaddr *sa, sa_family_t af, u_int8_t net)
{
struct pfr_addr *p;
int i;
if (pfi_buffer_cnt >= pfi_buffer_max) {
int new_max = pfi_buffer_max * 2;
if (new_max > PFI_BUFFER_MAX) {
DPFPRINTF(LOG_ERR,
"pfi_address_add: address buffer full (%d/%d)",
pfi_buffer_cnt, PFI_BUFFER_MAX);
return;
}
p = mallocarray(new_max, sizeof(*pfi_buffer), PFI_MTYPE,
M_DONTWAIT);
if (p == NULL) {
DPFPRINTF(LOG_ERR,
"pfi_address_add: no memory to grow buffer "
"(%d/%d)", pfi_buffer_cnt, PFI_BUFFER_MAX);
return;
}
memcpy(p, pfi_buffer, pfi_buffer_max * sizeof(*pfi_buffer));
/* no need to zero buffer */
free(pfi_buffer, PFI_MTYPE, pfi_buffer_max * sizeof(*pfi_buffer));
pfi_buffer = p;
pfi_buffer_max = new_max;
}
if (af == AF_INET && net > 32)
net = 128;
p = pfi_buffer + pfi_buffer_cnt++;
memset(p, 0, sizeof(*p));
p->pfra_af = af;
p->pfra_net = net;
if (af == AF_INET)
p->pfra_ip4addr = ((struct sockaddr_in *)sa)->sin_addr;
else if (af == AF_INET6) {
p->pfra_ip6addr = ((struct sockaddr_in6 *)sa)->sin6_addr;
if (IN6_IS_SCOPE_EMBED(&p->pfra_ip6addr))
p->pfra_ip6addr.s6_addr16[1] = 0;
}
/* mask network address bits */
if (net < 128)
((caddr_t)p)[p->pfra_net/8] &= ~(0xFF >> (p->pfra_net%8));
for (i = (p->pfra_net+7)/8; i < sizeof(p->pfra_u); i++)
((caddr_t)p)[i] = 0;
}
void
pfi_dynaddr_remove(struct pf_addr_wrap *aw)
{
if (aw->type != PF_ADDR_DYNIFTL || aw->p.dyn == NULL ||
aw->p.dyn->pfid_kif == NULL || aw->p.dyn->pfid_kt == NULL)
return;
TAILQ_REMOVE(&aw->p.dyn->pfid_kif->pfik_dynaddrs, aw->p.dyn, entry);
pfi_kif_unref(aw->p.dyn->pfid_kif, PFI_KIF_REF_RULE);
aw->p.dyn->pfid_kif = NULL;
pfr_detach_table(aw->p.dyn->pfid_kt);
aw->p.dyn->pfid_kt = NULL;
pool_put(&pfi_addr_pl, aw->p.dyn);
aw->p.dyn = NULL;
}
void
pfi_dynaddr_copyout(struct pf_addr_wrap *aw)
{
if (aw->type != PF_ADDR_DYNIFTL || aw->p.dyn == NULL ||
aw->p.dyn->pfid_kif == NULL)
return;
aw->p.dyncnt = aw->p.dyn->pfid_acnt4 + aw->p.dyn->pfid_acnt6;
}
void
pfi_kifaddr_update(void *v)
{
struct pfi_kif *kif = (struct pfi_kif *)v;
NET_ASSERT_LOCKED();
PF_LOCK();
pfi_update++;
pfi_kif_update(kif);
PF_UNLOCK();
}
int
pfi_if_compare(struct pfi_kif *p, struct pfi_kif *q)
{
return (strncmp(p->pfik_name, q->pfik_name, IFNAMSIZ));
}
void
pfi_update_status(const char *name, struct pf_status *pfs)
{
struct pfi_kif *p;
struct pfi_kif_cmp key;
struct ifg_member p_member, *ifgm;
TAILQ_HEAD(, ifg_member) ifg_members;
int i, j, k;
if (*name == '\0' && pfs == NULL) {
RB_FOREACH(p, pfi_ifhead, &pfi_ifs) {
memset(p->pfik_packets, 0, sizeof(p->pfik_packets));
memset(p->pfik_bytes, 0, sizeof(p->pfik_bytes));
p->pfik_tzero = gettime();
}
return;
}
strlcpy(key.pfik_name, name, sizeof(key.pfik_name));
p = RB_FIND(pfi_ifhead, &pfi_ifs, (struct pfi_kif *)&key);
if (p == NULL) {
return;
}
if (p->pfik_group != NULL) {
memcpy(&ifg_members, &p->pfik_group->ifg_members,
sizeof(ifg_members));
} else {
/* build a temporary list for p only */
memset(&p_member, 0, sizeof(p_member));
p_member.ifgm_ifp = p->pfik_ifp;
TAILQ_INIT(&ifg_members);
TAILQ_INSERT_TAIL(&ifg_members, &p_member, ifgm_next);
}
if (pfs) {
memset(pfs->pcounters, 0, sizeof(pfs->pcounters));
memset(pfs->bcounters, 0, sizeof(pfs->bcounters));
}
TAILQ_FOREACH(ifgm, &ifg_members, ifgm_next) {
if (ifgm->ifgm_ifp == NULL)
continue;
p = (struct pfi_kif *)ifgm->ifgm_ifp->if_pf_kif;
/* just clear statistics */
if (pfs == NULL) {
memset(p->pfik_packets, 0, sizeof(p->pfik_packets));
memset(p->pfik_bytes, 0, sizeof(p->pfik_bytes));
p->pfik_tzero = gettime();
continue;
}
for (i = 0; i < 2; i++)
for (j = 0; j < 2; j++)
for (k = 0; k < 2; k++) {
pfs->pcounters[i][j][k] +=
p->pfik_packets[i][j][k];
pfs->bcounters[i][j] +=
p->pfik_bytes[i][j][k];
}
}
}
void
pfi_get_ifaces(const char *name, struct pfi_kif *buf, int *size)
{
struct pfi_kif *p;
int n = 0;
RB_FOREACH(p, pfi_ifhead, &pfi_ifs) {
if (pfi_skip_if(name, p))
continue;
if (n >= *size)
break;
n++;
if (!p->pfik_tzero)
p->pfik_tzero = gettime();
memcpy(buf++, p, sizeof(*buf));
}
*size = n;
}
int
pfi_skip_if(const char *filter, struct pfi_kif *p)
{
struct ifg_list *i;
int n;
PF_ASSERT_LOCKED();
if (filter == NULL || !*filter)
return (0);
if (!strcmp(p->pfik_name, filter))
return (0); /* exact match */
n = strlen(filter);
if (n < 1 || n >= IFNAMSIZ)
return (1); /* sanity check */
if (filter[n-1] >= '0' && filter[n-1] <= '9')
return (1); /* group names may not end in a digit */
if (p->pfik_ifp != NULL)
TAILQ_FOREACH(i, &p->pfik_ifp->if_groups, ifgl_next)
if (!strncmp(i->ifgl_group->ifg_group, filter, IFNAMSIZ))
return (0); /* iface is in group "filter" */
return (1);
}
int
pfi_set_flags(const char *name, int flags)
{
struct pfi_kif *p;
size_t n;
PF_ASSERT_LOCKED();
if (name != NULL && name[0] != '\0') {
p = pfi_kif_find(name);
if (p == NULL) {
n = strlen(name);
if (n < 1 || n >= IFNAMSIZ)
return (EINVAL);
if (!isalpha(name[0]))
return (EINVAL);
p = pfi_kif_get(name, NULL);
if (p != NULL) {
p->pfik_flags_new = p->pfik_flags | flags;
/*
* We use pfik_flagrefs counter as an
* indication whether the kif has been created
* on behalf of 'pfi_set_flags()' or not.
*/
KASSERT(p->pfik_flagrefs == 0);
if (ISSET(p->pfik_flags_new, PFI_IFLAG_SKIP))
pfi_kif_ref(p, PFI_KIF_REF_FLAG);
} else
panic("%s pfi_kif_get() returned NULL\n",
__func__);
} else {
/*
* pf.conf may accidentally contain two set skip on ...
* statements. For example:
* set skip lo
* set skip lo
* We need to grab reference only when skip flag is
* set to avoid tripping assert pfi_clear_flags()
*/
if (ISSET(flags, PFI_IFLAG_SKIP) &&
!ISSET(p->pfik_flags_new, PFI_IFLAG_SKIP) &&
!ISSET(p->pfik_flags, PFI_IFLAG_SKIP))
pfi_kif_ref(p, PFI_KIF_REF_FLAG);
p->pfik_flags_new = p->pfik_flags | flags;
}
} else {
RB_FOREACH(p, pfi_ifhead, &pfi_ifs)
p->pfik_flags_new = p->pfik_flags | flags;
}
return (0);
}
int
pfi_clear_flags(const char *name, int flags)
{
struct pfi_kif *p, *w;
PF_ASSERT_LOCKED();
if (name != NULL && name[0] != '\0') {
p = pfi_kif_find(name);
if (p != NULL) {
p->pfik_flags_new = p->pfik_flags & ~flags;
KASSERT((p->pfik_flagrefs == 0) ||
(p->pfik_flagrefs == 1));
if (!ISSET(p->pfik_flags_new, PFI_IFLAG_SKIP) &&
(p->pfik_flagrefs == 1))
pfi_kif_unref(p, PFI_KIF_REF_FLAG);
} else
return (ESRCH);
} else
RB_FOREACH_SAFE(p, pfi_ifhead, &pfi_ifs, w) {
p->pfik_flags_new = p->pfik_flags & ~flags;
KASSERT((p->pfik_flagrefs == 0) ||
(p->pfik_flagrefs == 1));
if (!ISSET(p->pfik_flags_new, PFI_IFLAG_SKIP) &&
(p->pfik_flagrefs == 1))
pfi_kif_unref(p, PFI_KIF_REF_FLAG);
}
return (0);
}
void
pfi_xcommit(void)
{
struct pfi_kif *p, *gkif;
struct ifg_list *g;
struct ifnet *ifp;
size_t n;
PF_ASSERT_LOCKED();
RB_FOREACH(p, pfi_ifhead, &pfi_ifs) {
p->pfik_flags = p->pfik_flags_new;
n = strlen(p->pfik_name);
ifp = p->pfik_ifp;
/*
* if kif is backed by existing interface, then we must use
* skip flags found in groups. We use pfik_flags_new, otherwise
* we would need to do two RB_FOREACH() passes: the first to
* commit group changes the second to commit flag changes for
* interfaces.
*/
if (ifp != NULL)
TAILQ_FOREACH(g, &ifp->if_groups, ifgl_next) {
gkif =
(struct pfi_kif *)g->ifgl_group->ifg_pf_kif;
KASSERT(gkif != NULL);
p->pfik_flags |= gkif->pfik_flags_new;
}
}
}
/* from pf_print_state.c */
int
pfi_unmask(void *addr)
{
struct pf_addr *m = addr;
int i = 31, j = 0, b = 0;
u_int32_t tmp;
while (j < 4 && m->addr32[j] == 0xffffffff) {
b += 32;
j++;
}
if (j < 4) {
tmp = ntohl(m->addr32[j]);
for (i = 31; tmp & (1 << i); --i)
b++;
}
return (b);
}
+218
View File
@@ -0,0 +1,218 @@
/* PFLINUX port of OpenBSD 8.0 net/pf_ioctl.c — see docs/porting-notes.md */
#include <pfk.h>
struct pf_statelim_kstat {
struct kstat_kv name;
struct kstat_kv inuse;
struct kstat_kv limit;
struct kstat_kv admitted;
struct kstat_kv hardlimited;
struct kstat_kv ratelimited;
};
static int
pf_statelim_kstat_read(struct kstat *ks)
{
struct pf_statelim *pfstlim = ks->ks_softc;
struct pf_statelim_kstat *d = ks->ks_data;
unsigned int gen;
strlcpy(kstat_kv_istr(&d->name), pfstlim->pfstlim_nm,
sizeof(kstat_kv_istr(&d->name)));
kstat_kv_u32(&d->limit) = pfstlim->pfstlim_limit;
pc_cons_enter(&pfstlim->pfstlim_lock, &gen);
do {
kstat_kv_u32(&d->inuse) = pfstlim->pfstlim_inuse;
kstat_kv_u64(&d->admitted) =
pfstlim->pfstlim_counters.admitted;
kstat_kv_u64(&d->hardlimited) =
pfstlim->pfstlim_counters.hardlimited;
kstat_kv_u64(&d->ratelimited) =
pfstlim->pfstlim_counters.ratelimited;
} while (pc_cons_leave(&pfstlim->pfstlim_lock, &gen) != 0);
nanouptime(&ks->ks_updated);
return (0);
};
static void
pf_statelim_kstat_attach(struct pf_statelim *pfstlim)
{
struct kstat *ks = pfstlim->pfstlim_ks;
struct pf_statelim_kstat *d;
if (ks != NULL)
return;
d = malloc(sizeof(*d), M_DEVBUF, M_WAITOK|M_CANFAIL|M_ZERO);
if (d == NULL) {
printf("pf: unable to allocate state-limiter kstat\n");
return;
}
ks = kstat_create("pf", 0, "state-limiter", pfstlim->pfstlim_id,
KSTAT_T_KV, 0);
if (ks == NULL) {
printf("pf: unable to create state-limiter kstat\n");
free(d, M_DEVBUF, sizeof(*d));
return;
}
kstat_kv_init(&d->name, "name", KSTAT_KV_T_ISTR);
kstat_kv_init(&d->inuse, "in-use", KSTAT_KV_T_UINT32);
kstat_kv_init(&d->limit, "limit", KSTAT_KV_T_UINT32);
kstat_kv_init(&d->admitted, "admitted", KSTAT_KV_T_COUNTER64);
kstat_kv_init(&d->hardlimited, "hard-limited", KSTAT_KV_T_COUNTER64);
kstat_kv_init(&d->ratelimited, "rate-limited", KSTAT_KV_T_COUNTER64);
ks->ks_softc = pfstlim;
ks->ks_data = d;
ks->ks_datalen = sizeof(*d);
ks->ks_read = pf_statelim_kstat_read;
kstat_install(ks);
pfstlim->pfstlim_ks = ks;
}
static void
pf_statelim_kstat_detach(struct pf_statelim *pfstlim)
{
struct kstat *ks = pfstlim->pfstlim_ks;
struct pf_statelim_kstat *d;
if (ks == NULL)
return;
kstat_remove(ks);
d = ks->ks_data;
kstat_destroy(ks);
free(d, M_DEVBUF, sizeof(*d));
}
struct pf_sourcelim_kstat {
struct kstat_kv name;
/* pf_source/address/prefix counters */
struct kstat_kv sourceinuse;
struct kstat_kv sourcelimit;
struct kstat_kv sourceallocs;
struct kstat_kv sourcelimited;
struct kstat_kv sourcenomem;
/* state counters */
struct kstat_kv inuse;
struct kstat_kv limit;
struct kstat_kv admitted;
struct kstat_kv hardlimited;
struct kstat_kv ratelimited;
};
static int
pf_sourcelim_kstat_read(struct kstat *ks)
{
struct pf_sourcelim *pfsrlim = ks->ks_softc;
struct pf_sourcelim_kstat *d = ks->ks_data;
unsigned int gen;
strlcpy(kstat_kv_istr(&d->name), pfsrlim->pfsrlim_nm,
sizeof(kstat_kv_istr(&d->name)));
kstat_kv_u32(&d->sourcelimit) = pfsrlim->pfsrlim_entries;
kstat_kv_u32(&d->limit) = pfsrlim->pfsrlim_limit;
pc_cons_enter(&pfsrlim->pfsrlim_lock, &gen);
do {
kstat_kv_u32(&d->sourceinuse) =
pfsrlim->pfsrlim_nsources;
kstat_kv_u64(&d->sourceallocs) =
pfsrlim->pfsrlim_counters.addrallocs;
kstat_kv_u64(&d->sourcelimited) =
pfsrlim->pfsrlim_counters.addrlimited;
kstat_kv_u64(&d->sourcenomem) =
pfsrlim->pfsrlim_counters.addrnomem;
kstat_kv_u32(&d->inuse) =
pfsrlim->pfsrlim_counters.inuse;
kstat_kv_u64(&d->admitted) =
pfsrlim->pfsrlim_counters.admitted;
kstat_kv_u64(&d->hardlimited) =
pfsrlim->pfsrlim_counters.hardlimited;
kstat_kv_u64(&d->ratelimited) =
pfsrlim->pfsrlim_counters.ratelimited;
} while (pc_cons_leave(&pfsrlim->pfsrlim_lock, &gen) != 0);
nanouptime(&ks->ks_updated);
return (0);
};
static void
pf_sourcelim_kstat_attach(struct pf_sourcelim *pfsrlim)
{
struct kstat *ks = pfsrlim->pfsrlim_ks;
struct pf_sourcelim_kstat *d;
if (ks != NULL)
return;
d = malloc(sizeof(*d), M_DEVBUF, M_WAITOK|M_CANFAIL|M_ZERO);
if (d == NULL) {
printf("pf: unable to allocate source-limiter kstat\n");
return;
}
ks = kstat_create("pf", 0, "source-limiter", pfsrlim->pfsrlim_id,
KSTAT_T_KV, 0);
if (ks == NULL) {
printf("pf: unable to create source-limiter kstat\n");
free(d, M_DEVBUF, sizeof(*d));
return;
}
kstat_kv_init(&d->name, "name", KSTAT_KV_T_ISTR);
kstat_kv_init(&d->sourceinuse, "source-in-use", KSTAT_KV_T_UINT32);
kstat_kv_init(&d->sourcelimit, "source-limit", KSTAT_KV_T_UINT32);
kstat_kv_init(&d->sourceallocs, "source-allocs",
KSTAT_KV_T_COUNTER64);
kstat_kv_init(&d->sourcelimited, "source-limited",
KSTAT_KV_T_COUNTER64);
kstat_kv_init(&d->sourcenomem, "source-nomem",
KSTAT_KV_T_COUNTER64);
kstat_kv_init(&d->inuse, "in-use", KSTAT_KV_T_UINT32);
kstat_kv_init(&d->limit, "limit", KSTAT_KV_T_UINT32);
kstat_kv_init(&d->admitted, "admitted", KSTAT_KV_T_COUNTER64);
kstat_kv_init(&d->hardlimited, "hard-limited", KSTAT_KV_T_COUNTER64);
kstat_kv_init(&d->ratelimited, "rate-limited", KSTAT_KV_T_COUNTER64);
ks->ks_softc = pfsrlim;
ks->ks_data = d;
ks->ks_datalen = sizeof(*d);
ks->ks_read = pf_sourcelim_kstat_read;
kstat_install(ks);
pfsrlim->pfsrlim_ks = ks;
}
static void
pf_sourcelim_kstat_detach(struct pf_sourcelim *pfsrlim)
{
struct kstat *ks = pfsrlim->pfsrlim_ks;
struct pf_sourcelim_kstat *d;
if (ks == NULL)
return;
kstat_remove(ks);
d = ks->ks_data;
kstat_destroy(ks);
free(d, M_DEVBUF, sizeof(*d));
}
#endif /* NKSTAT > 0 */
+945
View File
@@ -0,0 +1,945 @@
/* PFLINUX port of OpenBSD 8.0 net/pf_lb.c — see docs/porting-notes.md */
#include <pfk.h>
u_int64_t pf_hash(struct pf_addr *, struct pf_addr *,
struct pf_poolhashkey *, sa_family_t);
int pf_get_sport(struct pf_pdesc *, struct pf_rule *,
struct pf_addr *, u_int16_t *, u_int16_t,
u_int16_t, struct pf_src_node **);
int pf_map_addr_states_increase(sa_family_t,
struct pf_pool *, struct pf_addr *);
int pf_get_transaddr_af(struct pf_rule *,
struct pf_pdesc *, struct pf_src_node **);
int pf_map_addr_sticky(sa_family_t, struct pf_rule *,
struct pf_addr *, struct pf_addr *,
struct pf_src_node **, struct pf_pool *,
enum pf_sn_types);
int pf_pool_states_decrease_addr(struct pf_pool *, int,
struct pf_addr *);
u_int64_t
pf_hash(struct pf_addr *inaddr, struct pf_addr *hash,
struct pf_poolhashkey *key, sa_family_t af)
{
uint64_t res = 0;
#ifdef INET6
union {
uint64_t hash64;
uint32_t hash32[2];
} h;
#endif /* INET6 */
switch (af) {
case AF_INET:
res = SipHash24((SIPHASH_KEY *)key,
&inaddr->addr32[0], sizeof(inaddr->addr32[0]));
hash->addr32[0] = res;
break;
#ifdef INET6
case AF_INET6:
res = SipHash24((SIPHASH_KEY *)key, &inaddr->addr32[0],
4 * sizeof(inaddr->addr32[0]));
h.hash64 = res;
hash->addr32[0] = h.hash32[0];
hash->addr32[1] = h.hash32[1];
/*
* siphash isn't big enough, but flipping it around is
* good enough here.
*/
hash->addr32[2] = ~h.hash32[1];
hash->addr32[3] = ~h.hash32[0];
break;
#endif /* INET6 */
default:
unhandled_af(af);
}
return (res);
}
int
pf_get_sport(struct pf_pdesc *pd, struct pf_rule *r,
struct pf_addr *naddr, u_int16_t *nport, u_int16_t low, u_int16_t high,
struct pf_src_node **sn)
{
struct pf_state_key_cmp key;
struct pf_addr init_addr;
u_int16_t cut;
int dir = (pd->dir == PF_IN) ? PF_OUT : PF_IN;
int sidx = pd->sidx;
int didx = pd->didx;
memset(&init_addr, 0, sizeof(init_addr));
if (pf_map_addr(pd->naf, r, &pd->nsaddr, naddr, &init_addr, sn, &r->nat,
PF_SN_NAT))
return (1);
if (pd->proto == IPPROTO_ICMP) {
if (pd->ndport == htons(ICMP_ECHO)) {
low = 1;
high = 65535;
} else
return (0); /* Don't try to modify non-echo ICMP */
}
#ifdef INET6
if (pd->proto == IPPROTO_ICMPV6) {
if (pd->ndport == htons(ICMP6_ECHO_REQUEST)) {
low = 1;
high = 65535;
} else
return (0); /* Don't try to modify non-echo ICMP */
}
#endif /* INET6 */
do {
key.af = pd->naf;
key.proto = pd->proto;
key.rdomain = pd->rdomain;
pf_addrcpy(&key.addr[didx], &pd->ndaddr, key.af);
pf_addrcpy(&key.addr[sidx], naddr, key.af);
key.port[didx] = pd->ndport;
/*
* port search; start random, step;
* similar 2 portloop in in_pcbbind
*/
if (!(pd->proto == IPPROTO_TCP || pd->proto == IPPROTO_UDP ||
pd->proto == IPPROTO_ICMP || pd->proto == IPPROTO_ICMPV6)) {
/* XXX bug: icmp states dont use the id on both
* XXX sides (traceroute -I through nat) */
key.port[sidx] = pd->nsport;
key.hash = pf_pkt_hash(key.af, key.proto, &key.addr[0],
&key.addr[1], key.port[0], key.port[1]);
if (pf_find_state_all(&key, dir, NULL) == NULL) {
*nport = pd->nsport;
return (0);
}
} else if (low == 0 && high == 0) {
key.port[sidx] = pd->nsport;
key.hash = pf_pkt_hash(key.af, key.proto, &key.addr[0],
&key.addr[1], key.port[0], key.port[1]);
if (pf_find_state_all(&key, dir, NULL) == NULL) {
*nport = pd->nsport;
return (0);
}
} else if (low == high) {
key.port[sidx] = htons(low);
key.hash = pf_pkt_hash(key.af, key.proto, &key.addr[0],
&key.addr[1], key.port[0], key.port[1]);
if (pf_find_state_all(&key, dir, NULL) == NULL) {
*nport = htons(low);
return (0);
}
} else {
u_int32_t tmp;
if (low > high) {
tmp = low;
low = high;
high = tmp;
}
/* low < high */
cut = arc4random_uniform(1 + high - low) + low;
/* low <= cut <= high */
for (tmp = cut; tmp <= high && tmp <= 0xffff; ++tmp) {
key.port[sidx] = htons(tmp);
key.hash = pf_pkt_hash(key.af, key.proto,
&key.addr[0], &key.addr[1], key.port[0],
key.port[1]);
if (pf_find_state_all(&key, dir, NULL) ==
NULL && !in_baddynamic(tmp, pd->proto)) {
*nport = htons(tmp);
return (0);
}
}
tmp = cut;
for (tmp -= 1; tmp >= low && tmp <= 0xffff; --tmp) {
key.port[sidx] = htons(tmp);
key.hash = pf_pkt_hash(key.af, key.proto,
&key.addr[0], &key.addr[1], key.port[0],
key.port[1]);
if (pf_find_state_all(&key, dir, NULL) ==
NULL && !in_baddynamic(tmp, pd->proto)) {
*nport = htons(tmp);
return (0);
}
}
}
switch (r->nat.opts & PF_POOL_TYPEMASK) {
case PF_POOL_RANDOM:
case PF_POOL_ROUNDROBIN:
case PF_POOL_LEASTSTATES:
/*
* pick a different source address since we're out
* of free port choices for the current one.
*/
if (pf_map_addr(pd->naf, r, &pd->nsaddr, naddr,
&init_addr, sn, &r->nat, PF_SN_NAT))
return (1);
break;
case PF_POOL_NONE:
case PF_POOL_SRCHASH:
case PF_POOL_BITMASK:
default:
return (1);
}
} while (! PF_AEQ(&init_addr, naddr, pd->naf) );
return (1); /* none available */
}
int
pf_map_addr_sticky(sa_family_t af, struct pf_rule *r, struct pf_addr *saddr,
struct pf_addr *naddr, struct pf_src_node **sns, struct pf_pool *rpool,
enum pf_sn_types type)
{
struct pf_addr *raddr, *rmask, *cached;
struct pf_state *s;
struct pf_src_node k;
int valid;
k.af = af;
k.type = type;
pf_addrcpy(&k.addr, saddr, af);
k.rule.ptr = r;
pf_status.scounters[SCNT_SRC_NODE_SEARCH]++;
sns[type] = RB_FIND(pf_src_tree, &tree_src_tracking, &k);
if (sns[type] == NULL)
return (-1);
/* check if the cached entry is still valid */
cached = &(sns[type])->raddr;
valid = 0;
if (PF_AZERO(cached, af)) {
valid = 1;
} else if (rpool->addr.type == PF_ADDR_DYNIFTL) {
if (pfr_kentry_byaddr(rpool->addr.p.dyn->pfid_kt, cached,
af, 0))
valid = 1;
} else if (rpool->addr.type == PF_ADDR_TABLE) {
if (pfr_kentry_byaddr(rpool->addr.p.tbl, cached, af, 0))
valid = 1;
} else if (rpool->addr.type != PF_ADDR_NOROUTE) {
raddr = &rpool->addr.v.a.addr;
rmask = &rpool->addr.v.a.mask;
valid = pf_match_addr(0, raddr, rmask, cached, af);
}
if (!valid) {
if (pf_status.debug >= LOG_DEBUG) {
log(LOG_DEBUG, "pf: pf_map_addr: "
"stale src tracking (%u) ", type);
pf_print_host(&k.addr, 0, af);
addlog(" to ");
pf_print_host(cached, 0, af);
addlog("\n");
}
if (sns[type]->states != 0) {
/* XXX expensive */
RBT_FOREACH(s, pf_state_tree_id, &tree_id)
pf_state_rm_src_node(s, sns[type]);
}
sns[type]->expire = 1;
pf_remove_src_node(sns[type]);
sns[type] = NULL;
return (-1);
}
if (!PF_AZERO(cached, af)) {
pf_addrcpy(naddr, cached, af);
if ((rpool->opts & PF_POOL_TYPEMASK) == PF_POOL_LEASTSTATES &&
pf_map_addr_states_increase(af, rpool, cached) == -1)
return (-1);
}
if (pf_status.debug >= LOG_DEBUG) {
log(LOG_DEBUG, "pf: pf_map_addr: "
"src tracking (%u) maps ", type);
pf_print_host(&k.addr, 0, af);
addlog(" to ");
pf_print_host(naddr, 0, af);
addlog("\n");
}
if (sns[type]->kif != NULL)
rpool->kif = sns[type]->kif;
return (0);
}
uint32_t
pf_rand_addr(uint32_t mask)
{
uint32_t addr;
mask = ~ntohl(mask);
addr = arc4random_uniform(mask + 1);
return (htonl(addr));
}
int
pf_map_addr(sa_family_t af, struct pf_rule *r, struct pf_addr *saddr,
struct pf_addr *naddr, struct pf_addr *init_addr, struct pf_src_node **sns,
struct pf_pool *rpool, enum pf_sn_types type)
{
struct pf_addr hash;
struct pf_addr faddr;
struct pf_addr *raddr = &rpool->addr.v.a.addr;
struct pf_addr *rmask = &rpool->addr.v.a.mask;
struct pfr_ktable *kt;
struct pfi_kif *kif;
u_int64_t states;
u_int16_t weight;
u_int64_t load;
u_int64_t cload;
u_int64_t hashidx;
int cnt;
if (sns[type] == NULL && rpool->opts & PF_POOL_STICKYADDR &&
(rpool->opts & PF_POOL_TYPEMASK) != PF_POOL_NONE &&
pf_map_addr_sticky(af, r, saddr, naddr, sns, rpool, type) == 0)
return (0);
if (rpool->addr.type == PF_ADDR_NOROUTE)
return (1);
if (rpool->addr.type == PF_ADDR_DYNIFTL) {
switch (af) {
case AF_INET:
if (rpool->addr.p.dyn->pfid_acnt4 < 1 &&
!PF_POOL_DYNTYPE(rpool->opts))
return (1);
raddr = &rpool->addr.p.dyn->pfid_addr4;
rmask = &rpool->addr.p.dyn->pfid_mask4;
break;
#ifdef INET6
case AF_INET6:
if (rpool->addr.p.dyn->pfid_acnt6 < 1 &&
!PF_POOL_DYNTYPE(rpool->opts))
return (1);
raddr = &rpool->addr.p.dyn->pfid_addr6;
rmask = &rpool->addr.p.dyn->pfid_mask6;
break;
#endif /* INET6 */
default:
unhandled_af(af);
}
} else if (rpool->addr.type == PF_ADDR_TABLE) {
if (!PF_POOL_DYNTYPE(rpool->opts))
return (1); /* unsupported */
} else {
raddr = &rpool->addr.v.a.addr;
rmask = &rpool->addr.v.a.mask;
}
switch (rpool->opts & PF_POOL_TYPEMASK) {
case PF_POOL_NONE:
pf_addrcpy(naddr, raddr, af);
break;
case PF_POOL_BITMASK:
pf_poolmask(naddr, raddr, rmask, saddr, af);
break;
case PF_POOL_RANDOM:
if (rpool->addr.type == PF_ADDR_TABLE ||
rpool->addr.type == PF_ADDR_DYNIFTL) {
if (rpool->addr.type == PF_ADDR_TABLE)
kt = rpool->addr.p.tbl;
else
kt = rpool->addr.p.dyn->pfid_kt;
kt = pfr_ktable_select_active(kt);
if (kt == NULL)
return (1);
cnt = kt->pfrkt_cnt;
if (cnt == 0)
rpool->tblidx = 0;
else
rpool->tblidx = (int)arc4random_uniform(cnt);
memset(&rpool->counter, 0, sizeof(rpool->counter));
if (pfr_pool_get(rpool, &raddr, &rmask, af))
return (1);
pf_addrcpy(naddr, &rpool->counter, af);
} else if (init_addr != NULL && PF_AZERO(init_addr, af)) {
switch (af) {
case AF_INET:
rpool->counter.addr32[0] = pf_rand_addr(
rmask->addr32[0]);
break;
#ifdef INET6
case AF_INET6:
if (rmask->addr32[3] != 0xffffffff)
rpool->counter.addr32[3] = pf_rand_addr(
rmask->addr32[3]);
else
break;
if (rmask->addr32[2] != 0xffffffff)
rpool->counter.addr32[2] = pf_rand_addr(
rmask->addr32[2]);
else
break;
if (rmask->addr32[1] != 0xffffffff)
rpool->counter.addr32[1] = pf_rand_addr(
rmask->addr32[1]);
else
break;
if (rmask->addr32[0] != 0xffffffff)
rpool->counter.addr32[0] = pf_rand_addr(
rmask->addr32[0]);
break;
#endif /* INET6 */
default:
unhandled_af(af);
}
pf_poolmask(naddr, raddr, rmask, &rpool->counter, af);
pf_addrcpy(init_addr, naddr, af);
} else {
pf_addr_inc(&rpool->counter, af);
pf_poolmask(naddr, raddr, rmask, &rpool->counter, af);
}
break;
case PF_POOL_SRCHASH:
hashidx = pf_hash(saddr, &hash, &rpool->key, af);
if (rpool->addr.type == PF_ADDR_TABLE ||
rpool->addr.type == PF_ADDR_DYNIFTL) {
if (rpool->addr.type == PF_ADDR_TABLE)
kt = rpool->addr.p.tbl;
else
kt = rpool->addr.p.dyn->pfid_kt;
kt = pfr_ktable_select_active(kt);
if (kt == NULL)
return (1);
cnt = kt->pfrkt_cnt;
if (cnt == 0)
rpool->tblidx = 0;
else
rpool->tblidx = (int)(hashidx % cnt);
memset(&rpool->counter, 0, sizeof(rpool->counter));
if (pfr_pool_get(rpool, &raddr, &rmask, af))
return (1);
pf_addrcpy(naddr, &rpool->counter, af);
} else {
pf_poolmask(naddr, raddr, rmask, &hash, af);
}
break;
case PF_POOL_ROUNDROBIN:
if (rpool->addr.type == PF_ADDR_TABLE ||
rpool->addr.type == PF_ADDR_DYNIFTL) {
if (pfr_pool_get(rpool, &raddr, &rmask, af)) {
/*
* reset counter in case its value
* has been removed from the pool.
*/
memset(&rpool->counter, 0,
sizeof(rpool->counter));
if (pfr_pool_get(rpool, &raddr, &rmask, af))
return (1);
}
} else if (PF_AZERO(&rpool->counter, af)) {
/*
* fall back to POOL_NONE if there is a single host
* address in pool.
*/
if (af == AF_INET &&
rmask->addr32[0] == INADDR_BROADCAST) {
pf_addrcpy(naddr, raddr, af);
break;
}
#ifdef INET6
if (af == AF_INET6 &&
IN6_ARE_ADDR_EQUAL(&rmask->v6, &in6mask128)) {
pf_addrcpy(naddr, raddr, af);
break;
}
#endif
} else if (pf_match_addr(0, raddr, rmask, &rpool->counter, af))
return (1);
/* iterate over table if it contains entries which are weighted */
if ((rpool->addr.type == PF_ADDR_TABLE &&
rpool->addr.p.tbl->pfrkt_refcntcost > 0) ||
(rpool->addr.type == PF_ADDR_DYNIFTL &&
rpool->addr.p.dyn->pfid_kt->pfrkt_refcntcost > 0)) {
do {
if (rpool->addr.type == PF_ADDR_TABLE ||
rpool->addr.type == PF_ADDR_DYNIFTL) {
if (pfr_pool_get(rpool,
&raddr, &rmask, af))
return (1);
} else {
log(LOG_ERR, "pf: pf_map_addr: "
"weighted RR failure");
return (1);
}
if (rpool->weight >= rpool->curweight)
break;
pf_addr_inc(&rpool->counter, af);
} while (1);
weight = rpool->weight;
}
pf_poolmask(naddr, raddr, rmask, &rpool->counter, af);
if (init_addr != NULL && PF_AZERO(init_addr, af))
pf_addrcpy(init_addr, &rpool->counter, af);
pf_addr_inc(&rpool->counter, af);
break;
case PF_POOL_LEASTSTATES:
/* retrieve an address first */
if (rpool->addr.type == PF_ADDR_TABLE ||
rpool->addr.type == PF_ADDR_DYNIFTL) {
if (pfr_pool_get(rpool, &raddr, &rmask, af)) {
/* see PF_POOL_ROUNDROBIN */
memset(&rpool->counter, 0,
sizeof(rpool->counter));
if (pfr_pool_get(rpool, &raddr, &rmask, af))
return (1);
}
} else if (pf_match_addr(0, raddr, rmask, &rpool->counter, af))
return (1);
states = rpool->states;
weight = rpool->weight;
kif = rpool->kif;
if ((rpool->addr.type == PF_ADDR_TABLE &&
rpool->addr.p.tbl->pfrkt_refcntcost > 0) ||
(rpool->addr.type == PF_ADDR_DYNIFTL &&
rpool->addr.p.dyn->pfid_kt->pfrkt_refcntcost > 0))
load = ((UINT16_MAX * rpool->states) / rpool->weight);
else
load = states;
pf_addrcpy(&faddr, &rpool->counter, af);
pf_addrcpy(naddr, &rpool->counter, af);
if (init_addr != NULL && PF_AZERO(init_addr, af))
pf_addrcpy(init_addr, naddr, af);
/*
* iterate *once* over whole table and find destination with
* least connection
*/
do {
pf_addr_inc(&rpool->counter, af);
if (rpool->addr.type == PF_ADDR_TABLE ||
rpool->addr.type == PF_ADDR_DYNIFTL) {
if (pfr_pool_get(rpool, &raddr, &rmask, af))
return (1);
} else if (pf_match_addr(0, raddr, rmask,
&rpool->counter, af))
return (1);
if ((rpool->addr.type == PF_ADDR_TABLE &&
rpool->addr.p.tbl->pfrkt_refcntcost > 0) ||
(rpool->addr.type == PF_ADDR_DYNIFTL &&
rpool->addr.p.dyn->pfid_kt->pfrkt_refcntcost > 0))
cload = ((UINT16_MAX * rpool->states)
/ rpool->weight);
else
cload = rpool->states;
/* find lc minimum */
if (cload < load) {
states = rpool->states;
weight = rpool->weight;
kif = rpool->kif;
load = cload;
pf_addrcpy(naddr, &rpool->counter, af);
if (init_addr != NULL &&
PF_AZERO(init_addr, af))
pf_addrcpy(init_addr, naddr, af);
}
} while (pf_match_addr(1, &faddr, rmask, &rpool->counter, af) &&
(states > 0));
if (pf_map_addr_states_increase(af, rpool, naddr) == -1)
return (1);
/* revert the kif which was set by pfr_pool_get() */
rpool->kif = kif;
break;
}
if (rpool->opts & PF_POOL_STICKYADDR) {
if (sns[type] != NULL) {
pf_remove_src_node(sns[type]);
sns[type] = NULL;
}
if (pf_insert_src_node(&sns[type], r, type, af, saddr, naddr,
rpool->kif))
return (1);
}
if (pf_status.debug >= LOG_INFO &&
(rpool->opts & PF_POOL_TYPEMASK) != PF_POOL_NONE) {
log(LOG_INFO, "pf: pf_map_addr: selected address ");
pf_print_host(naddr, 0, af);
if ((rpool->opts & PF_POOL_TYPEMASK) ==
PF_POOL_LEASTSTATES)
addlog(" with state count %llu", states);
if ((rpool->addr.type == PF_ADDR_TABLE &&
rpool->addr.p.tbl->pfrkt_refcntcost > 0) ||
(rpool->addr.type == PF_ADDR_DYNIFTL &&
rpool->addr.p.dyn->pfid_kt->pfrkt_refcntcost > 0))
addlog(" with weight %u", weight);
addlog("\n");
}
return (0);
}
int
pf_map_addr_states_increase(sa_family_t af, struct pf_pool *rpool,
struct pf_addr *naddr)
{
if (rpool->addr.type == PF_ADDR_TABLE) {
if (pfr_states_increase(rpool->addr.p.tbl,
naddr, af) == -1) {
if (pf_status.debug >= LOG_DEBUG) {
log(LOG_DEBUG,
"pf: pf_map_addr_states_increase: "
"selected address ");
pf_print_host(naddr, 0, af);
addlog(". Failed to increase count!\n");
}
return (-1);
}
} else if (rpool->addr.type == PF_ADDR_DYNIFTL) {
if (pfr_states_increase(rpool->addr.p.dyn->pfid_kt,
naddr, af) == -1) {
if (pf_status.debug >= LOG_DEBUG) {
log(LOG_DEBUG,
"pf: pf_map_addr_states_increase: "
"selected address ");
pf_print_host(naddr, 0, af);
addlog(". Failed to increase count!\n");
}
return (-1);
}
}
return (0);
}
int
pf_get_transaddr(struct pf_rule *r, struct pf_pdesc *pd,
struct pf_src_node **sns, struct pf_rule **nr)
{
struct pf_addr naddr;
u_int16_t nport;
#ifdef INET6
if (pd->af != pd->naf)
return (pf_get_transaddr_af(r, pd, sns));
#endif /* INET6 */
if (r->nat.addr.type != PF_ADDR_NONE) {
/* XXX is this right? what if rtable is changed at the same
* XXX time? where do I need to figure out the sport? */
nport = 0;
if (pf_get_sport(pd, r, &naddr, &nport,
r->nat.proxy_port[0], r->nat.proxy_port[1], sns)) {
DPFPRINTF(LOG_NOTICE,
"pf: NAT proxy port allocation (%u-%u) failed",
r->nat.proxy_port[0],
r->nat.proxy_port[1]);
return (-1);
}
/* decrease least-connection state counter of the previous */
if ((*nr) != NULL && (*nr)->nat.addr.type != PF_ADDR_NONE &&
((*nr)->nat.opts & PF_POOL_TYPEMASK) ==
PF_POOL_LEASTSTATES)
pf_pool_states_decrease_addr(&(*nr)->nat, pd->af,
&pd->nsaddr);
*nr = r;
pf_addrcpy(&pd->nsaddr, &naddr, pd->af);
pd->nsport = nport;
}
if (r->rdr.addr.type != PF_ADDR_NONE) {
if (pf_map_addr(pd->af, r, &pd->nsaddr, &naddr, NULL, sns,
&r->rdr, PF_SN_RDR))
return (-1);
if ((r->rdr.opts & PF_POOL_TYPEMASK) == PF_POOL_BITMASK)
pf_poolmask(&naddr, &naddr, &r->rdr.addr.v.a.mask,
&pd->ndaddr, pd->af);
nport = 0;
if (r->rdr.proxy_port[1]) {
u_int32_t tmp_nport;
u_int16_t div;
div = r->rdr.proxy_port[1] - r->rdr.proxy_port[0] + 1;
div = (div == 0) ? 1 : div;
tmp_nport = ((ntohs(pd->ndport) - ntohs(r->dst.port[0])) % div) +
r->rdr.proxy_port[0];
/* wrap around if necessary */
if (tmp_nport > 65535)
tmp_nport -= 65535;
nport = htons((u_int16_t)tmp_nport);
} else if (r->rdr.proxy_port[0])
nport = htons(r->rdr.proxy_port[0]);
/* decrease least-connection state counter of the previous */
if ((*nr) != NULL && (*nr)->rdr.addr.type != PF_ADDR_NONE &&
((*nr)->rdr.opts & PF_POOL_TYPEMASK) ==
PF_POOL_LEASTSTATES)
pf_pool_states_decrease_addr(&(*nr)->rdr, pd->af,
&pd->ndaddr);
*nr = r;
pf_addrcpy(&pd->ndaddr, &naddr, pd->af);
if (nport)
pd->ndport = nport;
}
return (0);
}
#ifdef INET6
int
pf_get_transaddr_af(struct pf_rule *r, struct pf_pdesc *pd,
struct pf_src_node **sns)
{
struct pf_addr ndaddr, nsaddr, naddr;
u_int16_t nport;
int prefixlen = 96;
if (pf_status.debug >= LOG_INFO) {
log(LOG_INFO, "pf: af-to %s %s, ",
pd->naf == AF_INET ? "inet" : "inet6",
r->rdr.addr.type == PF_ADDR_NONE ? "nat" : "rdr");
pf_print_host(&pd->nsaddr, pd->nsport, pd->af);
addlog(" -> ");
pf_print_host(&pd->ndaddr, pd->ndport, pd->af);
addlog("\n");
}
if (r->nat.addr.type == PF_ADDR_NONE)
panic("pf_get_transaddr_af: no nat pool for source address");
/* get source address and port */
nport = 0;
if (pf_get_sport(pd, r, &nsaddr, &nport,
r->nat.proxy_port[0], r->nat.proxy_port[1], sns)) {
DPFPRINTF(LOG_NOTICE,
"pf: af-to NAT proxy port allocation (%u-%u) failed",
r->nat.proxy_port[0],
r->nat.proxy_port[1]);
return (-1);
}
pd->nsport = nport;
if (pd->proto == IPPROTO_ICMPV6 && pd->naf == AF_INET) {
if (pd->dir == PF_IN) {
pd->ndport = ntohs(pd->ndport);
if (pd->ndport == ICMP6_ECHO_REQUEST)
pd->ndport = ICMP_ECHO;
else if (pd->ndport == ICMP6_ECHO_REPLY)
pd->ndport = ICMP_ECHOREPLY;
pd->ndport = htons(pd->ndport);
} else {
pd->nsport = ntohs(pd->nsport);
if (pd->nsport == ICMP6_ECHO_REQUEST)
pd->nsport = ICMP_ECHO;
else if (pd->nsport == ICMP6_ECHO_REPLY)
pd->nsport = ICMP_ECHOREPLY;
pd->nsport = htons(pd->nsport);
}
} else if (pd->proto == IPPROTO_ICMP && pd->naf == AF_INET6) {
if (pd->dir == PF_IN) {
pd->ndport = ntohs(pd->ndport);
if (pd->ndport == ICMP_ECHO)
pd->ndport = ICMP6_ECHO_REQUEST;
else if (pd->ndport == ICMP_ECHOREPLY)
pd->ndport = ICMP6_ECHO_REPLY;
pd->ndport = htons(pd->ndport);
} else {
pd->nsport = ntohs(pd->nsport);
if (pd->nsport == ICMP_ECHO)
pd->nsport = ICMP6_ECHO_REQUEST;
else if (pd->nsport == ICMP_ECHOREPLY)
pd->nsport = ICMP6_ECHO_REPLY;
pd->nsport = htons(pd->nsport);
}
}
/* get the destination address and port */
if (r->rdr.addr.type != PF_ADDR_NONE) {
if (pf_map_addr(pd->naf, r, &nsaddr, &naddr, NULL, sns,
&r->rdr, PF_SN_RDR))
return (-1);
if (r->rdr.proxy_port[0])
pd->ndport = htons(r->rdr.proxy_port[0]);
if (pd->naf == AF_INET) {
/* The prefix is the IPv4 rdr address */
prefixlen = in_mask2len((struct in_addr *)
&r->rdr.addr.v.a.mask);
inet_nat46(pd->naf, &pd->ndaddr,
&ndaddr, &naddr, prefixlen);
} else {
/* The prefix is the IPv6 rdr address */
prefixlen =
in6_mask2len((struct in6_addr *)
&r->rdr.addr.v.a.mask, NULL);
inet_nat64(pd->naf, &pd->ndaddr,
&ndaddr, &naddr, prefixlen);
}
} else {
if (pd->naf == AF_INET) {
/* The prefix is the IPv6 dst address */
prefixlen =
in6_mask2len((struct in6_addr *)
&r->dst.addr.v.a.mask, NULL);
if (prefixlen < 32)
prefixlen = 96;
inet_nat64(pd->naf, &pd->ndaddr,
&ndaddr, &pd->ndaddr, prefixlen);
} else {
/*
* The prefix is the IPv6 nat address
* (that was stored in pd->nsaddr)
*/
prefixlen = in6_mask2len((struct in6_addr *)
&r->nat.addr.v.a.mask, NULL);
if (prefixlen > 96)
prefixlen = 96;
inet_nat64(pd->naf, &pd->ndaddr,
&ndaddr, &nsaddr, prefixlen);
}
}
pf_addrcpy(&pd->nsaddr, &nsaddr, pd->naf);
pf_addrcpy(&pd->ndaddr, &ndaddr, pd->naf);
if (pf_status.debug >= LOG_INFO) {
log(LOG_INFO, "pf: af-to %s %s done, prefixlen %d, ",
pd->naf == AF_INET ? "inet" : "inet6",
r->rdr.addr.type == PF_ADDR_NONE ? "nat" : "rdr",
prefixlen);
pf_print_host(&pd->nsaddr, pd->nsport, pd->naf);
addlog(" -> ");
pf_print_host(&pd->ndaddr, pd->ndport, pd->naf);
addlog("\n");
}
return (0);
}
#endif /* INET6 */
int
pf_postprocess_addr(struct pf_state *cur)
{
struct pf_state_key *sks;
struct pf_addr *addr;
struct pf_pool *rpool, *natpl = NULL, *rdrpl = NULL;
struct pf_rule_item *ri;
int ret = 0;
/* decrease the states counter in pool for "least-states" */
if (cur->natrule.ptr != NULL) { /* this is the final */
if (cur->natrule.ptr->nat.addr.type != PF_ADDR_NONE)
natpl = &cur->natrule.ptr->nat;
if (cur->natrule.ptr->rdr.addr.type != PF_ADDR_NONE)
rdrpl = &cur->natrule.ptr->rdr;
}
if (natpl == NULL || rdrpl == NULL) {
/* nat or rdr might be done by a previous "match" */
SLIST_FOREACH(ri, &cur->match_rules, entry) {
/* first match since the list order is reversed */
if (natpl == NULL &&
ri->r->nat.addr.type != PF_ADDR_NONE)
natpl = &ri->r->nat;
if (rdrpl == NULL &&
ri->r->rdr.addr.type != PF_ADDR_NONE)
rdrpl = &ri->r->rdr;
if (natpl != NULL && rdrpl != NULL)
break;
}
}
if (natpl != NULL &&
(natpl->opts & PF_POOL_TYPEMASK) == PF_POOL_LEASTSTATES) {
/* nat-to <table> least-state */
if (cur->direction == PF_IN) {
sks = cur->key[PF_SK_STACK];
addr = &sks->addr[0];
} else {
sks = cur->key[PF_SK_WIRE];
addr = &sks->addr[1];
}
if (pf_pool_states_decrease_addr(natpl, sks->af, addr) != 0)
ret = 1;
}
if (rdrpl != NULL &&
(rdrpl->opts & PF_POOL_TYPEMASK) == PF_POOL_LEASTSTATES) {
/* rdr <table> least-state */
if (cur->direction == PF_IN) {
sks = cur->key[PF_SK_STACK];
addr = &sks->addr[1];
} else {
sks = cur->key[PF_SK_WIRE];
addr = &sks->addr[0];
}
if (pf_pool_states_decrease_addr(rdrpl, sks->af, addr) != 0)
ret = 1;
}
if (cur->rule.ptr != NULL) {
/* route-to <table> least-state */
rpool = &cur->rule.ptr->route;
if (rpool->addr.type != PF_ADDR_NONE &&
(rpool->opts & PF_POOL_TYPEMASK) == PF_POOL_LEASTSTATES) {
sks = cur->key[(cur->direction == PF_IN)
? PF_SK_STACK : PF_SK_WIRE];
addr = &cur->rt_addr;
KASSERT(sks != NULL);
if (pf_pool_states_decrease_addr(rpool, sks->af, addr)
!= 0)
ret = 1;
}
}
return (ret);
}
int
pf_pool_states_decrease_addr(struct pf_pool *rpool, int af,
struct pf_addr *addr)
{
int slbcount = -1;
if (rpool->addr.type == PF_ADDR_TABLE) {
if ((slbcount = pfr_states_decrease(
rpool->addr.p.tbl, addr, af)) == -1) {
if (pf_status.debug >= LOG_DEBUG) {
log(LOG_DEBUG, "pf: %s: selected address ",
__func__);
pf_print_host(addr, 0, af);
addlog(". Failed to "
"decrease count!\n");
}
return (1);
}
} else if (rpool->addr.type == PF_ADDR_DYNIFTL) {
if ((slbcount = pfr_states_decrease(
rpool->addr.p.dyn->pfid_kt, addr, af)) == -1) {
if (pf_status.debug >= LOG_DEBUG) {
log(LOG_DEBUG, "pf: %s: selected address ",
__func__);
pf_print_host(addr, 0, af);
addlog(". Failed to "
"decrease count!\n");
}
return (1);
}
}
if (slbcount > -1) {
if (pf_status.debug >= LOG_INFO) {
log(LOG_INFO, "pf: %s: selected address ", __func__);
pf_print_host(addr, 0, af);
addlog(" decreased state count to %u\n",
slbcount);
}
}
return (0);
}
File diff suppressed because it is too large. Load diff
+560
View File
@@ -0,0 +1,560 @@
/* PFLINUX port of OpenBSD 8.0 net/pf_osfp.c — see docs/porting-notes.md */
#include <pfk.h>
#endif /* PFDEBUG */
#endif /* _KERNEL */
/*
* Protection/ownership:
* I immutable after pf_osfp_initialize()
* p pf_lock
*/
SLIST_HEAD(pf_osfp_list, pf_os_fingerprint) pf_osfp_list =
SLIST_HEAD_INITIALIZER(pf_osfp_list); /* [p] */
pool_t pf_osfp_entry_pl; /* [I] */
pool_t pf_osfp_pl; /* [I] */
struct pf_os_fingerprint *pf_osfp_find(struct pf_os_fingerprint *,
u_int8_t);
struct pf_os_fingerprint *pf_osfp_find_exact(struct pf_os_fingerprint *);
void pf_osfp_insert(struct pf_os_fingerprint *);
#ifdef _KERNEL
/*
* Passively fingerprint the OS of the host (IPv4 TCP SYN packets only)
* Returns the list of possible OSes.
*/
struct pf_osfp_enlist *
pf_osfp_fingerprint(struct pf_pdesc *pd)
{
struct pf_tcphdr *th = &pd->hdr.tcp;
struct pf_ip *ip = NULL;
struct pf_ip6_hdr *ip6 = NULL;
char hdr[60];
if (pd->proto != IPPROTO_TCP)
return (NULL);
switch (pd->af) {
case AF_INET:
ip = mtod(pd->m, struct pf_ip *);
break;
case AF_INET6:
ip6 = mtod(pd->m, struct pf_ip6_hdr *);
break;
}
if (!pf_pull_hdr(pd->m, pd->off, hdr, th->th_off << 2, NULL, pd->af))
return (NULL);
return (pf_osfp_fingerprint_hdr(ip, ip6, (struct pf_tcphdr *)hdr));
}
#endif /* _KERNEL */
struct pf_osfp_enlist *
pf_osfp_fingerprint_hdr(const struct pf_ip *ip, const struct pf_ip6_hdr *ip6,
const struct pf_tcphdr *tcp)
{
struct pf_os_fingerprint fp, *fpresult;
int cnt, optlen = 0;
const u_int8_t *optp;
#ifdef _KERNEL
char srcname[128];
#else /* !_KERNEL */
char srcname[NI_MAXHOST];
#endif /* _KERNEL */
if ((tcp->th_flags & (TH_SYN|TH_ACK)) != TH_SYN)
return (NULL);
if (ip) {
if ((ip->ip_off & htons(IP_OFFMASK)) != 0)
return (NULL);
}
memset(&fp, 0, sizeof(fp));
if (ip) {
#ifndef _KERNEL
struct sockaddr_in sin;
#endif /* _KERNEL */
fp.fp_psize = ntohs(ip->ip_len);
fp.fp_ttl = ip->ip_ttl;
if (ip->ip_off & htons(IP_DF))
fp.fp_flags |= PF_OSFP_DF;
#ifdef _KERNEL
inet_ntop(AF_INET, &ip->ip_src, srcname, sizeof(srcname));
#else /* !_KERNEL */
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin./* PFLINUX sin_len */;
sin.sin_addr = ip->ip_src;
(void)getnameinfo((struct sockaddr *)&sin,
sizeof(struct sockaddr_in), srcname, sizeof(srcname),
NULL, 0, NI_NUMERICHOST);
#endif /* _KERNEL */
}
#ifdef INET6
else if (ip6) {
#ifndef _KERNEL
struct sockaddr_in6 sin6;
#endif /* !_KERNEL */
/* jumbo payload? */
fp.fp_psize = sizeof(struct pf_ip6_hdr) + ntohs(ip6->ip6_plen);
fp.fp_ttl = ip6->ip6_hlim;
fp.fp_flags |= PF_OSFP_DF;
fp.fp_flags |= PF_OSFP_INET6;
#ifdef _KERNEL
inet_ntop(AF_INET6, &ip6->ip6_src, srcname, sizeof(srcname));
#else /* !_KERNEL */
memset(&sin6, 0, sizeof(sin6));
sin6.sin6_family = AF_INET6;
sin6.sin6_len = sizeof(struct sockaddr_in6);
sin6.sin6_addr = ip6->ip6_src;
(void)getnameinfo((struct sockaddr *)&sin6,
sizeof(struct sockaddr_in6), srcname, sizeof(srcname),
NULL, 0, NI_NUMERICHOST);
#endif /* !_KERNEL */
}
#endif /* INET6 */
else
return (NULL);
fp.fp_wsize = ntohs(tcp->th_win);
cnt = (tcp->th_off << 2) - sizeof(*tcp);
optp = (const u_int8_t *)((const char *)tcp + sizeof(*tcp));
for (; cnt > 0; cnt -= optlen, optp += optlen) {
if (*optp == TCPOPT_EOL)
break;
fp.fp_optcnt++;
if (*optp == TCPOPT_NOP) {
fp.fp_tcpopts = (fp.fp_tcpopts << PF_OSFP_TCPOPT_BITS) |
PF_OSFP_TCPOPT_NOP;
optlen = 1;
} else {
if (cnt < 2)
return (NULL);
optlen = optp[1];
if (optlen > cnt || optlen < 2)
return (NULL);
switch (*optp) {
case TCPOPT_MAXSEG:
if (optlen >= TCPOLEN_MAXSEG)
memcpy(&fp.fp_mss, &optp[2],
sizeof(fp.fp_mss));
fp.fp_tcpopts = (fp.fp_tcpopts <<
PF_OSFP_TCPOPT_BITS) | PF_OSFP_TCPOPT_MSS;
fp.fp_mss = ntohs(fp.fp_mss);
break;
case TCPOPT_WINDOW:
if (optlen >= TCPOLEN_WINDOW)
memcpy(&fp.fp_wscale, &optp[2],
sizeof(fp.fp_wscale));
fp.fp_tcpopts = (fp.fp_tcpopts <<
PF_OSFP_TCPOPT_BITS) |
PF_OSFP_TCPOPT_WSCALE;
break;
case TCPOPT_SACK_PERMITTED:
fp.fp_tcpopts = (fp.fp_tcpopts <<
PF_OSFP_TCPOPT_BITS) | PF_OSFP_TCPOPT_SACK;
break;
case TCPOPT_TIMESTAMP:
if (optlen >= TCPOLEN_TIMESTAMP) {
u_int32_t ts;
memcpy(&ts, &optp[2], sizeof(ts));
if (ts == 0)
fp.fp_flags |= PF_OSFP_TS0;
}
fp.fp_tcpopts = (fp.fp_tcpopts <<
PF_OSFP_TCPOPT_BITS) | PF_OSFP_TCPOPT_TS;
break;
default:
return (NULL);
}
}
optlen = MAX(optlen, 1); /* paranoia */
}
DPFPRINTF(LOG_INFO,
"fingerprinted %s:%d %d:%d:%d:%d:%llx (%d) "
"(TS=%s,M=%s%d,W=%s%d)",
srcname, ntohs(tcp->th_sport),
fp.fp_wsize, fp.fp_ttl, (fp.fp_flags & PF_OSFP_DF) != 0,
fp.fp_psize, (long long int)fp.fp_tcpopts, fp.fp_optcnt,
(fp.fp_flags & PF_OSFP_TS0) ? "0" : "",
(fp.fp_flags & PF_OSFP_MSS_MOD) ? "%" :
(fp.fp_flags & PF_OSFP_MSS_DC) ? "*" : "",
fp.fp_mss,
(fp.fp_flags & PF_OSFP_WSCALE_MOD) ? "%" :
(fp.fp_flags & PF_OSFP_WSCALE_DC) ? "*" : "",
fp.fp_wscale);
if ((fpresult = pf_osfp_find(&fp, PF_OSFP_MAXTTL_OFFSET)))
return (&fpresult->fp_oses);
return (NULL);
}
/* Match a fingerprint ID against a list of OSes */
int
pf_osfp_match(struct pf_osfp_enlist *list, pf_osfp_t os)
{
struct pf_osfp_entry *entry;
int os_class, os_version, os_subtype;
int en_class, en_version, en_subtype;
if (os == PF_OSFP_ANY)
return (1);
if (list == NULL) {
DPFPRINTF(LOG_INFO, "osfp no match against %x", os);
return (os == PF_OSFP_UNKNOWN);
}
PF_OSFP_UNPACK(os, os_class, os_version, os_subtype);
SLIST_FOREACH(entry, list, fp_entry) {
PF_OSFP_UNPACK(entry->fp_os, en_class, en_version, en_subtype);
if ((os_class == PF_OSFP_ANY || en_class == os_class) &&
(os_version == PF_OSFP_ANY || en_version == os_version) &&
(os_subtype == PF_OSFP_ANY || en_subtype == os_subtype)) {
DPFPRINTF(LOG_INFO,
"osfp matched %s %s %s %x==%x",
entry->fp_class_nm, entry->fp_version_nm,
entry->fp_subtype_nm, os, entry->fp_os);
return (1);
}
}
DPFPRINTF(LOG_INFO, "fingerprint 0x%x didn't match", os);
return (0);
}
/* Initialize the OS fingerprint system */
void
pf_osfp_initialize(void)
{
pool_init(&pf_osfp_entry_pl, sizeof(struct pf_osfp_entry), 0,
IPL_NONE, PR_WAITOK, "pfosfpen", NULL);
pool_init(&pf_osfp_pl, sizeof(struct pf_os_fingerprint), 0,
IPL_NONE, PR_WAITOK, "pfosfp", NULL);
}
/* Flush the fingerprint list */
void
pf_osfp_flush(void)
{
struct pf_os_fingerprint *fp;
struct pf_osfp_entry *entry;
PF_LOCK();
while ((fp = SLIST_FIRST(&pf_osfp_list))) {
SLIST_REMOVE_HEAD(&pf_osfp_list, fp_next);
while ((entry = SLIST_FIRST(&fp->fp_oses))) {
SLIST_REMOVE_HEAD(&fp->fp_oses, fp_entry);
pool_put(&pf_osfp_entry_pl, entry);
}
pool_put(&pf_osfp_pl, fp);
}
PF_UNLOCK();
}
/* Add a fingerprint */
int
pf_osfp_add(struct pf_osfp_ioctl *fpioc)
{
struct pf_os_fingerprint *fp, *fp_prealloc, fpadd;
struct pf_osfp_entry *entry;
if (strnlen(fpioc->fp_os.fp_class_nm, sizeof(fpioc->fp_os.fp_class_nm)) >=
sizeof(fpioc->fp_os.fp_class_nm))
return ENAMETOOLONG;
if (strnlen(fpioc->fp_os.fp_version_nm, sizeof(fpioc->fp_os.fp_version_nm)) >=
sizeof(fpioc->fp_os.fp_version_nm))
return ENAMETOOLONG;
if (strnlen(fpioc->fp_os.fp_subtype_nm, sizeof(fpioc->fp_os.fp_subtype_nm)) >=
sizeof(fpioc->fp_os.fp_subtype_nm))
return ENAMETOOLONG;
memset(&fpadd, 0, sizeof(fpadd));
fpadd.fp_tcpopts = fpioc->fp_tcpopts;
fpadd.fp_wsize = fpioc->fp_wsize;
fpadd.fp_psize = fpioc->fp_psize;
fpadd.fp_mss = fpioc->fp_mss;
fpadd.fp_flags = fpioc->fp_flags;
fpadd.fp_optcnt = fpioc->fp_optcnt;
fpadd.fp_wscale = fpioc->fp_wscale;
fpadd.fp_ttl = fpioc->fp_ttl;
DPFPRINTF(LOG_DEBUG,
"adding osfp %s %s %s = %s%d:%d:%d:%s%d:0x%llx %d "
"(TS=%s,M=%s%d,W=%s%d) %x",
fpioc->fp_os.fp_class_nm, fpioc->fp_os.fp_version_nm,
fpioc->fp_os.fp_subtype_nm,
(fpadd.fp_flags & PF_OSFP_WSIZE_MOD) ? "%" :
(fpadd.fp_flags & PF_OSFP_WSIZE_MSS) ? "S" :
(fpadd.fp_flags & PF_OSFP_WSIZE_MTU) ? "T" :
(fpadd.fp_flags & PF_OSFP_WSIZE_DC) ? "*" : "",
fpadd.fp_wsize,
fpadd.fp_ttl,
(fpadd.fp_flags & PF_OSFP_DF) ? 1 : 0,
(fpadd.fp_flags & PF_OSFP_PSIZE_MOD) ? "%" :
(fpadd.fp_flags & PF_OSFP_PSIZE_DC) ? "*" : "",
fpadd.fp_psize,
(long long int)fpadd.fp_tcpopts, fpadd.fp_optcnt,
(fpadd.fp_flags & PF_OSFP_TS0) ? "0" : "",
(fpadd.fp_flags & PF_OSFP_MSS_MOD) ? "%" :
(fpadd.fp_flags & PF_OSFP_MSS_DC) ? "*" : "",
fpadd.fp_mss,
(fpadd.fp_flags & PF_OSFP_WSCALE_MOD) ? "%" :
(fpadd.fp_flags & PF_OSFP_WSCALE_DC) ? "*" : "",
fpadd.fp_wscale,
fpioc->fp_os.fp_os);
entry = pool_get(&pf_osfp_entry_pl, PR_WAITOK|PR_LIMITFAIL);
if (entry == NULL)
return (ENOMEM);
fp_prealloc = pool_get(&pf_osfp_pl, PR_WAITOK|PR_ZERO|PR_LIMITFAIL);
if (fp_prealloc == NULL) {
pool_put(&pf_osfp_entry_pl, entry);
return (ENOMEM);
}
PF_LOCK();
if ((fp = pf_osfp_find_exact(&fpadd))) {
struct pf_osfp_entry *tentry;
SLIST_FOREACH(tentry, &fp->fp_oses, fp_entry) {
if (PF_OSFP_ENTRY_EQ(tentry, &fpioc->fp_os)) {
PF_UNLOCK();
pool_put(&pf_osfp_entry_pl, entry);
pool_put(&pf_osfp_pl, fp_prealloc);
return (EEXIST);
}
}
} else {
fp = fp_prealloc;
fp_prealloc = NULL;
fp->fp_tcpopts = fpioc->fp_tcpopts;
fp->fp_wsize = fpioc->fp_wsize;
fp->fp_psize = fpioc->fp_psize;
fp->fp_mss = fpioc->fp_mss;
fp->fp_flags = fpioc->fp_flags;
fp->fp_optcnt = fpioc->fp_optcnt;
fp->fp_wscale = fpioc->fp_wscale;
fp->fp_ttl = fpioc->fp_ttl;
SLIST_INIT(&fp->fp_oses);
pf_osfp_insert(fp);
}
memcpy(entry, &fpioc->fp_os, sizeof(*entry));
SLIST_INSERT_HEAD(&fp->fp_oses, entry, fp_entry);
PF_UNLOCK();
#ifdef PFDEBUG
if ((fp = pf_osfp_validate()))
DPFPRINTF(LOG_NOTICE,
"Invalid fingerprint list");
#endif /* PFDEBUG */
if (fp_prealloc != NULL)
pool_put(&pf_osfp_pl, fp_prealloc);
return (0);
}
/* Find a fingerprint in the list */
struct pf_os_fingerprint *
pf_osfp_find(struct pf_os_fingerprint *find, u_int8_t ttldiff)
{
struct pf_os_fingerprint *f;
PF_ASSERT_LOCKED();
#define MATCH_INT(_MOD, _DC, _field) \
if ((f->fp_flags & _DC) == 0) { \
if ((f->fp_flags & _MOD) == 0) { \
if (f->_field != find->_field) \
continue; \
} else { \
if (f->_field == 0 || find->_field % f->_field) \
continue; \
} \
}
SLIST_FOREACH(f, &pf_osfp_list, fp_next) {
if (f->fp_tcpopts != find->fp_tcpopts ||
f->fp_optcnt != find->fp_optcnt ||
f->fp_ttl < find->fp_ttl ||
f->fp_ttl - find->fp_ttl > ttldiff ||
(f->fp_flags & (PF_OSFP_DF|PF_OSFP_TS0)) !=
(find->fp_flags & (PF_OSFP_DF|PF_OSFP_TS0)))
continue;
MATCH_INT(PF_OSFP_PSIZE_MOD, PF_OSFP_PSIZE_DC, fp_psize)
MATCH_INT(PF_OSFP_MSS_MOD, PF_OSFP_MSS_DC, fp_mss)
MATCH_INT(PF_OSFP_WSCALE_MOD, PF_OSFP_WSCALE_DC, fp_wscale)
if ((f->fp_flags & PF_OSFP_WSIZE_DC) == 0) {
if (f->fp_flags & PF_OSFP_WSIZE_MSS) {
if (find->fp_mss == 0)
continue;
/* Some "smart" NAT devices and DSL routers will tweak the MSS size and
* will set it to whatever is suitable for the link type.
*/
#define SMART_MSS 1460
if ((find->fp_wsize % find->fp_mss ||
find->fp_wsize / find->fp_mss !=
f->fp_wsize) &&
(find->fp_wsize % SMART_MSS ||
find->fp_wsize / SMART_MSS !=
f->fp_wsize))
continue;
} else if (f->fp_flags & PF_OSFP_WSIZE_MTU) {
if (find->fp_mss == 0)
continue;
#define MTUOFF (sizeof(struct pf_ip) + sizeof(struct pf_tcphdr))
#define SMART_MTU (SMART_MSS + MTUOFF)
if ((find->fp_wsize % (find->fp_mss + MTUOFF) ||
find->fp_wsize / (find->fp_mss + MTUOFF) !=
f->fp_wsize) &&
(find->fp_wsize % SMART_MTU ||
find->fp_wsize / SMART_MTU !=
f->fp_wsize))
continue;
} else if (f->fp_flags & PF_OSFP_WSIZE_MOD) {
if (f->fp_wsize == 0 || find->fp_wsize %
f->fp_wsize)
continue;
} else {
if (f->fp_wsize != find->fp_wsize)
continue;
}
}
return (f);
}
return (NULL);
}
/* Find an exact fingerprint in the list */
struct pf_os_fingerprint *
pf_osfp_find_exact(struct pf_os_fingerprint *find)
{
struct pf_os_fingerprint *f;
PF_ASSERT_LOCKED();
SLIST_FOREACH(f, &pf_osfp_list, fp_next) {
if (f->fp_tcpopts == find->fp_tcpopts &&
f->fp_wsize == find->fp_wsize &&
f->fp_psize == find->fp_psize &&
f->fp_mss == find->fp_mss &&
f->fp_flags == find->fp_flags &&
f->fp_optcnt == find->fp_optcnt &&
f->fp_wscale == find->fp_wscale &&
f->fp_ttl == find->fp_ttl)
return (f);
}
return (NULL);
}
/* Insert a fingerprint into the list */
void
pf_osfp_insert(struct pf_os_fingerprint *ins)
{
struct pf_os_fingerprint *f, *prev = NULL;
PF_ASSERT_LOCKED();
/* XXX need to go semi tree based. can key on tcp options */
SLIST_FOREACH(f, &pf_osfp_list, fp_next)
prev = f;
if (prev)
SLIST_INSERT_AFTER(prev, ins, fp_next);
else
SLIST_INSERT_HEAD(&pf_osfp_list, ins, fp_next);
}
/* Fill a fingerprint by its number (from an ioctl) */
int
pf_osfp_get(struct pf_osfp_ioctl *fpioc)
{
struct pf_os_fingerprint *fp;
struct pf_osfp_entry *entry;
int num = fpioc->fp_getnum;
int i = 0;
memset(fpioc, 0, sizeof(*fpioc));
PF_LOCK();
SLIST_FOREACH(fp, &pf_osfp_list, fp_next) {
SLIST_FOREACH(entry, &fp->fp_oses, fp_entry) {
if (i++ == num) {
fpioc->fp_mss = fp->fp_mss;
fpioc->fp_wsize = fp->fp_wsize;
fpioc->fp_flags = fp->fp_flags;
fpioc->fp_psize = fp->fp_psize;
fpioc->fp_ttl = fp->fp_ttl;
fpioc->fp_wscale = fp->fp_wscale;
fpioc->fp_getnum = num;
memcpy(&fpioc->fp_os, entry,
sizeof(fpioc->fp_os));
PF_UNLOCK();
return (0);
}
}
}
PF_UNLOCK();
return (EBUSY);
}
/* Validate that each signature is reachable */
struct pf_os_fingerprint *
pf_osfp_validate(void)
{
struct pf_os_fingerprint *f, *f2, find;
PF_ASSERT_LOCKED();
SLIST_FOREACH(f, &pf_osfp_list, fp_next) {
memcpy(&find, f, sizeof(find));
/* We do a few MSS/th_win percolations to make things unique */
if (find.fp_mss == 0)
find.fp_mss = 128;
if (f->fp_flags & PF_OSFP_WSIZE_MSS)
find.fp_wsize *= find.fp_mss;
else if (f->fp_flags & PF_OSFP_WSIZE_MTU)
find.fp_wsize *= (find.fp_mss + 40);
else if (f->fp_flags & PF_OSFP_WSIZE_MOD)
find.fp_wsize *= 2;
if (f != (f2 = pf_osfp_find(&find, 0))) {
if (f2)
DPFPRINTF(LOG_NOTICE,
"Found \"%s %s %s\" instead of "
"\"%s %s %s\"\n",
SLIST_FIRST(&f2->fp_oses)->fp_class_nm,
SLIST_FIRST(&f2->fp_oses)->fp_version_nm,
SLIST_FIRST(&f2->fp_oses)->fp_subtype_nm,
SLIST_FIRST(&f->fp_oses)->fp_class_nm,
SLIST_FIRST(&f->fp_oses)->fp_version_nm,
SLIST_FIRST(&f->fp_oses)->fp_subtype_nm);
else
DPFPRINTF(LOG_NOTICE,
"Couldn't find \"%s %s %s\"\n",
SLIST_FIRST(&f->fp_oses)->fp_class_nm,
SLIST_FIRST(&f->fp_oses)->fp_version_nm,
SLIST_FIRST(&f->fp_oses)->fp_subtype_nm);
return (f);
}
}
return (NULL);
}
+380
View File
@@ -0,0 +1,380 @@
/* PFLINUX port of OpenBSD 8.0 net/pf_ruleset.c — see docs/porting-notes.md */
#include <pfk.h>
#endif /* PFDEBUG */
#endif /* _KERNEL */
struct pf_anchor_global pf_anchors;
struct pf_anchor pf_main_anchor;
static __inline int pf_anchor_compare(struct pf_anchor *, struct pf_anchor *);
RB_GENERATE(pf_anchor_global, pf_anchor, entry_global, pf_anchor_compare);
RB_GENERATE(pf_anchor_node, pf_anchor, entry_node, pf_anchor_compare);
static __inline int
pf_anchor_compare(struct pf_anchor *a, struct pf_anchor *b)
{
int c = strcmp(a->path, b->path);
return (c ? (c < 0 ? -1 : 1) : 0);
}
void
pf_init_ruleset(struct pf_ruleset *ruleset)
{
memset(ruleset, 0, sizeof(struct pf_ruleset));
TAILQ_INIT(&ruleset->rules.queues[0]);
TAILQ_INIT(&ruleset->rules.queues[1]);
ruleset->rules.active.ptr = &ruleset->rules.queues[0];
ruleset->rules.inactive.ptr = &ruleset->rules.queues[1];
}
struct pf_anchor *
pf_find_anchor(const char *path)
{
struct pf_anchor *key, *found;
key = rs_malloc(sizeof(*key));
if (key == NULL)
return (NULL);
strlcpy(key->path, path, sizeof(key->path));
found = RB_FIND(pf_anchor_global, &pf_anchors, key);
rs_free(key, sizeof(*key));
return (found);
}
struct pf_ruleset *
pf_find_ruleset(const char *path)
{
struct pf_anchor *anchor;
while (*path == '/')
path++;
if (!*path)
return (&pf_main_ruleset);
anchor = pf_find_anchor(path);
if (anchor == NULL)
return (NULL);
else
return (&anchor->ruleset);
}
struct pf_ruleset *
pf_get_leaf_ruleset(char *path, char **path_remainder)
{
struct pf_ruleset *ruleset;
char *leaf, *p;
int i = 0;
p = path;
while (*p == '/')
p++;
ruleset = pf_find_ruleset(p);
leaf = p;
while (ruleset == NULL) {
leaf = strrchr(p, '/');
if (leaf != NULL) {
*leaf = '\0';
i++;
ruleset = pf_find_ruleset(p);
} else {
leaf = path;
/*
* if no path component exists, then main ruleset is
* our parent.
*/
ruleset = &pf_main_ruleset;
}
}
if (path_remainder != NULL)
*path_remainder = leaf;
/* restore slashes in path. */
while (i != 0) {
while (*leaf != '\0')
leaf++;
*leaf = '/';
i--;
}
return (ruleset);
}
struct pf_anchor *
pf_create_anchor(struct pf_anchor *parent, const char *aname)
{
struct pf_anchor *anchor, *dup;
if (!*aname || (strlen(aname) >= PF_ANCHOR_NAME_SIZE) ||
((parent != NULL) && (strlen(parent->path) >= PF_ANCHOR_MAXPATH)))
return (NULL);
anchor = rs_pool_get_anchor();
if (anchor == NULL)
return (NULL);
RB_INIT(&anchor->children);
strlcpy(anchor->name, aname, sizeof(anchor->name));
if (parent != NULL) {
/*
* Make sure path for levels 2, 3, ... is terminated by '/':
* 1/2/3/...
*/
strlcpy(anchor->path, parent->path, sizeof(anchor->path));
strlcat(anchor->path, "/", sizeof(anchor->path));
}
strlcat(anchor->path, anchor->name, sizeof(anchor->path));
if ((dup = RB_INSERT(pf_anchor_global, &pf_anchors, anchor)) != NULL) {
DPFPRINTF(LOG_NOTICE,
"%s: RB_INSERT to global '%s' '%s' collides with '%s' '%s'",
__func__, anchor->path, anchor->name, dup->path, dup->name);
rs_pool_put_anchor(anchor);
return (NULL);
}
if (parent != NULL) {
anchor->parent = parent;
dup = RB_INSERT(pf_anchor_node, &parent->children, anchor);
if (dup != NULL) {
DPFPRINTF(LOG_NOTICE,
"%s: RB_INSERT to parent '%s' '%s' collides with "
"'%s' '%s'", __func__, anchor->path, anchor->name,
dup->path, dup->name);
RB_REMOVE(pf_anchor_global, &pf_anchors,
anchor);
rs_pool_put_anchor(anchor);
return (NULL);
}
}
pf_init_ruleset(&anchor->ruleset);
anchor->ruleset.anchor = anchor;
#ifdef _KERNEL
refcnt_init(&anchor->ref);
#endif
return (anchor);
}
struct pf_ruleset *
pf_find_or_create_ruleset(const char *path)
{
char *p, *aname, *r;
struct pf_ruleset *ruleset;
struct pf_anchor *anchor;
if (path[0] == 0)
return (&pf_main_ruleset);
while (*path == '/')
path++;
ruleset = pf_find_ruleset(path);
if (ruleset != NULL)
return (ruleset);
p = rs_malloc(MAXPATHLEN);
if (p == NULL)
return (NULL);
strlcpy(p, path, MAXPATHLEN);
ruleset = pf_get_leaf_ruleset(p, &aname);
anchor = ruleset->anchor;
while (*aname == '/')
aname++;
/*
* aname is a path remainder, which contains nodes we must create. We
* process the aname path from left to right, effectively descending
* from parents to children.
*/
while ((r = strchr(aname, '/')) != NULL || *aname) {
if (r != NULL)
*r = 0;
anchor = pf_create_anchor(anchor, aname);
if (anchor == NULL) {
rs_free(p, MAXPATHLEN);
return (NULL);
}
if (r == NULL)
break;
else
aname = r + 1;
}
rs_free(p, MAXPATHLEN);
return (&anchor->ruleset);
}
void
pf_remove_if_empty_ruleset(struct pf_ruleset *ruleset)
{
struct pf_anchor *parent;
while (ruleset != NULL) {
if (ruleset == &pf_main_ruleset ||
!RB_EMPTY(&ruleset->anchor->children) ||
ruleset->anchor->refcnt > 0 || ruleset->tables > 0 ||
ruleset->topen)
return;
if (!TAILQ_EMPTY(ruleset->rules.active.ptr) ||
!TAILQ_EMPTY(ruleset->rules.inactive.ptr) ||
ruleset->rules.inactive.open)
return;
RB_REMOVE(pf_anchor_global, &pf_anchors, ruleset->anchor);
if ((parent = ruleset->anchor->parent) != NULL)
RB_REMOVE(pf_anchor_node, &parent->children,
ruleset->anchor);
pf_anchor_rele(ruleset->anchor);
if (parent == NULL)
return;
ruleset = &parent->ruleset;
}
}
int
pf_anchor_setup(struct pf_rule *r, const struct pf_ruleset *s,
const char *name)
{
char *p, *path;
struct pf_ruleset *ruleset;
r->anchor = NULL;
r->anchor_relative = 0;
r->anchor_wildcard = 0;
if (!name[0])
return (0);
path = rs_malloc(MAXPATHLEN);
if (path == NULL)
return (1);
if (name[0] == '/')
strlcpy(path, name + 1, MAXPATHLEN);
else {
/* relative path */
r->anchor_relative = 1;
if (s->anchor == NULL || !s->anchor->path[0])
path[0] = 0;
else
strlcpy(path, s->anchor->path, MAXPATHLEN);
while (name[0] == '.' && name[1] == '.' && name[2] == '/') {
if (!path[0]) {
DPFPRINTF(LOG_NOTICE,
"pf_anchor_setup: .. beyond root");
rs_free(path, MAXPATHLEN);
return (1);
}
if ((p = strrchr(path, '/')) != NULL)
*p = 0;
else
path[0] = 0;
r->anchor_relative++;
name += 3;
}
if (path[0])
strlcat(path, "/", MAXPATHLEN);
strlcat(path, name, MAXPATHLEN);
}
if ((p = strrchr(path, '/')) != NULL && !strcmp(p, "/*")) {
r->anchor_wildcard = 1;
*p = 0;
}
ruleset = pf_find_or_create_ruleset(path);
rs_free(path, MAXPATHLEN);
if (ruleset == NULL || ruleset == &pf_main_ruleset) {
DPFPRINTF(LOG_NOTICE,
"pf_anchor_setup: ruleset");
return (1);
}
r->anchor = ruleset->anchor;
r->anchor->refcnt++;
return (0);
}
int
pf_anchor_copyout(const struct pf_ruleset *rs, const struct pf_rule *r,
struct pfioc_rule *pr)
{
pr->anchor_call[0] = 0;
if (r->anchor == NULL)
return (0);
if (!r->anchor_relative) {
strlcpy(pr->anchor_call, "/", sizeof(pr->anchor_call));
strlcat(pr->anchor_call, r->anchor->path,
sizeof(pr->anchor_call));
} else {
char *a, *p;
int i;
a = rs_malloc(MAXPATHLEN);
if (a == NULL)
return (1);
if (rs == &pf_main_ruleset)
a[0] = 0;
else
strlcpy(a, rs->anchor->path, MAXPATHLEN);
for (i = 1; i < r->anchor_relative; ++i) {
if ((p = strrchr(a, '/')) == NULL)
p = a;
*p = 0;
strlcat(pr->anchor_call, "../",
sizeof(pr->anchor_call));
}
if (strncmp(a, r->anchor->path, strlen(a))) {
DPFPRINTF(LOG_NOTICE,
"pf_anchor_copyout: '%s' '%s'", a,
r->anchor->path);
rs_free(a, MAXPATHLEN);
return (1);
}
if (strlen(r->anchor->path) > strlen(a))
strlcat(pr->anchor_call, r->anchor->path + (a[0] ?
strlen(a) + 1 : 0), sizeof(pr->anchor_call));
rs_free(a, MAXPATHLEN);
}
if (r->anchor_wildcard)
strlcat(pr->anchor_call, pr->anchor_call[0] ? "/*" : "*",
sizeof(pr->anchor_call));
return (0);
}
void
pf_remove_anchor(struct pf_rule *r)
{
if (r->anchor == NULL)
return;
if (r->anchor->refcnt <= 0)
DPFPRINTF(LOG_NOTICE, "pf_remove_anchor: broken refcount");
else if (!--r->anchor->refcnt)
pf_remove_if_empty_ruleset(&r->anchor->ruleset);
r->anchor = NULL;
}
void
pf_anchor_rele(struct pf_anchor *anchor)
{
if ((anchor == NULL) || (anchor == &pf_main_anchor))
return;
#ifdef _KERNEL
if (refcnt_rele(&anchor->ref))
rs_pool_put_anchor(anchor);
#else
rs_pool_put_anchor(anchor);
#endif
}
struct pf_anchor *
pf_anchor_take(struct pf_anchor *anchor)
{
#ifdef _KERNEL
if (anchor != NULL && anchor != &pf_main_anchor)
refcnt_take(&anchor->ref);
#endif
return (anchor);
}
+294
View File
@@ -0,0 +1,294 @@
/* PFLINUX port of OpenBSD 8.0 net/pf_syncookies.c — see docs/porting-notes.md */
#include <pfk.h>
union pf_syncookie {
uint8_t cookie;
struct {
uint8_t oddeven:1,
sack_ok:1,
wscale_idx:3,
mss_idx:3;
} flags;
};
#define PF_SYNCOOKIE_SECRET_SIZE SIPHASH_KEY_LENGTH
#define PF_SYNCOOKIE_SECRET_LIFETIME 15 /* seconds */
static struct {
struct timeout keytimeout;
volatile uint oddeven;
SIPHASH_KEY key[2];
uint32_t hiwat; /* absolute; # of states */
uint32_t lowat;
} pf_syncookie_status;
void pf_syncookie_rotate(void *);
void pf_syncookie_newkey(void);
uint32_t pf_syncookie_mac(struct pf_pdesc *, union pf_syncookie,
uint32_t);
uint32_t pf_syncookie_generate(struct pf_pdesc *, uint16_t);
void
pf_syncookies_init(void)
{
timeout_set(&pf_syncookie_status.keytimeout,
pf_syncookie_rotate, NULL);
pf_syncookie_status.hiwat = PFSTATE_HIWAT * PF_SYNCOOKIES_HIWATPCT/100;
pf_syncookie_status.lowat = PFSTATE_HIWAT * PF_SYNCOOKIES_LOWATPCT/100;
pf_syncookies_setmode(PF_SYNCOOKIES_NEVER);
}
int
pf_syncookies_setmode(u_int8_t mode)
{
if (mode > PF_SYNCOOKIES_MODE_MAX)
return (EINVAL);
if (pf_status.syncookies_mode == mode)
return (0);
pf_status.syncookies_mode = mode;
if (pf_status.syncookies_mode == PF_SYNCOOKIES_ALWAYS) {
pf_syncookie_newkey();
pf_status.syncookies_active = 1;
}
return (0);
}
int
pf_syncookies_setwats(u_int32_t hiwat, u_int32_t lowat)
{
if (lowat > hiwat)
return (EINVAL);
pf_syncookie_status.hiwat = hiwat;
pf_syncookie_status.lowat = lowat;
return (0);
}
int
pf_syncookies_getwats(struct pfioc_synflwats *wats)
{
wats->hiwat = pf_syncookie_status.hiwat;
wats->lowat = pf_syncookie_status.lowat;
return (0);
}
int
pf_synflood_check(struct pf_pdesc *pd)
{
KASSERT (pd->proto == IPPROTO_TCP);
if (pd->m && (pd->m->m_pkthdr.pf.tag & PF_TAG_SYNCOOKIE_RECREATED))
return (0);
if (pf_status.syncookies_mode != PF_SYNCOOKIES_ADAPTIVE)
return (pf_status.syncookies_mode);
if (!pf_status.syncookies_active &&
pf_status.states_halfopen > pf_syncookie_status.hiwat) {
pf_syncookie_newkey();
pf_status.syncookies_active = 1;
DPFPRINTF(LOG_WARNING,
"synflood detected, enabling syncookies");
pf_status.lcounters[LCNT_SYNFLOODS]++;
}
return (pf_status.syncookies_active);
}
void
pf_syncookie_send(struct pf_pdesc *pd, u_short *reason)
{
uint16_t mss, mssdflt;
uint32_t iss;
mssdflt = atomic_load_int(&tcp_mssdflt);
mss = max(pf_get_mss(pd, mssdflt), mssdflt);
iss = pf_syncookie_generate(pd, mss);
pf_send_tcp(NULL, pd->af, pd->dst, pd->src, *pd->dport, *pd->sport,
iss, ntohl(pd->hdr.tcp.th_seq) + 1, TH_SYN|TH_ACK, 0, mss,
0, 1, 0, pd->rdomain, reason);
pf_status.syncookies_inflight[pf_syncookie_status.oddeven]++;
pf_status.lcounters[LCNT_SYNCOOKIES_SENT]++;
}
uint8_t
pf_syncookie_validate(struct pf_pdesc *pd)
{
uint32_t hash, ack, seq;
union pf_syncookie cookie;
KASSERT(pd->proto == IPPROTO_TCP);
seq = ntohl(pd->hdr.tcp.th_seq) - 1;
ack = ntohl(pd->hdr.tcp.th_ack) - 1;
cookie.cookie = (ack & 0xff) ^ (ack >> 24);
/* we don't know oddeven before setting the cookie (union) */
if (pf_status.syncookies_inflight[cookie.flags.oddeven] == 0)
return (0);
hash = pf_syncookie_mac(pd, cookie, seq);
if ((ack & ~0xff) != (hash & ~0xff))
return (0);
pf_status.syncookies_inflight[cookie.flags.oddeven]--;
pf_status.lcounters[LCNT_SYNCOOKIES_VALID]++;
return (1);
}
/*
* all following functions private
*/
void
pf_syncookie_rotate(void *arg)
{
/* do we want to disable syncookies? */
if (pf_status.syncookies_active &&
((pf_status.syncookies_mode == PF_SYNCOOKIES_ADAPTIVE &&
pf_status.states_halfopen + pf_status.syncookies_inflight[0] +
pf_status.syncookies_inflight[1] < pf_syncookie_status.lowat) ||
pf_status.syncookies_mode == PF_SYNCOOKIES_NEVER)) {
pf_status.syncookies_active = 0;
DPFPRINTF(LOG_WARNING, "syncookies disabled");
}
/* nothing in flight any more? delete keys and return */
if (!pf_status.syncookies_active &&
pf_status.syncookies_inflight[0] == 0 &&
pf_status.syncookies_inflight[1] == 0) {
memset(&pf_syncookie_status.key[0], 0,
PF_SYNCOOKIE_SECRET_SIZE);
memset(&pf_syncookie_status.key[1], 0,
PF_SYNCOOKIE_SECRET_SIZE);
return;
}
/* new key, including timeout */
pf_syncookie_newkey();
}
void
pf_syncookie_newkey(void)
{
pf_syncookie_status.oddeven = (pf_syncookie_status.oddeven + 1) & 0x1;
pf_status.syncookies_inflight[pf_syncookie_status.oddeven] = 0;
arc4random_buf(&pf_syncookie_status.key[pf_syncookie_status.oddeven],
PF_SYNCOOKIE_SECRET_SIZE);
timeout_add_sec(&pf_syncookie_status.keytimeout,
PF_SYNCOOKIE_SECRET_LIFETIME);
}
/*
* Distribution and probability of certain MSS values. Those in between are
* rounded down to the next lower one.
* [An Analysis of TCP Maximum Segment Sizes, S. Alcock and R. Nelson, 2011]
* .2% .3% 5% 7% 7% 20% 15% 45%
*/
static int pf_syncookie_msstab[] =
{ 216, 536, 1200, 1360, 1400, 1440, 1452, 1460 };
/*
* Distribution and probability of certain WSCALE values.
* The absence of the WSCALE option is encoded with index zero.
* [WSCALE values histograms, Allman, 2012]
* X 10 10 35 5 6 14 10% by host
* X 11 4 5 5 18 49 3% by connections
*/
static int pf_syncookie_wstab[] = { 0, 0, 1, 2, 4, 6, 7, 8 };
uint32_t
pf_syncookie_mac(struct pf_pdesc *pd, union pf_syncookie cookie, uint32_t seq)
{
SIPHASH_CTX ctx;
uint32_t siphash[2];
KASSERT(pd->proto == IPPROTO_TCP);
SipHash24_Init(&ctx, &pf_syncookie_status.key[cookie.flags.oddeven]);
switch (pd->af) {
case AF_INET:
SipHash24_Update(&ctx, pd->src, sizeof(pd->src->v4));
SipHash24_Update(&ctx, pd->dst, sizeof(pd->dst->v4));
break;
case AF_INET6:
SipHash24_Update(&ctx, pd->src, sizeof(pd->src->v6));
SipHash24_Update(&ctx, pd->dst, sizeof(pd->dst->v6));
break;
default:
panic("unknown address family");
}
SipHash24_Update(&ctx, pd->sport, sizeof(*pd->sport));
SipHash24_Update(&ctx, pd->dport, sizeof(*pd->dport));
SipHash24_Update(&ctx, &seq, sizeof(seq));
SipHash24_Update(&ctx, &cookie, sizeof(cookie));
SipHash24_Final((uint8_t *)&siphash, &ctx);
return (siphash[0] ^ siphash[1]);
}
uint32_t
pf_syncookie_generate(struct pf_pdesc *pd, uint16_t mss)
{
uint8_t i, wscale;
uint32_t iss, hash;
union pf_syncookie cookie;
cookie.cookie = 0;
/* map MSS */
for (i = nitems(pf_syncookie_msstab) - 1;
pf_syncookie_msstab[i] > mss && i > 0; i--)
/* nada */;
cookie.flags.mss_idx = i;
/* map WSCALE */
wscale = pf_get_wscale(pd);
for (i = nitems(pf_syncookie_wstab) - 1;
pf_syncookie_wstab[i] > wscale && i > 0; i--)
/* nada */;
cookie.flags.wscale_idx = i;
cookie.flags.sack_ok = 0; /* XXX */
cookie.flags.oddeven = pf_syncookie_status.oddeven;
hash = pf_syncookie_mac(pd, cookie, ntohl(pd->hdr.tcp.th_seq));
/*
* Put the flags into the hash and XOR them to get better ISS number
* variance. This doesn't enhance the cryptographic strength and is
* done to prevent the 8 cookie bits from showing up directly on the
* wire.
*/
iss = hash & ~0xff;
iss |= cookie.cookie ^ (hash >> 24);
return (iss);
}
struct mbuf *
pf_syncookie_recreate_syn(struct pf_pdesc *pd, u_short *reason)
{
uint8_t wscale;
uint16_t mss;
uint32_t ack, seq;
union pf_syncookie cookie;
seq = ntohl(pd->hdr.tcp.th_seq) - 1;
ack = ntohl(pd->hdr.tcp.th_ack) - 1;
cookie.cookie = (ack & 0xff) ^ (ack >> 24);
if (cookie.flags.mss_idx >= nitems(pf_syncookie_msstab) ||
cookie.flags.wscale_idx >= nitems(pf_syncookie_wstab))
return (NULL);
mss = pf_syncookie_msstab[cookie.flags.mss_idx];
wscale = pf_syncookie_wstab[cookie.flags.wscale_idx];
return (pf_build_tcp(NULL, pd->af, pd->src, pd->dst, *pd->sport,
*pd->dport, seq, 0, TH_SYN, wscale, mss, pd->ttl, 0,
PF_TAG_SYNCOOKIE_RECREATED, cookie.flags.sack_ok, pd->rdomain,
reason));
}
File diff suppressed because it is too large. Load diff
+92
View File
@@ -0,0 +1,92 @@
/*
* PF-on-Linux netfilter hook registration + M1 ioctl skeleton.
* The real verdict logic arrives from staged pf.c (T12); this file owns
* hook lifecycle and /dev/pf dispatch. (ISC 2026)
*/
#include <pfk.h>
#include <pfk_api.h>
#include <net/pfvar.h>
#include <net/pfvar_priv.h>
static int pfk_enabled = 1; /* like pf_defaultwise/pf_enable on boot */
static struct pf_status pfk_stat;
/* --- temporary M1 control surface (replaced by staged pf_ioctl.c, T7b) --- */
static int pfk_ioctl_min(struct file *f, unsigned int cmd, unsigned long addr)
{
struct pf_status s;
switch (cmd) {
case DIOCGETSTATUS:
memset(&s, 0, sizeof(s));
s.running = pfk_enabled;
strscpy(s.ifname, "pf", sizeof(s.ifname));
if (copy_to_user((void __user *)addr, &s, sizeof(s)))
return EFAULT;
return 0;
default:
/* Rule/state/table ioctls arrive with the staged pf_ioctl.c
* (plan T7b); until then they are rejected (not silently
* accepted) so behaviour is explicit. */
return EINVAL;
}
}
long pfk_ioctl(struct file *f, unsigned int cmd, unsigned long addr)
{
return pfk_ioctl_min(f, cmd, addr);
}
void pfk_status(struct pf_status *s)
{
if (s)
*s = pfk_stat;
}
int pfk_enable(int on) { pfk_enabled = on ? 1 : 0; return 0; }
/* --- netfilter hooks: ingress/egress plumbing (real work lands in T12) --- */
static unsigned int
pfk_ipv4_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *st)
{
/* PFLINUX M1: accept-everything placeholder; T12 routes to pf_test() */
return NF_ACCEPT;
}
static struct nf_hook_ops pfk_hooks[] = {
{ .hook = pfk_ipv4_hook, .pf = NFPROTO_IPV4, .hooknum = NF_INET_PRE_ROUTING,
.priority = NF_IP_PRI_FIRST },
{ .hook = pfk_ipv4_hook, .pf = NFPROTO_IPV4, .hooknum = NF_INET_LOCAL_IN,
.priority = NF_IP_PRI_FIRST + 1 },
{ .hook = pfk_ipv4_hook, .pf = NFPROTO_IPV4, .hooknum = NF_INET_FORWARD,
.priority = NF_IP_PRI_FIRST },
{ .hook = pfk_ipv4_hook, .pf = NFPROTO_IPV4, .hooknum = NF_INET_LOCAL_OUT,
.priority = NF_IP_PRI_CONNTRACK + 1 },
{ .hook = pfk_ipv4_hook, .pf = NFPROTO_IPV4, .hooknum = NF_INET_POST_ROUTING,
.priority = NF_IP_PRI_LAST - 1 },
};
#define PFK_NHOOKS ARRAY_SIZE(pfk_hooks)
int pfk_hooks_register(void)
{
int i, err;
for (i = 0; i < PFK_NHOOKS; i++) {
pfk_hooks[i].priv = NULL;
err = nf_register_net_hook(&init_net, &pfk_hooks[i]);
if (err)
goto fail;
}
return 0;
fail:
while (--i >= 0)
nf_unregister_net_hook(&init_net, &pfk_hooks[i]);
return err;
}
void pfk_hooks_unregister(void)
{
int i;
for (i = 0; i < PFK_NHOOKS; i++)
nf_unregister_net_hook(&init_net, &pfk_hooks[i]);
}
+86
View File
@@ -0,0 +1,86 @@
/*
* PF-on-Linux: pfk module core — load/exit, /dev/pf misc device,
* NET_LOCK bring-up, staged upstream bring-up hooks. (ISC 2026)
*/
#include <pfk.h>
#include <pfk_api.h>
#include <net/pfvar.h>
#include <net/pfvar_priv.h>
extern void pfk_mbuf_exit(void);
extern int pfk_if_init(void);
extern void pfk_if_exit(void);
static int pfk_open(struct inode *inode, struct file *filp)
{
if (filp->f_mode & FMODE_WRITE)
return capable(CAP_NET_ADMIN) ? 0 : -EPERM;
return 0;
}
static long pfk_ioctl_io(struct file *filp, unsigned int cmd,
unsigned long arg)
{
if (!capable(CAP_NET_ADMIN))
return -EPERM;
return pfk_ioctl(filp, cmd, arg);
}
static const struct file_operations pfk_fops = {
.owner = THIS_MODULE,
.open = pfk_open,
.unlocked_ioctl = pfk_ioctl_io,
.compat_ioctl = compat_ptr_ioctl,
};
static struct miscdevice pfk_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "pf",
.fops = &pfk_fops,
.mode = 0600,
};
static int __init pfk_init(void)
{
int err;
pfk_net_lock_init();
err = pfk_mbuf_init();
if (err)
return err;
err = pfk_if_init();
if (err)
goto err_mbuf;
err = pfk_hooks_register();
if (err)
goto err_if;
err = misc_register(&pfk_dev);
if (err)
goto err_hooks;
pr_info("pfk: PF-on-Linux engine loaded (OpenBSD 8.0 base)\n");
return 0;
err_hooks:
pfk_hooks_unregister();
err_if:
pfk_if_exit();
err_mbuf:
pfk_mbuf_exit();
return err;
}
static void __exit pfk_exit(void)
{
misc_deregister(&pfk_dev);
pfk_hooks_unregister();
pfk_if_exit();
pfk_mbuf_exit();
pr_info("pfk: unloaded\n");
}
module_init(pfk_init);
module_exit(pfk_exit);
MODULE_LICENSE("Dual MIT/GPL"); /* ISC/BSD code; Dual MIT/GPL is the kernel-
* recognised tag for permissive BSD-licensed
* modules linking GPL exports (see LICENSE). */
MODULE_DESCRIPTION("PF packet filter (ported from OpenBSD 8.0) for Linux");
MODULE_AUTHOR("PF-on-Linux contributors");