diff options
author | Niels Provos <provos@cvs.openbsd.org> | 2001-06-28 21:53:43 +0000 |
---|---|---|
committer | Niels Provos <provos@cvs.openbsd.org> | 2001-06-28 21:53:43 +0000 |
commit | e64fc6bb94eb65581578538e1f33048caa9abcb4 (patch) | |
tree | e291519aa8271b2f11fc5131a74ddcdbb5cbd0f7 /sys/net | |
parent | 332e5291ec2d1b50efabc37a669246745e918db6 (diff) |
first stab at packet normalization. includes full ip reassembly.
okay dhartmei@, dugsong@
Diffstat (limited to 'sys/net')
-rw-r--r-- | sys/net/if_bridge.c | 4 | ||||
-rw-r--r-- | sys/net/pf.c | 478 | ||||
-rw-r--r-- | sys/net/pfvar.h | 12 |
3 files changed, 479 insertions, 15 deletions
diff --git a/sys/net/if_bridge.c b/sys/net/if_bridge.c index 17f30c39931..d97f8c9e792 100644 --- a/sys/net/if_bridge.c +++ b/sys/net/if_bridge.c @@ -1,4 +1,4 @@ -/* $OpenBSD: if_bridge.c,v 1.69 2001/06/27 06:26:43 angelos Exp $ */ +/* $OpenBSD: if_bridge.c,v 1.70 2001/06/28 21:53:42 provos Exp $ */ /* * Copyright (c) 1999, 2000 Jason L. Wright (jason@thought.net) @@ -1984,7 +1984,7 @@ bridge_filter(sc, ifp, eh, m) } /* Finally, we get to filter the packet! */ - if (pf_test(PF_IN, m->m_pkthdr.rcvif, m) != PF_PASS) + if (pf_test(PF_IN, m->m_pkthdr.rcvif, &m) != PF_PASS) goto dropit; /* Rebuild the IP header */ diff --git a/sys/net/pf.c b/sys/net/pf.c index a8c16aa6f40..210ab34022a 100644 --- a/sys/net/pf.c +++ b/sys/net/pf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: pf.c,v 1.82 2001/06/28 10:04:19 hugh Exp $ */ +/* $OpenBSD: pf.c,v 1.83 2001/06/28 21:53:41 provos Exp $ */ /* * Copyright (c) 2001, Daniel Hartmeier @@ -60,6 +60,9 @@ #include "bpfilter.h" #include "pflog.h" +int pf_debug = 0; +#define DPFPRINTF(x) if (pf_debug) printf x + /* * Tree data structure */ @@ -77,10 +80,31 @@ struct pf_tree_node { signed char balance; }; +struct pf_frent { + TAILQ_ENTRY(pf_frent) fr_next; + struct ip *fr_ip; + struct mbuf *fr_m; +}; + +#define PFFRAG_SEENLAST 0x0001 /* Seen the last fragment for this */ + +struct pf_fragment { + TAILQ_ENTRY(pf_fragment) frag_next; + struct in_addr fr_src; + struct in_addr fr_dst; + u_int8_t fr_p; /* protocol of this fragment */ + u_int8_t fr_flags; /* status flags */ + u_int16_t fr_id; /* fragment id for reassemble */ + u_int16_t fr_max; /* fragment data max */ + struct timeval fr_timeout; + TAILQ_HEAD(pf_fragq, pf_frent) fr_queue; +}; + /* * Global variables */ +TAILQ_HEAD(pf_fragqueue, pf_fragment) pf_fragqueue; TAILQ_HEAD(pf_rulequeue, pf_rule) pf_rules[2]; TAILQ_HEAD(pf_natqueue, pf_nat) pf_nats[2]; TAILQ_HEAD(pf_rdrqueue, pf_rdr) pf_rdrs[2]; @@ -92,6 +116,7 @@ struct pf_natqueue *pf_nats_inactive; struct pf_rdrqueue *pf_rdrs_active; struct pf_rdrqueue *pf_rdrs_inactive; struct pf_tree_node *tree_lan_ext, *tree_ext_gwy; +struct pf_tree_node *tree_fragment; struct timeval pftv; struct pf_status pf_status; struct ifnet *status_ifp; @@ -111,6 +136,9 @@ struct pool pf_rule_pl; struct pool pf_nat_pl; struct pool pf_rdr_pl; struct pool pf_state_pl; +struct pool pf_frent_pl; +struct pool pf_frag_pl; +int pf_nfrents; /* * Prototypes @@ -120,9 +148,9 @@ int tree_key_compare(struct pf_tree_key *, struct pf_tree_key *); void tree_rotate_left(struct pf_tree_node **); void tree_rotate_right(struct pf_tree_node **); int tree_insert(struct pf_tree_node **, struct pf_tree_key *, - struct pf_state *); + void *); int tree_remove(struct pf_tree_node **, struct pf_tree_key *); -struct pf_state *find_state(struct pf_tree_node *, struct pf_tree_key *); +void *find_state(struct pf_tree_node *, struct pf_tree_key *); void insert_state(struct pf_state *); void purge_expired_states(void); @@ -167,6 +195,18 @@ void *pull_hdr(struct ifnet *, struct mbuf *, int, int, void *, int, int pflog_packet(struct mbuf *, int, u_short, u_short, struct pf_rule *); +int pf_normalize_ip(struct mbuf **, int, struct ifnet *, + struct ip *, u_short *); + +void purge_expired_fragments(void); +void pf_ip2key(struct pf_tree_key *, struct ip *); +void pf_remove_fragment(struct pf_fragment *); +void pf_flush_fragments(void); +void pf_free_fragment(struct pf_fragment *); +struct pf_fragment *pf_find_fragment(struct ip *); +struct mbuf *pf_reassemble(struct mbuf **, struct pf_fragment *, + struct pf_frent *, int); + #if NPFLOG > 0 #define PFLOG_PACKET(x,a,b,c,d,e) \ do { \ @@ -180,6 +220,9 @@ int pflog_packet(struct mbuf *, int, u_short, u_short, #define PFLOG_PACKET #endif +#define PFFRAG_FRENT_HIWAT 10000 /* Number of fragment entries */ +#define PFFRAG_FRAG_HIWAT 3000 /* Number of fragmented packets */ + int tree_key_compare(struct pf_tree_key *a, struct pf_tree_key *b) { @@ -243,7 +286,7 @@ tree_rotate_right(struct pf_tree_node **p) } int -tree_insert(struct pf_tree_node **p, struct pf_tree_key *key, struct pf_state *state) +tree_insert(struct pf_tree_node **p, struct pf_tree_key *key, void *state) { int deltaH = 0; @@ -397,7 +440,7 @@ pflog_packet(struct mbuf *m, int af, u_short dir, u_short reason, return (0); } -struct pf_state * +void * find_state(struct pf_tree_node *p, struct pf_tree_key *key) { int c; @@ -541,6 +584,15 @@ pfattach(int num) 0, NULL, NULL, 0); pool_init(&pf_state_pl, sizeof(struct pf_state), 0, 0, 0, "pfstatepl", 0, NULL, NULL, 0); + pool_init(&pf_frent_pl, sizeof(struct pf_frent), 0, 0, 0, "pffrent", + 0, NULL, NULL, 0); + pool_init(&pf_frag_pl, sizeof(struct pf_fragment), 0, 0, 0, "pffrag", + 0, NULL, NULL, 0); + + pool_sethiwat(&pf_frag_pl, PFFRAG_FRAG_HIWAT); + pool_sethiwat(&pf_frent_pl, PFFRAG_FRENT_HIWAT); + + TAILQ_INIT(&pf_fragqueue); TAILQ_INIT(&pf_rules[0]); TAILQ_INIT(&pf_rules[1]); TAILQ_INIT(&pf_nats[0]); @@ -1257,8 +1309,9 @@ pf_test_tcp(int direction, struct ifnet *ifp, struct mbuf *m, } } - r = TAILQ_FIRST(pf_rules_active); - while (r != NULL) { + TAILQ_FOREACH(r, pf_rules_active, entries) { + if (r->action == PF_SCRUB) + continue; if (r->direction == direction && (r->ifp == NULL || r->ifp == ifp) && (!r->proto || r->proto == IPPROTO_TCP) && @@ -1275,7 +1328,6 @@ pf_test_tcp(int direction, struct ifnet *ifp, struct mbuf *m, if (r->quick) break; } - r = TAILQ_NEXT(r, entries); } if (rm != NULL) { @@ -1408,6 +1460,8 @@ pf_test_udp(int direction, struct ifnet *ifp, struct mbuf *m, } TAILQ_FOREACH(r, pf_rules_active, entries) { + if (r->action == PF_SCRUB) + continue; if ((r->direction == direction) && ((r->ifp == NULL) || (r->ifp == ifp)) && (!r->proto || (r->proto == IPPROTO_UDP)) && @@ -1520,6 +1574,8 @@ pf_test_icmp(int direction, struct ifnet *ifp, struct mbuf *m, } TAILQ_FOREACH(r, pf_rules_active, entries) { + if (r->action == PF_SCRUB) + continue; if ((r->direction == direction) && ((r->ifp == NULL) || (r->ifp == ifp)) && (!r->proto || (r->proto == IPPROTO_ICMP)) && @@ -1605,6 +1661,8 @@ pf_test_other(int direction, struct ifnet *ifp, struct mbuf *m, struct ip *h) struct pf_rule *r, *rm = NULL; TAILQ_FOREACH(r, pf_rules_active, entries) { + if (r->action == PF_SCRUB) + continue; if ((r->direction == direction) && ((r->ifp == NULL) || (r->ifp == ifp)) && (!r->proto || (r->proto == h->ip_p)) && @@ -2072,6 +2130,397 @@ pf_test_state_icmp(int direction, struct ifnet *ifp, struct mbuf *m, } } +#define FRAG_EXPIRE 30 + +void +purge_expired_fragments(void) +{ + struct pf_fragment *frag; + struct timeval now, expire; + + microtime(&now); + + timerclear(&expire); + expire.tv_sec = FRAG_EXPIRE; + timersub(&now, &expire, &expire); + + while ((frag = TAILQ_LAST(&pf_fragqueue, pf_fragqueue)) != NULL) { + DPFPRINTF((__FUNCTION__": %d,%d > %d,%d\n", + frag->fr_timeout.tv_sec, frag->fr_timeout.tv_usec, + expire.tv_sec, expire.tv_usec)); + if (timercmp(&frag->fr_timeout, &expire, >)) + break; + + DPFPRINTF((__FUNCTION__": expiring %p\n", frag)); + pf_free_fragment(frag); + } + +} + +/* + * Try to flush old fragments to make space for new ones + */ + +void +pf_flush_fragments(void) +{ + struct pf_fragment *frag; + int goal = pf_nfrents * 9 / 10; + + DPFPRINTF((__FUNCTION__": trying to free > %d frents\n", + pf_nfrents - goal)); + + while (goal < pf_nfrents) { + frag = TAILQ_LAST(&pf_fragqueue, pf_fragqueue); + if (frag == NULL) + break; + pf_free_fragment(frag); + } +} + +void +pf_free_fragment(struct pf_fragment *frag) +{ + struct pf_frent *frent; + + /* Free all fragments */ + for (frent = TAILQ_FIRST(&frag->fr_queue); frent; + frent = TAILQ_FIRST(&frag->fr_queue)) { + TAILQ_REMOVE(&frag->fr_queue, frent, fr_next); + + m_freem(frent->fr_m); + pool_put(&pf_frent_pl, frent); + pf_nfrents--; + } + + pf_remove_fragment(frag); + pool_put(&pf_frag_pl, frag); +} + +void +pf_ip2key(struct pf_tree_key *key, struct ip *ip) +{ + key->proto = ip->ip_p; + key->addr[0] = ip->ip_src; + key->addr[1] = ip->ip_dst; + key->port[0] = ip->ip_id; + key->port[1] = 0; +} + +struct pf_fragment * +pf_find_fragment(struct ip *ip) +{ + struct pf_tree_key key; + struct pf_fragment *frag; + + pf_ip2key(&key, ip); + + frag = find_state(tree_fragment, &key); + + if (frag != NULL) { + microtime(&frag->fr_timeout); + TAILQ_REMOVE(&pf_fragqueue, frag, frag_next); + TAILQ_INSERT_HEAD(&pf_fragqueue, frag, frag_next); + } + + return (frag); +} + +void +pf_remove_fragment(struct pf_fragment *frag) +{ + struct pf_tree_key key; + + key.proto = frag->fr_p; + key.addr[0] = frag->fr_src; + key.addr[1] = frag->fr_dst; + key.port[0] = frag->fr_id; + key.port[1] = 0; + + tree_remove(&tree_fragment, &key); + TAILQ_REMOVE(&pf_fragqueue, frag, frag_next); +} + +struct mbuf * +pf_reassemble(struct mbuf **m0, struct pf_fragment *frag, + struct pf_frent *frent, int mff) +{ + struct mbuf *m = *m0, *m2; + struct pf_frent *frep, *frea, *next; + struct ip *ip = frent->fr_ip; + int hlen = ip->ip_hl << 2; + u_int16_t off = ip->ip_off; + u_int16_t max = ip->ip_len + off; + + /* Strip off ip header */ + m->m_data += hlen; + m->m_len -= hlen; + + /* Create a new reassembly queue for this packet */ + if (frag == NULL) { + struct pf_tree_key key; + + frag = pool_get(&pf_frag_pl, M_NOWAIT); + if (frag == NULL) + goto drop_fragment; + + frag->fr_flags = 0; + frag->fr_src = frent->fr_ip->ip_src; + frag->fr_dst = frent->fr_ip->ip_dst; + frag->fr_p = frent->fr_ip->ip_p; + frag->fr_id = frent->fr_ip->ip_id; + TAILQ_INIT(&frag->fr_queue); + + pf_ip2key(&key, frent->fr_ip); + + tree_insert(&tree_fragment, &key, frag); + TAILQ_INSERT_HEAD(&pf_fragqueue, frag, frag_next); + + DPFPRINTF((__FUNCTION__": insert new fragment\n")); + /* We do not have a previous fragment */ + frep = NULL; + goto insert; + } + + /* + * Find a fragment after the current one: + * - off contains the real shifted offset. + */ + TAILQ_FOREACH(frea, &frag->fr_queue, fr_next) { + if (frea->fr_ip->ip_off > off) + break; + frep = frea; + } + + KASSERT(frep != NULL || frea != NULL); + + if (frep != NULL) { + u_int16_t precut; + + precut = frep->fr_ip->ip_off + frep->fr_ip->ip_len - off; + if (precut > ip->ip_len) + goto drop_fragment; + m_adj(frent->fr_m, precut); + + /* We enforce 8 byte boundaries, ip_off not converted yet */ + off = ip->ip_off += precut; + ip->ip_len -= precut; + } + + for (; frea != NULL && ip->ip_len + off > frea->fr_ip->ip_off; + frea = next) { + u_int16_t aftercut; + + aftercut = (ip->ip_len + off) - frea->fr_ip->ip_off; + if (aftercut < frea->fr_ip->ip_len) { + frea->fr_ip->ip_len -= aftercut; + frea->fr_ip->ip_off += aftercut; + m_adj(frea->fr_m, aftercut); + break; + } + + /* This fragment is completely overlapped, loose it */ + next = TAILQ_NEXT(frea, fr_next); + m_freem(frea->fr_m); + TAILQ_REMOVE(&frag->fr_queue, frea, fr_next); + pool_put(&pf_frent_pl, frea); + pf_nfrents--; + } + + insert: + /* Update maxmimum data size */ + if (frag->fr_max < max) + frag->fr_max = max; + /* This is the last segment */ + if (!mff) + frag->fr_flags |= PFFRAG_SEENLAST; + + if (frep == NULL) + TAILQ_INSERT_HEAD(&frag->fr_queue, frent, fr_next); + else + TAILQ_INSERT_AFTER(&frag->fr_queue, frep, frent, fr_next); + + /* Check if we are completely reassembled */ + if (!(frag->fr_flags & PFFRAG_SEENLAST)) + return (NULL); + + /* Check if we have all the data */ + off = 0; + for (frep = TAILQ_FIRST(&frag->fr_queue); frep; frep = next) { + next = TAILQ_NEXT(frep, fr_next); + + off += frep->fr_ip->ip_len; + if (off < frag->fr_max && + (next == NULL || next->fr_ip->ip_off != off)) + return (NULL); + } + DPFPRINTF((__FUNCTION__": %d < %d?\n", off, frag->fr_max)); + if (off < frag->fr_max) + return (NULL); + + /* We have all the data */ + frent = TAILQ_FIRST(&frag->fr_queue); + KASSERT(frent != NULL); + if ((frent->fr_ip->ip_hl << 2) + off > IP_MAXPACKET) { + DPFPRINTF((__FUNCTION__": drop: too big: %d\n", off)); + pf_free_fragment(frag); + return (NULL); + } + next = TAILQ_NEXT(frent, fr_next); + + /* Magic from ip_input */ + ip = frent->fr_ip; + m = frent->fr_m; + m2 = m->m_next; + m->m_next = NULL; + m_cat(m, m2); + pool_put(&pf_frent_pl, frent); + for (frent = next; frent != NULL; frent = next) { + DPFPRINTF((__FUNCTION__": frent %p\n", frent)); + next = TAILQ_NEXT(frent, fr_next); + + m2 = frent->fr_m; + pool_put(&pf_frent_pl, frent); + pf_nfrents--; + m_cat(m, m2); + } + + ip->ip_src = frag->fr_src; + ip->ip_dst = frag->fr_dst; + + /* Remove from fragment queue */ + pf_remove_fragment(frag); + + hlen = ip->ip_hl << 2; + ip->ip_len = off + hlen; + m->m_len += hlen; + m->m_data -= hlen; + + /* some debugging cruft by sklower, below, will go away soon */ + /* XXX this should be done elsewhere */ + if (m->m_flags & M_PKTHDR) { + int plen = 0; + for (m2 = m; m2; m2 = m2->m_next) + plen += m2->m_len; + m->m_pkthdr.len = plen; + } + + DPFPRINTF((__FUNCTION__": complete: %p(%d)\n", m, ip->ip_len)); + + return (m); + + drop_fragment: + /* Oops - fail safe - drop packet */ + m_freem(m); + return (NULL); +} + +int +pf_normalize_ip(struct mbuf **m0, int dir, struct ifnet *ifp, struct ip *h, + u_short *reason) +{ + struct mbuf *m = *m0; + struct pf_rule *r; + struct pf_frent *frent; + struct pf_fragment *frag; + int mff = (h->ip_off & IP_MF), hlen = h->ip_hl << 2; + u_int16_t fragoff = (h->ip_off & IP_OFFMASK) << 3; + u_int16_t max; + + TAILQ_FOREACH(r, pf_rules_active, entries) { + if ((r->direction == dir) && (r->action == PF_SCRUB) && + ((r->ifp == NULL) || (r->ifp == ifp)) && + (!r->proto || (r->proto == h->ip_p)) && + ((!r->src.addr && !r->src.mask) || match_addr(r->src.not, r->src.addr, + r->src.mask, h->ip_src.s_addr)) && + ((!r->dst.addr && !r->dst.mask) || match_addr(r->dst.not, r->dst.addr, + r->dst.mask, h->ip_dst.s_addr)) ) + break; + } + + if (r == NULL) + return (PF_PASS); + + /* Check for illegal packets */ + if (hlen < sizeof(struct ip)) + goto drop; + + if (hlen > h->ip_len) + goto drop; + + /* We will need other tests here */ + if (!fragoff && !mff) + return (PF_PASS); + + /* Now we are dealing with a fragmented packet */ + frag = pf_find_fragment(h); + + /* This can not happen */ + if (h->ip_off & IP_DF) { + DPFPRINTF((__FUNCTION__": IP_DF\n")); + goto bad; + } + + h->ip_len -= hlen; + h->ip_off <<= 3; + + /* All fragments are 8 byte aligned */ + if (mff && (h->ip_len & 0x7)) { + DPFPRINTF((__FUNCTION__": mff and %d\n", h->ip_len)); + goto bad; + } + + max = fragoff + h->ip_len; + /* Respect maximum length */ + if (max > IP_MAXPACKET) { + DPFPRINTF((__FUNCTION__": max packet %d\n", max)); + goto bad; + } + /* Check if we saw the last fragment already */ + if (frag != NULL && (frag->fr_flags & PFFRAG_SEENLAST) && + max > frag->fr_max) + goto bad; + + /* Get an entry for the fragment queue */ + frent = pool_get(&pf_frent_pl, PR_NOWAIT); + if (frent == NULL) { + /* Try to clean up old fragments */ + pf_flush_fragments(); + frent = pool_get(&pf_frent_pl, PR_NOWAIT); + if (frent == NULL) { + REASON_SET(reason, PFRES_MEMORY); + return (PF_DROP); + } + } + pf_nfrents++; + frent->fr_ip = h; + frent->fr_m = m; + + /* Might return a completely reassembled mbuf, or NULL */ + DPFPRINTF((__FUNCTION__": reass frag @ %d\n", fragoff)); + *m0 = m = pf_reassemble(m0, frag, frent, mff); + + return (m == NULL ? PF_DROP : PF_PASS); + + drop: + REASON_SET(reason, PFRES_NORM); + if (r != NULL && r->log) + PFLOG_PACKET(h, m, AF_INET, dir, *reason, r); + return (PF_DROP); + + bad: + DPFPRINTF((__FUNCTION__": dropping bad fragment\n")); + + /* Free assoicated fragments */ + if (frag != NULL) + pf_free_fragment(frag); + + if (r != NULL && r->log) + PFLOG_PACKET(h, m, AF_INET, dir, *reason, r); + + return (PF_DROP); +} + /* * ipoff and off are measured from the start of the mbuf chain. * h must be at "ipoff" on the mbuf chain. @@ -2107,9 +2556,10 @@ pull_hdr(struct ifnet *ifp, struct mbuf *m, int ipoff, int off, void *p, } int -pf_test(int dir, struct ifnet *ifp, struct mbuf *m) +pf_test(int dir, struct ifnet *ifp, struct mbuf **m0) { u_short action, reason = 0, log = 0; + struct mbuf *m = *m0; struct ip *h; struct pf_rule *r = NULL; struct pf_state *s; @@ -2127,6 +2577,7 @@ pf_test(int dir, struct ifnet *ifp, struct mbuf *m) microtime(&pftv); if (pftv.tv_sec - pf_last_purge >= 10) { purge_expired_states(); + purge_expired_fragments(); pf_last_purge = pftv.tv_sec; } @@ -2136,8 +2587,17 @@ pf_test(int dir, struct ifnet *ifp, struct mbuf *m) log = 1; goto done; } + h = mtod(m, struct ip *); + /* We do IP header normalization and packet reassembly here */ + if (pf_normalize_ip(m0, dir, ifp, h, &reason) != PF_PASS) { + ACTION_SET(&action, PF_DROP); + goto done; + } + m = *m0; + h = mtod(m, struct ip *); + off = h->ip_hl << 2; switch (h->ip_p) { diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h index 3f86ed947b3..8cf2af3c0bd 100644 --- a/sys/net/pfvar.h +++ b/sys/net/pfvar.h @@ -1,4 +1,4 @@ -/* $OpenBSD: pfvar.h,v 1.23 2001/06/27 10:31:51 kjell Exp $ */ +/* $OpenBSD: pfvar.h,v 1.24 2001/06/28 21:53:42 provos Exp $ */ /* * Copyright (c) 2001, Daniel Hartmeier @@ -37,7 +37,7 @@ #include <sys/queue.h> enum { PF_IN=0, PF_OUT=1 }; -enum { PF_PASS=0, PF_DROP=1, PF_DROP_RST=2 }; +enum { PF_PASS=0, PF_DROP=1, PF_DROP_RST=2, PF_SCRUB }; enum { PF_OP_GL=1, PF_OP_EQ=2, PF_OP_NE=3, PF_OP_LT=4, PF_OP_LE=5, PF_OP_GT=6, PF_OP_GE=7 }; @@ -129,13 +129,17 @@ struct pf_rdr { #define PFRES_BADOFF 1 /* Bad offset for pull_hdr */ #define PFRES_FRAG 2 /* Dropping following fragment */ #define PFRES_SHORT 3 /* Dropping short packet */ -#define PFRES_MAX 4 /* total+1 */ +#define PFRES_NORM 4 /* Dropping by normalizer */ +#define PFRES_MEMORY 5 /* Dropped due to lacking mem */ +#define PFRES_MAX 6 /* total+1 */ #define PFRES_NAMES { \ "match", \ "bad-offset", \ "fragment", \ "short", \ + "normalize", \ + "memory", \ NULL \ } @@ -221,7 +225,7 @@ struct pfioc_if { #ifdef _KERNEL -int pf_test(int, struct ifnet *, struct mbuf *); +int pf_test(int, struct ifnet *, struct mbuf **); #endif /* _KERNEL */ |