diff options
author | Kjell Wooding <kjell@cvs.openbsd.org> | 2001-06-24 19:49:00 +0000 |
---|---|---|
committer | Kjell Wooding <kjell@cvs.openbsd.org> | 2001-06-24 19:49:00 +0000 |
commit | 6b7e146b5046259ba9faa9444114b5c4c18070fe (patch) | |
tree | 6769ac6a79b4a0c4a79698115a952bc6f6a41909 /sys/net | |
parent | 4d339188d7d3d36098d08968a138bb031f00562d (diff) |
Initial import of pf, an all-new ipf-compatable packet filter.
Insane amounts of work done my dhartmei. Great work!
Diffstat (limited to 'sys/net')
-rw-r--r-- | sys/net/pf.c | 1704 | ||||
-rw-r--r-- | sys/net/pfvar.h | 170 |
2 files changed, 1874 insertions, 0 deletions
diff --git a/sys/net/pf.c b/sys/net/pf.c new file mode 100644 index 00000000000..8025d79cef6 --- /dev/null +++ b/sys/net/pf.c @@ -0,0 +1,1704 @@ +/* $OpenBSD: pf.c,v 1.1 2001/06/24 19:48:58 kjell Exp $ */ + +/* + * Copyright (c) 2001, Daniel Hartmeier + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/mbuf.h> +#include <sys/filio.h> +#include <sys/fcntl.h> +#include <sys/socket.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/time.h> + +#include <net/if.h> +#include <net/if_types.h> +#include <net/route.h> +#include <net/pfvar.h> + +#include <netinet/in.h> +#include <netinet/in_var.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/tcp.h> +#include <netinet/udp.h> +#include <netinet/ip_icmp.h> + +/* + * Tree data structure + */ + +struct tree_node { + struct tree_key { + u_int8_t proto; + u_int32_t addr[2]; + u_int16_t port[2]; + } key; + struct state *state; + signed char balance; + struct tree_node *left, + *right; +}; + +/* + * Global variables + */ + +struct rule *rulehead = NULL; +struct nat *nathead = NULL; +struct rdr *rdrhead = NULL; +struct state *statehead = NULL; +struct tree_node *tree_lan_ext = NULL, + *tree_ext_gwy = NULL; +struct timeval tv; +struct status status; +struct ifnet *status_ifp = NULL; +u_int32_t last_purge = 0; +u_int16_t next_port_tcp = 50001, + next_port_udp = 50001; + +/* + * Prototypes + */ + +signed char tree_key_compare (struct tree_key *a, struct tree_key *b); +void tree_rotate_left (struct tree_node **p); +void tree_rotate_right (struct tree_node **p); +int tree_insert (struct tree_node **p, struct tree_key *key, + struct state *state); +int tree_remove (struct tree_node **p, struct tree_key *key); +struct state *find_state (struct tree_node *p, struct tree_key *key); +void insert_state (struct state *state); +void purge_expired_states (void); +void print_ip (struct ifnet *ifp, struct ip *h); +void print_host (u_int32_t a, u_int16_t p); +void print_state (int direction, struct state *s); +void print_flags (u_int8_t f); +void pfattach (int num); +int pfopen (dev_t dev, int flags, int fmt, struct proc *p); +int pfclose (dev_t dev, int flags, int fmt, struct proc *p); +int pfioctl (dev_t dev, u_long cmd, caddr_t addr, int flags, + struct proc *p); +u_short fix (u_short cksum, u_short old, u_short new); +void change_ap (u_int32_t *a, u_int16_t *p, u_int16_t *ic, u_int16_t + *pc, u_int32_t an, u_int16_t pn); +void change_a (u_int32_t *a, u_int16_t *c, u_int32_t an); +void change_icmp (u_int32_t *ia, u_int16_t *ip, u_int32_t *oa, + u_int32_t na, u_int16_t np, u_int16_t *pc, u_int16_t *h2c, + u_int16_t *ic, u_int16_t *hc); +void send_reset (int direction, struct ifnet *ifp, struct ip *h, + struct tcphdr *th); +int match_addr (u_int8_t n, u_int32_t a, u_int32_t m, u_int32_t b); +int match_port (u_int8_t op, u_int16_t a1, u_int16_t a2, u_int16_t p); +struct nat *get_nat (struct ifnet *ifp, u_int8_t proto, u_int32_t addr); +struct rdr *get_rdr (struct ifnet *ifp, u_int8_t proto, u_int32_t addr, + u_int16_t port); +int pf_test_tcp (int direction, struct ifnet *ifp, struct ip *h, + struct tcphdr *th); +int pf_test_udp (int direction, struct ifnet *ifp, struct ip *h, + struct udphdr *uh); +int pf_test_icmp (int direction, struct ifnet *ifp, struct ip *h, + struct icmp *ih); +struct state *pf_test_state_tcp (int direction, struct ifnet *ifp, struct ip *h, + struct tcphdr *th); +struct state *pf_test_state_udp (int direction, struct ifnet *ifp, struct ip *h, + struct udphdr *uh); +struct state *pf_test_state_icmp (int direction, struct ifnet *ifp, + struct ip *h, struct icmp *ih); +void *pull_hdr (struct ifnet *ifp, struct mbuf **m, struct ip *h, + int *action, u_int8_t len); +int pf_test (int direction, struct ifnet *ifp, struct mbuf **m); + +/* ------------------------------------------------------------------------ */ + +inline signed char +tree_key_compare(struct tree_key *a, struct tree_key *b) +{ + /* + * could use memcmp(), but with the best manual order, we can + * minimize the number of average compares. what is faster? + */ + if (a->proto < b->proto ) return -1; + if (a->proto > b->proto ) return 1; + if (a->addr[0] < b->addr[0]) return -1; + if (a->addr[0] > b->addr[0]) return 1; + if (a->addr[1] < b->addr[1]) return -1; + if (a->addr[1] > b->addr[1]) return 1; + if (a->port[0] < b->port[0]) return -1; + if (a->port[0] > b->port[0]) return 1; + if (a->port[1] < b->port[1]) return -1; + if (a->port[1] > b->port[1]) return 1; + return 0; +} + +inline void +tree_rotate_left(struct tree_node **p) +{ + struct tree_node *q = *p; + *p = (*p)->right; + q->right = (*p)->left; + (*p)->left = q; + q->balance--; + if ((*p)->balance > 0) + q->balance -= (*p)->balance; + (*p)->balance--; + if (q->balance < 0) + (*p)->balance += q->balance; +} + +inline void +tree_rotate_right(struct tree_node **p) +{ + struct tree_node *q = *p; + *p = (*p)->left; + q->left = (*p)->right; + (*p)->right = q; + q->balance++; + if ((*p)->balance < 0) + q->balance -= (*p)->balance; + (*p)->balance++; + if (q->balance > 0) + (*p)->balance += q->balance; +} + +int +tree_insert(struct tree_node **p, struct tree_key *key, struct state *state) +{ + int deltaH = 0; + if (*p == NULL) { + *p = malloc(sizeof(struct tree_node), M_DEVBUF, M_NOWAIT); + if (*p == NULL) { + printf("packetfilter: malloc() failed\n"); + return 0; + } + memcpy(&(*p)->key, key, sizeof(struct tree_key)); + (*p)->state = state; + (*p)->balance = 0; + (*p)->left = (*p)->right = NULL; + deltaH = 1; + } else if (tree_key_compare(key, &(*p)->key) > 0) { + if (tree_insert(&(*p)->right, key, state)) { + (*p)->balance++; + if ((*p)->balance == 1) + deltaH = 1; + else if ((*p)->balance == 2) { + if ((*p)->right->balance == -1) + tree_rotate_right(&(*p)->right); + tree_rotate_left(p); + } + } + } else { + if (tree_insert(&(*p)->left, key, state)) { + (*p)->balance--; + if ((*p)->balance == -1) + deltaH = 1; + else if ((*p)->balance == -2) { + if ((*p)->left->balance == 1) + tree_rotate_left(&(*p)->left); + tree_rotate_right(p); + } + } + } + return deltaH; +} + +int +tree_remove(struct tree_node **p, struct tree_key *key) +{ + int deltaH = 0; + signed char c; + if (*p == NULL) + return 0; + c = tree_key_compare(key, &(*p)->key); + if (c < 0) { + if (tree_remove(&(*p)->left, key)) { + (*p)->balance++; + if ((*p)->balance == 0) + deltaH = 1; + else if ((*p)->balance == 2) { + if ((*p)->right->balance == -1) + tree_rotate_right(&(*p)->right); + tree_rotate_left(p); + if ((*p)->balance == 0) + deltaH = 1; + } + } + } else if (c > 0) { + if (tree_remove(&(*p)->right, key)) { + (*p)->balance--; + if ((*p)->balance == 0) + deltaH = 1; + else if ((*p)->balance == -2) { + if ((*p)->left->balance == 1) + tree_rotate_left(&(*p)->left); + tree_rotate_right(p); + if ((*p)->balance == 0) + deltaH = 1; + } + } + } else { + if ((*p)->right == NULL) { + struct tree_node *p0 = *p; + *p = (*p)->left; + free(p0, M_DEVBUF); + deltaH = 1; + } else if ((*p)->left == NULL) { + struct tree_node *p0 = *p; + *p = (*p)->right; + free(p0, M_DEVBUF); + deltaH = 1; + } else { + struct tree_node **qq = &(*p)->left; + while ((*qq)->right != NULL) + qq = &(*qq)->right; + memcpy(&(*p)->key, &(*qq)->key, sizeof(struct tree_key)); + (*p)->state = (*qq)->state; + memcpy(&(*qq)->key, key, sizeof(struct tree_key)); + if (tree_remove(&(*p)->left, key)) { + (*p)->balance++; + if ((*p)->balance == 0) + deltaH = 1; + else if ((*p)->balance == 2) { + if ((*p)->right->balance == -1) + tree_rotate_right(&(*p)->right); + tree_rotate_left(p); + if ((*p)->balance == 0) + deltaH = 1; + } + } + } + } + return deltaH; +} + +inline struct state * +find_state(struct tree_node *p, struct tree_key *key) +{ + signed char c; + while ((p != NULL) && (c = tree_key_compare(&p->key, key))) + p = (c > 0) ? p->left : p->right; + status.state_searches++; + return p ? p->state : NULL; +} + +void +insert_state(struct state *state) +{ + struct tree_key key; + + key.proto = state->proto; + key.addr[0] = state->lan.addr; + key.port[0] = state->lan.port; + key.addr[1] = state->ext.addr; + key.port[1] = state->ext.port; + /* sanity checks can be removed later, should never occur */ + if (find_state(tree_lan_ext, &key) != NULL) + printf("packetfilter: ERROR! insert invalid\n"); + else { + tree_insert(&tree_lan_ext, &key, state); + if (find_state(tree_lan_ext, &key) != state) + printf("packetfilter: ERROR! insert failed\n"); + } + + key.proto = state->proto; + key.addr[0] = state->ext.addr; + key.port[0] = state->ext.port; + key.addr[1] = state->gwy.addr; + key.port[1] = state->gwy.port; + if (find_state(tree_ext_gwy, &key) != NULL) + printf("packetfilter: ERROR! insert invalid\n"); + else { + tree_insert(&tree_ext_gwy, &key, state); + if (find_state(tree_ext_gwy, &key) != state) + printf("packetfilter: ERROR! insert failed\n"); + } + + state->next = statehead; + statehead = state; + + status.state_inserts++; + status.states++; +} + +void +purge_expired_states(void) +{ + struct tree_key key; + struct state *cur = statehead, *prev = NULL; + while (cur != NULL) { + if (cur->expire <= tv.tv_sec) { + key.proto = cur->proto; + key.addr[0] = cur->lan.addr; + key.port[0] = cur->lan.port; + key.addr[1] = cur->ext.addr; + key.port[1] = cur->ext.port; + /* sanity checks can be removed later */ + if (find_state(tree_lan_ext, &key) != cur) + printf("packetfilter: ERROR! remove invalid\n"); + tree_remove(&tree_lan_ext, &key); + if (find_state(tree_lan_ext, &key) != NULL) + printf("packetfilter: ERROR! remove failed\n"); + key.proto = cur->proto; + key.addr[0] = cur->ext.addr; + key.port[0] = cur->ext.port; + key.addr[1] = cur->gwy.addr; + key.port[1] = cur->gwy.port; + if (find_state(tree_ext_gwy, &key) != cur) + printf("packetfilter: ERROR! remove invalid\n"); + tree_remove(&tree_ext_gwy, &key); + if (find_state(tree_ext_gwy, &key) != NULL) + printf("packetfilter: ERROR! remove failed\n"); + (prev ? prev->next : statehead) = cur->next; + free(cur, M_DEVBUF); + cur = (prev ? prev->next : statehead); + status.state_removals++; + status.states--; + } else { + prev = cur; + cur = cur->next; + } + } +} + +/* ------------------------------------------------------------------------ */ + +inline void +print_ip(struct ifnet *ifp, struct ip *h) +{ + u_int32_t a; + printf(" %s:", ifp->if_xname); + a = ntohl(h->ip_src.s_addr); + printf(" %u.%u.%u.%u", (a>>24)&255, (a>>16)&255, (a>>8)&255, a&255); + a = ntohl(h->ip_dst.s_addr); + printf(" -> %u.%u.%u.%u", (a>>24)&255, (a>>16)&255, (a>>8)&255, a&255); + printf(" hl=%u len=%u id=%u", h->ip_hl << 2, h->ip_len - (h->ip_hl << 2), + h->ip_id); + if (h->ip_off & IP_RF) printf(" RF"); + if (h->ip_off & IP_DF) printf(" DF"); + if (h->ip_off & IP_MF) printf(" MF"); + printf(" off=%u proto=%u\n", (h->ip_off & IP_OFFMASK) << 3, h->ip_p); +} + +void +print_host(u_int32_t a, u_int16_t p) +{ + a = ntohl(a); + p = ntohs(p); + printf("%u.%u.%u.%u:%u", (a>>24)&255, (a>>16)&255, (a>>8)&255, a&255, p); +} + +void +print_state(int direction, struct state *s) +{ + print_host(s->lan.addr, s->lan.port); + printf(" "); + print_host(s->gwy.addr, s->gwy.port); + printf(" "); + print_host(s->ext.addr, s->ext.port); + printf(" [%lu+%lu]", s->src.seqlo, s->src.seqhi - s->src.seqlo); + printf(" [%lu+%lu]", s->dst.seqlo, s->dst.seqhi - s->dst.seqlo); + printf(" %u:%u", s->src.state, s->dst.state); +} + +void +print_flags(u_int8_t f) +{ + if (f) printf(" "); + if (f & TH_FIN ) printf("F"); + if (f & TH_SYN ) printf("S"); + if (f & TH_RST ) printf("R"); + if (f & TH_PUSH) printf("P"); + if (f & TH_ACK ) printf("A"); + if (f & TH_URG ) printf("U"); +} + +/* ------------------------------------------------------------------------ */ + +void +pfattach(int num) +{ + memset(&status, 0, sizeof(struct status)); + printf("packetfilter: attached\n"); +} + +/* ------------------------------------------------------------------------ */ + +int +pfopen(dev_t dev, int flags, int fmt, struct proc *p) +{ + if (minor(dev) >= 1) + return ENXIO; + return NO_ERROR; +} + +/* ------------------------------------------------------------------------ */ + +int +pfclose(dev_t dev, int flags, int fmt, struct proc *p) +{ + if (minor(dev) >= 1) + return ENXIO; + return NO_ERROR; +} + +/* ------------------------------------------------------------------------ */ + +int +pfioctl(dev_t dev, u_long cmd, caddr_t addr, int flags, struct proc *p) +{ + int error = NO_ERROR; + struct ioctlbuffer *ub; + void *kb = NULL; + int s; + + if (!(flags & FWRITE)) + return EACCES; + + if ((cmd != DIOCSTART) && (cmd != DIOCSTOP) && (cmd != DIOCCLRSTATES)) { + ub = (struct ioctlbuffer *)addr; + if (ub == NULL) + return ERROR_INVALID_PARAMETERS; + kb = malloc(ub->size, M_DEVBUF, M_NOWAIT); + if (kb == NULL) + return ERROR_MALLOC; + if (copyin(ub->buffer, kb, ub->size)) { + free(kb, M_DEVBUF); + return ERROR_INVALID_PARAMETERS; + } + } + + s = splsoftnet(); + + microtime(&tv); + if (tv.tv_sec - last_purge >= 10) { + purge_expired_states(); + last_purge = tv.tv_sec; + } + + switch (cmd) { + + case DIOCSTART: + if (status.running) + error = ERROR_ALREADY_RUNNING; + else { + u_int32_t states = status.states; + memset(&status, 0, sizeof(struct status)); + status.running = 1; + status.states = states; + status.since = tv.tv_sec; + printf("packetfilter: started\n"); + } + break; + + case DIOCSTOP: + if (!status.running) + error = ERROR_NOT_RUNNING; + else { + status.running = 0; + printf("packetfilter: stopped\n"); + } + break; + + case DIOCSETRULES: { + struct rule *rules = (struct rule *)kb, *ruletail = NULL; + u_int16_t n; + while (rulehead != NULL) { + struct rule *next = rulehead->next; + free(rulehead, M_DEVBUF); + rulehead = next; + } + for (n = 0; n < ub->entries; ++n) { + struct rule *rule; + rule = malloc(sizeof(struct rule), M_DEVBUF, M_NOWAIT); + if (rule == NULL) { + error = ERROR_MALLOC; + goto done; + } + memcpy(rule, rules + n, sizeof(struct rule)); + rule->ifp = NULL; + if (rule->ifname[0]) { + rule->ifp = ifunit(rule->ifname); + if (rule->ifp == NULL) { + free(rule, M_DEVBUF); + error = ERROR_INVALID_PARAMETERS; + goto done; + } + } + rule->next = NULL; + if (ruletail != NULL) { + ruletail->next = rule; + ruletail = rule; + } else + rulehead = ruletail = rule; + } + break; + } + + case DIOCGETRULES: { + struct rule *rules = (struct rule *)kb; + struct rule *rule = rulehead; + u_int16_t n = 0; + while ((rule != NULL) && (n < ub->entries)) { + memcpy(rules + n, rule, sizeof(struct rule)); + n++; + rule = rule->next; + } + ub->entries = n; + break; + } + + case DIOCSETNAT: { + struct nat *nats = (struct nat *)kb; + u_int16_t n; + while (nathead != NULL) { + struct nat *next = nathead->next; + free(nathead, M_DEVBUF); + nathead = next; + } + for (n = 0; n < ub->entries; ++n) { + struct nat *nat; + nat = malloc(sizeof(struct nat), M_DEVBUF, M_NOWAIT); + if (nat == NULL) { + error = ERROR_MALLOC; + goto done; + } + memcpy(nat, nats + n, sizeof(struct nat)); + nat->ifp = ifunit(nat->ifname); + if (nat->ifp == NULL) { + free(nat, M_DEVBUF); + error = ERROR_INVALID_PARAMETERS; + goto done; + } + nat->next = nathead; + nathead = nat; + } + break; + } + + case DIOCGETNAT: { + struct nat *nats = (struct nat *)kb; + struct nat *nat = nathead; + u_int16_t n = 0; + while ((nat != NULL) && (n < ub->entries)) { + memcpy(nats + n, nat, sizeof(struct nat)); + n++; + nat = nat->next; + } + ub->entries = n; + break; + } + + case DIOCSETRDR: { + struct rdr *rdrs = (struct rdr *)kb; + u_int16_t n; + while (rdrhead != NULL) { + struct rdr *next = rdrhead->next; + free(rdrhead, M_DEVBUF); + rdrhead = next; + } + for (n = 0; n < ub->entries; ++n) { + struct rdr *rdr; + rdr = malloc(sizeof(struct rdr), M_DEVBUF, M_NOWAIT); + if (rdr == NULL) { + error = ERROR_MALLOC; + goto done; + } + memcpy(rdr, rdrs + n, sizeof(struct rdr)); + rdr->ifp = ifunit(rdr->ifname); + if (rdr->ifp == NULL) { + free(rdr, M_DEVBUF); + error = ERROR_INVALID_PARAMETERS; + goto done; + } + rdr->next = rdrhead; + rdrhead = rdr; + } + break; + } + + case DIOCGETRDR: { + struct rdr *rdrs = (struct rdr *)kb; + struct rdr *rdr = rdrhead; + u_int16_t n = 0; + while ((rdr != NULL) && (n < ub->entries)) { + memcpy(rdrs + n, rdr, sizeof(struct rdr)); + n++; + rdr = rdr->next; + } + ub->entries = n; + break; + } + + case DIOCCLRSTATES: { + struct state *state = statehead; + while (state != NULL) { + state->expire = 0; + state = state->next; + } + purge_expired_states(); + break; + } + + case DIOCGETSTATES: { + struct state *states = (struct state *)kb; + struct state *state; + u_int16_t n = 0; + state = statehead; + while ((state != NULL) && (n < ub->entries)) { + memcpy(states + n, state, sizeof(struct state)); + states[n].creation = tv.tv_sec - states[n].creation; + if (states[n].expire <= tv.tv_sec) + states[n].expire = 0; + else + states[n].expire -= tv.tv_sec; + n++; + state = state->next; + } + ub->entries = n; + break; + } + + case DIOCSETSTATUSIF: { + char *ifname = (char *)kb; + struct ifnet *ifp = ifunit(ifname); + if (ifp == NULL) + error = ERROR_INVALID_PARAMETERS; + else + status_ifp = ifp; + break; + } + + case DIOCGETSTATUS: { + struct status *st = (struct status *)kb; + u_int8_t running = status.running; + u_int32_t states = status.states; + memcpy(st, &status, sizeof(struct status)); + st->since = st->since ? tv.tv_sec - st->since : 0; + ub->entries = 1; + memset(&status, 0, sizeof(struct status)); + status.running = running; + status.states = states; + status.since = tv.tv_sec; + break; + } + + default: + error = ERROR_INVALID_OP; + break; + } + +done: + splx(s); + if (kb != NULL) { + if (copyout(kb, ub->buffer, ub->size)) + error = ERROR_INVALID_PARAMETERS; + free(kb, M_DEVBUF); + } + return error; +} + +/* ------------------------------------------------------------------------ */ + +inline u_short +fix(u_short cksum, u_short old, u_short new) +{ + u_long l = cksum + old - new; + l = (l >> 16) + (l & 65535); + l = l & 65535; + return l ? l : 65535; +} + +void +change_ap(u_int32_t *a, u_int16_t *p, u_int16_t *ic, u_int16_t *pc, u_int32_t an, + u_int16_t pn) +{ + u_int32_t ao = *a; + u_int16_t po = *p; + *a = an; + *ic = fix(fix(*ic, ao / 65536, an / 65536), ao % 65536, an % 65536); + *p = pn; + *pc = fix(fix(fix(*pc, ao / 65536, an / 65536), ao % 65536, an % 65536), + po, pn); +} + +void +change_a(u_int32_t *a, u_int16_t *c, u_int32_t an) +{ + u_int32_t ao = *a; + *a = an; + *c = fix(fix(*c, ao / 65536, an / 65536), ao % 65536, an % 65536); +} + +void +change_icmp(u_int32_t *ia, u_int16_t *ip, u_int32_t *oa, u_int32_t na, + u_int16_t np, u_int16_t *pc, u_int16_t *h2c, u_int16_t *ic, u_int16_t *hc) +{ + u_int32_t oia = *ia, ooa = *oa, opc = *pc, oh2c = *h2c; + u_int16_t oip = *ip; + // change inner protocol port, fix inner protocol checksum + *ip = np; + *pc = fix(*pc, oip, *ip); + *ic = fix(*ic, oip, *ip); + *ic = fix(*ic, opc, *pc); + // change inner ip address, fix inner ip checksum and icmp checksum + *ia = na; + *h2c = fix(fix(*h2c, oia / 65536, *ia / 65536), oia % 65536, *ia % 65536); + *ic = fix(fix(*ic, oia / 65536, *ia / 65536), oia % 65536, *ia % 65536); + *ic = fix(*ic, oh2c, *h2c); + // change outer ip address, fix outer ip checksum + *oa = na; + *hc = fix(fix(*hc, ooa / 65536, *oa / 65536), ooa % 65536, *oa % 65536); +} + +/* ------------------------------------------------------------------------ */ + +void +send_reset(int direction, struct ifnet *ifp, struct ip *h, struct tcphdr *th) +{ + struct mbuf *m; + int len = sizeof(struct ip) + sizeof(struct tcphdr); + struct ip *h2; + struct tcphdr *th2; + + /* don't reply to RST packets */ + if (th->th_flags & TH_RST) + return; + + /* create outgoing mbuf */ + m = m_gethdr(M_DONTWAIT, MT_HEADER); + if (m == NULL) + return; + m->m_data += max_linkhdr; + m->m_pkthdr.len = m->m_len = len; + m->m_pkthdr.rcvif = NULL; + bzero((caddr_t)m->m_data, len); + h2 = mtod(m, struct ip *); + + /* IP header fields included in the TCP checksum */ + h2->ip_p = IPPROTO_TCP; + h2->ip_len = htons(sizeof(struct tcphdr)); + h2->ip_src.s_addr = h->ip_dst.s_addr; + h2->ip_dst.s_addr = h->ip_src.s_addr; + + /* TCP header */ + th2 = (struct tcphdr *)((caddr_t)h2 + sizeof(struct ip)); + th2->th_sport = th->th_dport; + th2->th_dport = th->th_sport; + if (th->th_flags & TH_ACK) { + th2->th_seq = th->th_ack; + th2->th_flags = TH_RST; + } else { + int tlen = h->ip_len - ((h->ip_hl + th->th_off) << 2) + + ((th->th_flags & TH_SYN) ? 1 : 0) + + ((th->th_flags & TH_FIN) ? 1 : 0); + th2->th_ack = htonl(ntohl(th->th_seq) + tlen); + th2->th_flags = TH_RST | TH_ACK; + } + th2->th_off = sizeof(*th2) >> 2; + + /* TCP checksum */ + th2->th_sum = in_cksum(m, len); + + /* Finish the IP header */ + h2->ip_v = 4; + h2->ip_hl = sizeof(struct ip) >> 2; + h2->ip_len = htons(len); + h2->ip_ttl = 128; + h2->ip_sum = 0; + + /* IP header checksum */ + h2->ip_sum = in_cksum(m, sizeof(struct ip)); + + if (direction == PF_IN) { + /* set up route and send RST out through the same interface */ + struct route iproute; + struct route *ro = &iproute; + struct sockaddr_in *dst; + int error; + bzero((caddr_t)ro, sizeof(*ro)); + dst = (struct sockaddr_in *)&ro->ro_dst; + dst->sin_family = AF_INET; + dst->sin_addr = h2->ip_dst; + dst->sin_len = sizeof(*dst); + rtalloc(ro); + if (ro->ro_rt != NULL) + ro->ro_rt->rt_use++; + error = (*ifp->if_output)(ifp, m, (struct sockaddr *)dst, + ro->ro_rt); + } else { + /* send RST through the loopback interface */ + struct sockaddr_in dst; + dst.sin_family = AF_INET; + dst.sin_addr = h2->ip_dst; + dst.sin_len = sizeof(struct sockaddr_in); + m->m_pkthdr.rcvif = ifp; + looutput(lo0ifp, m, sintosa(&dst), NULL); + } + return; +} + +/* ------------------------------------------------------------------------ */ + +inline int +match_addr(u_int8_t n, u_int32_t a, u_int32_t m, u_int32_t b) +{ + return n == !((a & m) == (b & m)); +} + +inline int +match_port(u_int8_t op, u_int16_t a1, u_int16_t a2, u_int16_t p) +{ + switch (op) { + case 1: return (p >= a1) && (p <= a2); + case 2: return p == a1; + case 3: return p != a1; + case 4: return p < a1; + case 5: return p <= a1; + case 6: return p > a1; + case 7: return p >= a1; + } + return 0; /* never reached */ +} + +/* ------------------------------------------------------------------------ */ + +struct nat * +get_nat(struct ifnet *ifp, u_int8_t proto, u_int32_t addr) +{ + struct nat *n = nathead, *nm = NULL; + while ((n != NULL) && (nm == NULL)) { + if ((n->ifp == ifp) && + (!n->proto || (n->proto == proto)) && + match_addr(n->not, n->saddr, n->smask, addr)) + nm = n; + else + n = n->next; + } + return nm; +} + +struct rdr * +get_rdr(struct ifnet *ifp, u_int8_t proto, u_int32_t addr, u_int16_t port) +{ + struct rdr *r = rdrhead, *rm = NULL; + while ((r != NULL) && (rm == NULL)) { + if ((r->ifp == ifp) && + (!r->proto || (r->proto == proto)) && + match_addr(r->not, r->daddr, r->dmask, addr) && + (r->dport == port)) + rm = r; + else + r = r->next; + } + return rm; +} + +/* ------------------------------------------------------------------------ */ + +int +pf_test_tcp(int direction, struct ifnet *ifp, struct ip *h, struct tcphdr *th) +{ + struct nat *nat = NULL; + struct rdr *rdr = NULL; + u_int32_t baddr; + u_int16_t bport; + struct rule *r = rulehead, *rm = NULL; + u_int16_t nr = 1, mnr = 0; + + if (direction == PF_OUT) { + /* check outgoing packet for NAT */ + if ((nat = get_nat(ifp, IPPROTO_TCP, h->ip_src.s_addr)) != NULL) { + baddr = h->ip_src.s_addr; + bport = th->th_sport; + change_ap(&h->ip_src.s_addr, &th->th_sport, &h->ip_sum, + &th->th_sum, nat->daddr, htons(next_port_tcp)); + } + } else { + /* check incoming packet for RDR */ + if ((rdr = get_rdr(ifp, IPPROTO_TCP, h->ip_dst.s_addr, + th->th_dport)) != NULL) { + baddr = h->ip_dst.s_addr; + bport = th->th_dport; + change_ap(&h->ip_dst.s_addr, &th->th_dport, + &h->ip_sum, &th->th_sum, rdr->raddr, rdr->rport); + } + } + + while (r != NULL) { + if ((r->direction == direction) && + ((r->ifp == NULL) || (r->ifp == ifp)) && + (!r->proto || (r->proto == IPPROTO_TCP)) && + ((th->th_flags & r->flagset) == r->flags) && + (!r->src.addr || match_addr(r->src.not, r->src.addr, + r->src.mask, h->ip_src.s_addr)) && + (!r->dst.addr || match_addr(r->dst.not, r->dst.addr, + r->dst.mask, h->ip_dst.s_addr)) && + (!r->dst.port_op || match_port(r->dst.port_op, r->dst.port[0], + r->dst.port[1], th->th_dport)) && + (!r->src.port_op || match_port(r->src.port_op, r->src.port[0], + r->src.port[1], th->th_sport)) ) { + rm = r; + mnr = nr; + if (r->quick) + break; + } + r = r->next; + nr++; + } + + if ((rm != NULL) && rm->log) { + u_int32_t seq = ntohl(th->th_seq); + u_int16_t len = h->ip_len - ((h->ip_hl + th->th_off) << 2); + printf("packetfilter: @%u", mnr); + printf(" %s %s", rm->action ? "block" : "pass", direction ? "in" : + "out"); + printf(" on %s proto tcp", ifp->if_xname); + printf(" from "); + print_host(h->ip_src.s_addr, th->th_sport); + printf(" to "); + print_host(h->ip_dst.s_addr, th->th_dport); + print_flags(th->th_flags); + if (len || (th->th_flags & (TH_SYN | TH_FIN | TH_RST))) + printf(" %lu:%lu(%u)", seq, seq + len, len); + if (th->th_ack) printf(" ack=%lu", ntohl(th->th_ack)); + printf("\n"); + } + + if ((rm != NULL) && (rm->action == PF_DROP_RST)) { + /* undo NAT/RST changes, if they have taken place */ + if (nat != NULL) + change_ap(&h->ip_src.s_addr, &th->th_sport, + &h->ip_sum, &th->th_sum, baddr, bport); + else if (rdr != NULL) + change_ap(&h->ip_dst.s_addr, &th->th_dport, + &h->ip_sum, &th->th_sum, baddr, bport); + send_reset(direction, ifp, h, th); + return PF_DROP; + } + + if ((rm != NULL) && (rm->action == PF_DROP)) + return PF_DROP; + + if (((rm != NULL) && rm->keep_state) || (nat != NULL) || (rdr != NULL)) { + /* create new state */ + u_int16_t len = h->ip_len - ((h->ip_hl + th->th_off) << 2); + struct state *s = malloc(sizeof(struct state), M_DEVBUF, M_NOWAIT); + if (s == NULL) { + printf("packetfilter: malloc() failed\n"); + return PF_DROP; + } + s->proto = IPPROTO_TCP; + s->direction = direction; + if (direction == PF_OUT) { + s->gwy.addr = h->ip_src.s_addr; + s->gwy.port = th->th_sport; + s->ext.addr = h->ip_dst.s_addr; + s->ext.port = th->th_dport; + if (nat != NULL) { + s->lan.addr = baddr; + s->lan.port = bport; + next_port_tcp++; + if (next_port_tcp == 65535) + next_port_tcp = 50001; + } else { + s->lan.addr = s->gwy.addr; + s->lan.port = s->gwy.port; + } + } else { + s->lan.addr = h->ip_dst.s_addr; + s->lan.port = th->th_dport; + s->ext.addr = h->ip_src.s_addr; + s->ext.port = th->th_sport; + if (rdr != NULL) { + s->gwy.addr = baddr; + s->gwy.port = bport; + } else { + s->gwy.addr = s->lan.addr; + s->gwy.port = s->lan.port; + } + } + s->src.seqlo = ntohl(th->th_seq) + len; // ??? + s->src.seqhi = s->src.seqlo + 1; + s->src.state = 1; + s->dst.seqlo = 0; + s->dst.seqhi = 0; + s->dst.state = 0; + s->creation = tv.tv_sec; + s->expire = tv.tv_sec + 60; + s->packets = 1; + s->bytes = len; + insert_state(s); + } + + return PF_PASS; +} + +int +pf_test_udp(int direction, struct ifnet *ifp, struct ip *h, struct udphdr *uh) +{ + struct nat *nat = NULL; + struct rdr *rdr = NULL; + u_int32_t baddr; + u_int16_t bport; + struct rule *r = rulehead, *rm = NULL; + u_int16_t nr = 1, mnr = 0; + + if (direction == PF_OUT) { + /* check outgoing packet for NAT */ + if ((nat = get_nat(ifp, IPPROTO_UDP, h->ip_src.s_addr)) != NULL) { + baddr = h->ip_src.s_addr; + bport = uh->uh_sport; + change_ap(&h->ip_src.s_addr, &uh->uh_sport, &h->ip_sum, + &uh->uh_sum, nat->daddr, htons(next_port_udp)); + } + } else { + /* check incoming packet for RDR */ + if ((rdr = get_rdr(ifp, IPPROTO_UDP, h->ip_dst.s_addr, + uh->uh_dport)) != NULL) { + baddr = h->ip_dst.s_addr; + bport = uh->uh_dport; + change_ap(&h->ip_dst.s_addr, &uh->uh_dport, + &h->ip_sum, &uh->uh_sum, rdr->raddr, rdr->rport); + } + } + + while (r != NULL) { + if ((r->direction == direction) && + ((r->ifp == NULL) || (r->ifp == ifp)) && + (!r->proto || (r->proto == IPPROTO_UDP)) && + (!r->src.addr || match_addr(r->src.not, r->src.addr, + r->src.mask, h->ip_src.s_addr)) && + (!r->dst.addr || match_addr(r->dst.not, r->dst.addr, + r->dst.mask, h->ip_dst.s_addr)) && + (!r->dst.port_op || match_port(r->dst.port_op, r->dst.port[0], + r->dst.port[1], uh->uh_dport)) && + (!r->src.port_op || match_port(r->src.port_op, r->src.port[0], + r->src.port[1], uh->uh_sport)) ) { + rm = r; + mnr = nr; + if (r->quick) + break; + } + r = r->next; + nr++; + } + + if ((rm != NULL) && rm->log) { + printf("packetfilter: @%u", mnr); + printf(" %s %s", rm->action ? "block" : "pass", direction ? "in" : + "out"); + printf(" on %s proto udp", ifp->if_xname); + printf(" from "); + print_host(h->ip_src.s_addr, uh->uh_sport); + printf(" to "); + print_host(h->ip_dst.s_addr, uh->uh_dport); + printf("\n"); + } + + if ((rm != NULL) && (rm->action != PF_PASS)) + return PF_DROP; + + if (((rm != NULL) && rm->keep_state) || (nat != NULL) || (rdr != NULL)) { + /* create new state */ + u_int16_t len = h->ip_len - (h->ip_hl << 2) - 8; + struct state *s = malloc(sizeof(struct state), M_DEVBUF, M_NOWAIT); + if (s == NULL) { + printf("packetfilter: malloc() failed\n"); + return PF_DROP; + } + s->proto = IPPROTO_UDP; + s->direction = direction; + if (direction == PF_OUT) { + s->gwy.addr = h->ip_src.s_addr; + s->gwy.port = uh->uh_sport; + s->ext.addr = h->ip_dst.s_addr; + s->ext.port = uh->uh_dport; + if (nat != NULL) { + s->lan.addr = baddr; + s->lan.port = bport; + next_port_udp++; + if (next_port_udp == 65535) + next_port_udp = 50001; + } else { + s->lan.addr = s->gwy.addr; + s->lan.port = s->gwy.port; + } + } else { + s->lan.addr = h->ip_dst.s_addr; + s->lan.port = uh->uh_dport; + s->ext.addr = h->ip_src.s_addr; + s->ext.port = uh->uh_sport; + if (rdr != NULL) { + s->gwy.addr = baddr; + s->gwy.port = bport; + } else { + s->gwy.addr = s->lan.addr; + s->gwy.port = s->lan.port; + } + } + s->src.seqlo = 0; + s->src.seqhi = 0; + s->src.state = 1; + s->dst.seqlo = 0; + s->dst.seqhi = 0; + s->dst.state = 0; + s->creation = tv.tv_sec; + s->expire = tv.tv_sec + 30; + s->packets = 1; + s->bytes = len; + insert_state(s); + } + + return PF_PASS; +} + +int +pf_test_icmp(int direction, struct ifnet *ifp, struct ip *h, struct icmp *ih) +{ + struct nat *nat = NULL; + u_int32_t baddr; + struct rule *r = rulehead, *rm = NULL; + u_int16_t nr = 1, mnr = 0; + + if (direction == PF_OUT) { + /* check outgoing packet for NAT */ + if ((nat = get_nat(ifp, IPPROTO_ICMP, h->ip_src.s_addr)) != NULL) { + baddr = h->ip_src.s_addr; + change_a(&h->ip_src.s_addr, &h->ip_sum, nat->daddr); + } + } + + while (r != NULL) { + if ((r->direction == direction) && + ((r->ifp == NULL) || (r->ifp == ifp)) && + (!r->proto || (r->proto == IPPROTO_ICMP)) && + (!r->src.addr || match_addr(r->src.not, r->src.addr, + r->src.mask, h->ip_src.s_addr)) && + (!r->dst.addr || match_addr(r->dst.not, r->dst.addr, + r->dst.mask, h->ip_dst.s_addr)) && + (!r->type || (r->type == ih->icmp_type + 1)) && + (!r->code || (r->code == ih->icmp_code + 1)) ) { + rm = r; + mnr = nr; + if (r->quick) + break; + } + r = r->next; + nr++; + } + + if ((rm != NULL) && rm->log) { + printf("packetfilter: @%u", mnr); + printf(" %s %s", rm->action ? "block" : "pass", direction ? "in" : + "out"); + printf(" on %s proto icmp", ifp->if_xname); + printf(" from "); + print_host(h->ip_src.s_addr, 0); + printf(" to "); + print_host(h->ip_dst.s_addr, 0); + printf(" type %u/%u", ih->icmp_type, ih->icmp_code); + printf("\n"); + } + + if ((rm != NULL) && (rm->action != PF_PASS)) + return PF_DROP; + + if (((rm != NULL) && rm->keep_state) || (nat != NULL)) { + /* create new state */ + u_int16_t len = h->ip_len - (h->ip_hl << 2) - 8; + u_int16_t id = ih->icmp_hun.ih_idseq.icd_id; + struct state *s = malloc(sizeof(struct state), M_DEVBUF, M_NOWAIT); + if (s == NULL) { + printf("packetfilter: malloc() failed\n"); + return PF_DROP; + } + s->proto = IPPROTO_ICMP; + s->direction = direction; + if (direction == PF_OUT) { + s->gwy.addr = h->ip_src.s_addr; + s->gwy.port = id; + s->ext.addr = h->ip_dst.s_addr; + s->ext.port = id; + s->lan.addr = nat ? baddr : s->gwy.addr; + s->lan.port = id; + } else { + s->lan.addr = h->ip_dst.s_addr; + s->lan.port = id; + s->ext.addr = h->ip_src.s_addr; + s->ext.port = id; + s->gwy.addr = s->lan.addr; + s->gwy.port = id; + } + s->src.seqlo = 0; + s->src.seqhi = 0; + s->src.state = 0; + s->dst.seqlo = 0; + s->dst.seqhi = 0; + s->dst.state = 0; + s->creation = tv.tv_sec; + s->expire = tv.tv_sec + 20; + s->packets = 1; + s->bytes = len; + insert_state(s); + } + + return PF_PASS; +} + +/* ------------------------------------------------------------------------ */ + +struct state * +pf_test_state_tcp(int direction, struct ifnet *ifp, struct ip *h, struct tcphdr *th) +{ + struct state *s; + struct tree_key key; + + key.proto = IPPROTO_TCP; + key.addr[0] = h->ip_src.s_addr; + key.port[0] = th->th_sport; + key.addr[1] = h->ip_dst.s_addr; + key.port[1] = th->th_dport; + + s = find_state((direction == PF_IN) ? tree_ext_gwy : tree_lan_ext, &key); + if (s != NULL) { + + u_int16_t len = h->ip_len - ((h->ip_hl + th->th_off) << 2); + u_int32_t seq = ntohl(th->th_seq), ack = ntohl(th->th_ack); + + struct peer *src, *dst; + if (direction == s->direction) { + src = &s->src; + dst = &s->dst; + } else { + src = &s->dst; + dst = &s->src; + } + + /* some senders do that instead of ACKing FIN */ + if ((th->th_flags == TH_RST) && !ack && !len && + ((seq == src->seqhi) || (seq == src->seqhi-1)) && + (src->state >= 4) && (dst->state >= 3)) + ack = dst->seqhi; + + if ((dst->seqhi >= dst->seqlo ? + (ack >= dst->seqlo) && (ack <= dst->seqhi) : + (ack >= dst->seqlo) || (ack <= dst->seqhi)) || + (seq == src->seqlo) || (seq == src->seqlo-1)) { + + s->packets++; + s->bytes += len; + + /* update sequence number range */ + if (th->th_flags & TH_ACK) + dst->seqlo = ack; + if (th->th_flags & (TH_SYN | TH_FIN)) + len++; + if (th->th_flags & TH_SYN) { + src->seqhi = seq + len; + src->seqlo = src->seqhi - 1; + } else if ((seq + len) - src->seqhi < 65536) + src->seqhi = seq + len; + + /* update states */ + if (th->th_flags & TH_SYN) + if (src->state < 1) + src->state = 1; + if (th->th_flags & TH_FIN) + if (src->state < 3) + src->state = 3; + if ((th->th_flags & TH_ACK) && (ack == dst->seqhi)) { + if (dst->state == 1) + dst->state = 2; + else if (dst->state == 3) + dst->state = 4; + } + if (th->th_flags & TH_RST) + src->state = dst->state = 5; + + /* update expire time */ + if ((src->state >= 4) && (dst->state >= 4)) + s->expire = tv.tv_sec + 5; + else if ((src->state >= 3) || (dst->state >= 3)) + s->expire = tv.tv_sec + 300; + else if ((src->state < 2) || (dst->state < 2)) + s->expire = tv.tv_sec + 30; + else + s->expire = tv.tv_sec + 24*60*60; + + /* translate source/destination address, if necessary */ + if ((s->lan.addr != s->gwy.addr) + || (s->lan.port != s->gwy.port)) { + if (direction == PF_OUT) + change_ap(&h->ip_src.s_addr, &th->th_sport, + &h->ip_sum, &th->th_sum, + s->gwy.addr, s->gwy.port); + else + change_ap(&h->ip_dst.s_addr, &th->th_dport, + &h->ip_sum, &th->th_sum, + s->lan.addr, s->lan.port); + } + + } else { + printf("packetfilter: BAD state: "); + print_state(direction, s); + print_flags(th->th_flags); + printf(" seq=%lu ack=%lu len=%u ", seq, ack, len); + printf("\n"); + s = NULL; + } + + return s; + } + return NULL; +} + +struct state * +pf_test_state_udp(int direction, struct ifnet *ifp, struct ip *h, struct udphdr *uh) +{ + struct state *s; + struct tree_key key; + + key.proto = IPPROTO_UDP; + key.addr[0] = h->ip_src.s_addr; + key.port[0] = uh->uh_sport; + key.addr[1] = h->ip_dst.s_addr; + key.port[1] = uh->uh_dport; + + s = find_state((direction == PF_IN) ? tree_ext_gwy : tree_lan_ext, &key); + if (s != NULL) { + + u_int16_t len = h->ip_len - (h->ip_hl << 2) - 8; + + struct peer *src, *dst; + if (direction == s->direction) { + src = &s->src; + dst = &s->dst; + } else { + src = &s->dst; + dst = &s->src; + } + + s->packets++; + s->bytes += len; + + /* update states */ + if (src->state < 1) + src->state = 1; + if (dst->state == 1) + dst->state = 2; + + /* update expire time */ + if ((src->state == 2) && (dst->state == 2)) + s->expire = tv.tv_sec + 60; + else + s->expire = tv.tv_sec + 20; + + /* translate source/destination address, if necessary */ + if ((s->lan.addr != s->gwy.addr) + || (s->lan.port != s->gwy.port)) { + if (direction == PF_OUT) + change_ap(&h->ip_src.s_addr, &uh->uh_sport, + &h->ip_sum, &uh->uh_sum, + s->gwy.addr, s->gwy.port); + else + change_ap(&h->ip_dst.s_addr, &uh->uh_dport, + &h->ip_sum, &uh->uh_sum, + s->lan.addr, s->lan.port); + } + + return s; + } + return NULL; +} + +struct state * +pf_test_state_icmp(int direction, struct ifnet *ifp, struct ip *h, struct icmp *ih) +{ + u_int16_t len = h->ip_len - (h->ip_hl << 2) - 8; + + if ((ih->icmp_type != ICMP_UNREACH) && + (ih->icmp_type != ICMP_SOURCEQUENCH) && + (ih->icmp_type != ICMP_REDIRECT) && + (ih->icmp_type != ICMP_TIMXCEED) && + (ih->icmp_type != ICMP_PARAMPROB)) { + + /* + * ICMP query/reply message not related to a TCP/UDP packet. + * Search for an ICMP state. + */ + + struct state *s; + struct tree_key key; + + key.proto = IPPROTO_ICMP; + key.addr[0] = h->ip_src.s_addr; + key.port[0] = ih->icmp_hun.ih_idseq.icd_id; + key.addr[1] = h->ip_dst.s_addr; + key.port[1] = ih->icmp_hun.ih_idseq.icd_id; + + s = find_state((direction == PF_IN) ? tree_ext_gwy : + tree_lan_ext, &key); + if (s != NULL) { + + s->packets++; + s->bytes += len; + s->expire = tv.tv_sec + 10; + + /* translate source/destination address, if necessary */ + if (s->lan.addr != s->gwy.addr) { + if (direction == PF_OUT) + change_a(&h->ip_src.s_addr, &h->ip_sum, + s->gwy.addr); + else + change_a(&h->ip_dst.s_addr, &h->ip_sum, + s->lan.addr); + } + + return s; + } + return NULL; + + } else { + + /* + * ICMP error message in response to a TCP/UDP packet. + * Extract the inner TCP/UDP header and search for that state. + */ + + struct ip *h2 = (struct ip *)(((char *)ih) + 8); + if (len < 28) { + printf("packetfilter: ICMP error message too short\n"); + return NULL; + } + switch (h2->ip_p) { + case IPPROTO_TCP: { + struct tcphdr *th = (struct tcphdr *)(((char *)h2) + 20); + u_int32_t seq = ntohl(th->th_seq); + struct state *s; + struct tree_key key; + struct peer *src; + + key.proto = IPPROTO_TCP; + key.addr[0] = h2->ip_dst.s_addr; + key.port[0] = th->th_dport; + key.addr[1] = h2->ip_src.s_addr; + key.port[1] = th->th_sport; + + s = find_state((direction == PF_IN) ? tree_ext_gwy : + tree_lan_ext, &key); + if (s == NULL) + return NULL; + + src = (direction == s->direction) ? &s->dst : &s->src; + + if ((src->seqhi >= src->seqlo ? + (seq < src->seqlo) || (seq > src->seqhi) : + (seq < src->seqlo) && (seq > src->seqhi))) { + printf("packetfilter: BAD ICMP state: "); + print_state(direction, s); + print_flags(th->th_flags); + printf(" seq=%lu\n", seq); + return NULL; + } + + if ((s->lan.addr != s->gwy.addr) || + (s->lan.port != s->gwy.port)) { + if (direction == PF_IN) { + change_icmp(&h2->ip_src.s_addr, + &th->th_sport, &h->ip_dst.s_addr, + s->lan.addr, s->lan.port, &th->th_sum, + &h2->ip_sum, &ih->icmp_cksum, + &h->ip_sum); + } else { + change_icmp(&h2->ip_dst.s_addr, + &th->th_dport, &h->ip_src.s_addr, + s->gwy.addr, s->gwy.port, &th->th_sum, + &h2->ip_sum, &ih->icmp_cksum, + &h->ip_sum); + } + } + return s; + break; + } + case IPPROTO_UDP: { + struct udphdr *uh = (struct udphdr *)(((char *)h2) + 20); + struct state *s; + struct tree_key key; + + key.proto = IPPROTO_UDP; + key.addr[0] = h2->ip_dst.s_addr; + key.port[0] = uh->uh_dport; + key.addr[1] = h2->ip_src.s_addr; + key.port[1] = uh->uh_sport; + + s = find_state((direction == PF_IN) ? tree_ext_gwy : + tree_lan_ext, &key); + if (s == NULL) + return NULL; + + if ((s->lan.addr != s->gwy.addr) || + (s->lan.port != s->gwy.port)) { + if (direction == PF_IN) { + change_icmp(&h2->ip_src.s_addr, + &uh->uh_sport, &h->ip_dst.s_addr, + s->lan.addr, s->lan.port, &uh->uh_sum, + &h2->ip_sum, &ih->icmp_cksum, + &h->ip_sum); + } else { + change_icmp(&h2->ip_dst.s_addr, + &uh->uh_dport, &h->ip_src.s_addr, + s->gwy.addr, s->gwy.port, &uh->uh_sum, + &h2->ip_sum, &ih->icmp_cksum, + &h->ip_sum); + } + } + return s; + break; + } + default: + printf("packetfilter: ICMP error message for bad proto\n"); + return NULL; + } + return NULL; + + } +} + +/* ------------------------------------------------------------------------ */ + +inline void * +pull_hdr(struct ifnet *ifp, struct mbuf **m, struct ip *h, int *action, + u_int8_t len) +{ + u_int16_t hl = h->ip_hl << 2; + u_int16_t off = (h->ip_off & IP_OFFMASK) << 3; + if (off) { + if (off >= len) + *action = PF_PASS; + else { + *action = PF_DROP; + printf("packetfilter: dropping following fragment"); + print_ip(ifp, h); + } + return NULL; + } + if ((h->ip_len - hl) < len) { + *action = PF_DROP; + printf("packetfilter: dropping first fragment"); + print_ip(ifp, h); + return NULL; + } + if ((*m)->m_len < (hl + len)) + if ((*m = m_pullup(*m, hl + len)) == NULL) { + printf("packetfilter: pullup proto header failed\n"); + *action = PF_DROP; + return NULL; + } + return mtod(*m, char *) + hl; +} + +int +pf_test(int direction, struct ifnet *ifp, struct mbuf **m) +{ + int action; + struct ip *h = mtod(*m, struct ip *); + + if (!status.running) + return PF_PASS; + + /* purge expire states, at most once every 10 seconds */ + microtime(&tv); + if ((tv.tv_sec - last_purge) >= 10) { + purge_expired_states(); + last_purge = tv.tv_sec; + } + + /* ensure we have at least the complete ip header pulled up */ + if ((*m)->m_len < (h->ip_hl << 2)) + if ((*m = m_pullup(*m, h->ip_hl << 2)) == NULL) { + printf("packetfilter: pullup ip header failed\n"); + action = PF_DROP; + goto done; + } + + switch (h->ip_p) { + + case IPPROTO_TCP: { + struct tcphdr *th = pull_hdr(ifp, m, h, &action, 20); + if (th == NULL) + goto done; + if (pf_test_state_tcp(direction, ifp, h, th)) + action = PF_PASS; + else + action = pf_test_tcp(direction, ifp, h, th); + break; + } + + case IPPROTO_UDP: { + struct udphdr *uh = pull_hdr(ifp, m, h, &action, 8); + if (uh == NULL) + goto done; + if (pf_test_state_udp(direction, ifp, h, uh)) + action = PF_PASS; + else + action = pf_test_udp(direction, ifp, h, uh); + break; + } + + case IPPROTO_ICMP: { + struct icmp *ih = pull_hdr(ifp, m, h, &action, 8); + if (ih == NULL) + goto done; + if (pf_test_state_icmp(direction, ifp, h, ih)) + action = PF_PASS; + else + action = pf_test_icmp(direction, ifp, h, ih); + break; + } + + default: + printf("packetfilter: dropping unsupported protocol"); + print_ip(ifp, h); + action = PF_DROP; + break; + } + +done: + if (ifp == status_ifp) { + status.bytes[direction] += h->ip_len; + status.packets[direction][action]++; + } + return action; +} + +/* ------------------------------------------------------------------------ */ + diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h new file mode 100644 index 00000000000..c771abece86 --- /dev/null +++ b/sys/net/pfvar.h @@ -0,0 +1,170 @@ +/* $OpenBSD: pfvar.h,v 1.1 2001/06/24 19:48:58 kjell Exp $ */ + +/* + * Copyright (c) 2001, Daniel Hartmeier + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef _NETINET_PACKETFILTER_H_ +#define _NETINET_PACKETFILTER_H_ + +#include <sys/types.h> + +enum { PF_IN=0, PF_OUT=1 }; +enum { PF_PASS=0, PF_DROP=1, PF_DROP_RST=2 }; + +struct rule { + u_int8_t action; + u_int8_t direction; + u_int8_t log; + u_int8_t quick; + u_int8_t keep_state; + char ifname[16]; + struct ifnet *ifp; + u_int8_t proto; + struct { + u_int8_t not; + u_int32_t addr, + mask; + u_int8_t port_op; + u_int16_t port[2]; + } src, + dst; + u_int8_t type, + code; + u_int8_t flags, + flagset; + struct rule *next; +}; + +struct state { + u_int8_t proto; + u_int8_t direction; + struct host { + u_int32_t addr; + u_int16_t port; + } lan, + gwy, + ext; + struct peer { + u_int32_t seqlo, + seqhi; + u_int8_t state; + } src, + dst; + u_int32_t creation, + expire; + u_int32_t packets, + bytes; + struct state *next; +}; + +struct nat { + char ifname[16]; + struct ifnet *ifp; + u_int8_t proto; + u_int8_t not; + u_int32_t saddr, + smask, + daddr; + struct nat *next; +}; + +struct rdr { + char ifname[16]; + struct ifnet *ifp; + u_int8_t proto; + u_int8_t not; + u_int32_t daddr, + dmask, + raddr; + u_int16_t dport, + rport; + struct rdr *next; +}; + +struct status { + u_int8_t running; + u_int32_t bytes[2]; + u_int32_t packets[2][2]; + u_int32_t states, + state_inserts, + state_removals, + state_searches; + u_int32_t since; +}; + +/* + * ioctl parameter structure + */ + +struct ioctlbuffer { + u_int32_t size; + u_int16_t entries; + void *buffer; +}; + +/* + * ioctl operations + */ + +#define DIOCSTART _IO ('D', 1) +#define DIOCSTOP _IO ('D', 2) +#define DIOCSETRULES _IOWR('D', 3, struct ioctlbuffer) +#define DIOCGETRULES _IOWR('D', 4, struct ioctlbuffer) +#define DIOCSETNAT _IOWR('D', 5, struct ioctlbuffer) +#define DIOCGETNAT _IOWR('D', 6, struct ioctlbuffer) +#define DIOCSETRDR _IOWR('D', 7, struct ioctlbuffer) +#define DIOCGETRDR _IOWR('D', 8, struct ioctlbuffer) +#define DIOCCLRSTATES _IO ('D', 9) +#define DIOCGETSTATES _IOWR('D', 10, struct ioctlbuffer) +#define DIOCSETSTATUSIF _IOWR('D', 11, struct ioctlbuffer) +#define DIOCGETSTATUS _IOWR('D', 12, struct ioctlbuffer) + +/* + * ioctl errors + */ + +enum error_msg { + NO_ERROR=0, + ERROR_INVALID_OP=100, + ERROR_ALREADY_RUNNING, + ERROR_NOT_RUNNING, + ERROR_INVALID_PARAMETERS, + ERROR_MALLOC, + MAX_ERROR_NUM +}; + + +#ifdef _KERNEL + +int pf_test (int, struct ifnet *, struct mbuf **); + +#endif /* _KERNEL */ + +#endif /* _NET_PACKETFILTER_H_ */ |