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:
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
@@ -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
File diff suppressed because it is too large.
Load diff
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
File diff suppressed because it is too large.
Load diff
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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]);
|
||||
}
|
||||
@@ -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");
|
||||
Reference in new issue
Block a user