Changeset View
Changeset View
Standalone View
Standalone View
sys/netpfil/ipfw/ip_fw_pfil.c
Show First 20 Lines • Show All 79 Lines • ▼ Show 20 Lines | |||||
#endif | #endif | ||||
VNET_DEFINE_STATIC(int, fwlink_enable) = 0; | VNET_DEFINE_STATIC(int, fwlink_enable) = 0; | ||||
#define V_fwlink_enable VNET(fwlink_enable) | #define V_fwlink_enable VNET(fwlink_enable) | ||||
int ipfw_chg_hook(SYSCTL_HANDLER_ARGS); | int ipfw_chg_hook(SYSCTL_HANDLER_ARGS); | ||||
/* Forward declarations. */ | /* Forward declarations. */ | ||||
static int ipfw_divert(struct mbuf **, int, struct ipfw_rule_ref *, int); | static int ipfw_divert(struct mbuf **, struct ip_fw_args *, bool); | ||||
#ifdef SYSCTL_NODE | #ifdef SYSCTL_NODE | ||||
SYSBEGIN(f1) | SYSBEGIN(f1) | ||||
SYSCTL_DECL(_net_inet_ip_fw); | SYSCTL_DECL(_net_inet_ip_fw); | ||||
SYSCTL_PROC(_net_inet_ip_fw, OID_AUTO, enable, | SYSCTL_PROC(_net_inet_ip_fw, OID_AUTO, enable, | ||||
CTLFLAG_VNET | CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_SECURE3, | CTLFLAG_VNET | CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_SECURE3, | ||||
Show All 16 Lines | |||||
#endif /* SYSCTL_NODE */ | #endif /* SYSCTL_NODE */ | ||||
/* | /* | ||||
* The pfilter hook to pass packets to ipfw_chk and then to | * The pfilter hook to pass packets to ipfw_chk and then to | ||||
* dummynet, divert, netgraph or other modules. | * dummynet, divert, netgraph or other modules. | ||||
* The packet may be consumed. | * The packet may be consumed. | ||||
*/ | */ | ||||
static pfil_return_t | static pfil_return_t | ||||
ipfw_check_packet(struct mbuf **m0, struct ifnet *ifp, int dir, | ipfw_check_packet(struct mbuf **m0, struct ifnet *ifp, int flags, | ||||
void *ruleset __unused, struct inpcb *inp) | void *ruleset __unused, struct inpcb *inp) | ||||
{ | { | ||||
struct ip_fw_args args; | struct ip_fw_args args; | ||||
struct m_tag *tag; | struct m_tag *tag; | ||||
pfil_return_t ret; | pfil_return_t ret; | ||||
int ipfw; | int ipfw; | ||||
/* convert dir to IPFW values */ | args.flags = (flags & PFIL_IN) ? IPFW_ARGS_IN : IPFW_ARGS_OUT; | ||||
dir = (dir & PFIL_IN) ? DIR_IN : DIR_OUT; | |||||
args.flags = 0; | |||||
again: | again: | ||||
/* | /* | ||||
* extract and remove the tag if present. If we are left | * extract and remove the tag if present. If we are left | ||||
* with onepass, optimize the outgoing path. | * with onepass, optimize the outgoing path. | ||||
*/ | */ | ||||
tag = m_tag_locate(*m0, MTAG_IPFW_RULE, 0, NULL); | tag = m_tag_locate(*m0, MTAG_IPFW_RULE, 0, NULL); | ||||
if (tag != NULL) { | if (tag != NULL) { | ||||
args.rule = *((struct ipfw_rule_ref *)(tag+1)); | args.rule = *((struct ipfw_rule_ref *)(tag+1)); | ||||
m_tag_delete(*m0, tag); | m_tag_delete(*m0, tag); | ||||
if (args.rule.info & IPFW_ONEPASS) | if (args.rule.info & IPFW_ONEPASS) | ||||
return (0); | return (0); | ||||
args.flags |= IPFW_ARGS_REF; | args.flags |= IPFW_ARGS_REF; | ||||
} | } | ||||
args.m = *m0; | args.m = *m0; | ||||
args.oif = dir == DIR_OUT ? ifp : NULL; | args.ifp = ifp; | ||||
args.inp = inp; | args.inp = inp; | ||||
ipfw = ipfw_chk(&args); | ipfw = ipfw_chk(&args); | ||||
*m0 = args.m; | *m0 = args.m; | ||||
KASSERT(*m0 != NULL || ipfw == IP_FW_DENY, ("%s: m0 is NULL", | KASSERT(*m0 != NULL || ipfw == IP_FW_DENY, ("%s: m0 is NULL", | ||||
__func__)); | __func__)); | ||||
Show All 37 Lines | if (args.flags & (IPFW_ARGS_NH6 | IPFW_ARGS_NH6PTR)) { | ||||
(*m0)->m_flags |= M_IP6_NEXTHOP; | (*m0)->m_flags |= M_IP6_NEXTHOP; | ||||
} | } | ||||
#endif /* INET6 */ | #endif /* INET6 */ | ||||
/* | /* | ||||
* Incoming packets should not be tagged so we do not | * Incoming packets should not be tagged so we do not | ||||
* m_tag_find. Outgoing packets may be tagged, so we | * m_tag_find. Outgoing packets may be tagged, so we | ||||
* reuse the tag if present. | * reuse the tag if present. | ||||
*/ | */ | ||||
tag = (dir == DIR_IN) ? NULL : | tag = (flags & PFIL_IN) ? NULL : | ||||
m_tag_find(*m0, PACKET_TAG_IPFORWARD, NULL); | m_tag_find(*m0, PACKET_TAG_IPFORWARD, NULL); | ||||
if (tag != NULL) { | if (tag != NULL) { | ||||
m_tag_unlink(*m0, tag); | m_tag_unlink(*m0, tag); | ||||
} else { | } else { | ||||
tag = m_tag_get(PACKET_TAG_IPFORWARD, len, | tag = m_tag_get(PACKET_TAG_IPFORWARD, len, | ||||
M_NOWAIT); | M_NOWAIT); | ||||
if (tag == NULL) { | if (tag == NULL) { | ||||
ret = PFIL_DROPPED; | ret = PFIL_DROPPED; | ||||
Show All 40 Lines | case IP_FW_DENY: | ||||
break; | break; | ||||
case IP_FW_DUMMYNET: | case IP_FW_DUMMYNET: | ||||
if (ip_dn_io_ptr == NULL) { | if (ip_dn_io_ptr == NULL) { | ||||
ret = PFIL_DROPPED; | ret = PFIL_DROPPED; | ||||
break; | break; | ||||
} | } | ||||
MPASS(args.flags & IPFW_ARGS_REF); | MPASS(args.flags & IPFW_ARGS_REF); | ||||
if (mtod(*m0, struct ip *)->ip_v == 4) | if (args.flags & (IPFW_ARGS_IP4 | IPFW_ARGS_IP6)) | ||||
(void )ip_dn_io_ptr(m0, dir, &args); | (void )ip_dn_io_ptr(m0, &args); | ||||
else if (mtod(*m0, struct ip *)->ip_v == 6) | |||||
(void )ip_dn_io_ptr(m0, dir | PROTO_IPV6, &args); | |||||
else { | else { | ||||
ret = PFIL_DROPPED; | ret = PFIL_DROPPED; | ||||
break; | break; | ||||
} | } | ||||
/* | /* | ||||
* XXX should read the return value. | * XXX should read the return value. | ||||
* dummynet normally eats the packet and sets *m0=NULL | * dummynet normally eats the packet and sets *m0=NULL | ||||
* unless the packet can be sent immediately. In this | * unless the packet can be sent immediately. In this | ||||
* case args is updated and we should re-run the | * case args is updated and we should re-run the | ||||
* check without clearing args. | * check without clearing args. | ||||
*/ | */ | ||||
if (*m0 != NULL) | if (*m0 != NULL) | ||||
goto again; | goto again; | ||||
ret = PFIL_CONSUMED; | ret = PFIL_CONSUMED; | ||||
break; | break; | ||||
case IP_FW_TEE: | case IP_FW_TEE: | ||||
case IP_FW_DIVERT: | case IP_FW_DIVERT: | ||||
if (ip_divert_ptr == NULL) { | if (ip_divert_ptr == NULL) { | ||||
ret = PFIL_DROPPED; | ret = PFIL_DROPPED; | ||||
break; | break; | ||||
} | } | ||||
MPASS(args.flags & IPFW_ARGS_REF); | MPASS(args.flags & IPFW_ARGS_REF); | ||||
(void )ipfw_divert(m0, dir, &args.rule, | (void )ipfw_divert(m0, &args, ipfw == IP_FW_TEE); | ||||
(ipfw == IP_FW_TEE) ? 1 : 0); | |||||
/* continue processing for the original packet (tee). */ | /* continue processing for the original packet (tee). */ | ||||
if (*m0) | if (*m0) | ||||
goto again; | goto again; | ||||
ret = PFIL_CONSUMED; | ret = PFIL_CONSUMED; | ||||
break; | break; | ||||
case IP_FW_NGTEE: | case IP_FW_NGTEE: | ||||
case IP_FW_NETGRAPH: | case IP_FW_NETGRAPH: | ||||
if (ng_ipfw_input_p == NULL) { | if (ng_ipfw_input_p == NULL) { | ||||
ret = PFIL_DROPPED; | ret = PFIL_DROPPED; | ||||
break; | break; | ||||
} | } | ||||
MPASS(args.flags & IPFW_ARGS_REF); | MPASS(args.flags & IPFW_ARGS_REF); | ||||
(void )ng_ipfw_input_p(m0, dir, &args, | (void )ng_ipfw_input_p(m0, &args, ipfw == IP_FW_NGTEE); | ||||
(ipfw == IP_FW_NGTEE) ? 1 : 0); | |||||
if (ipfw == IP_FW_NGTEE) /* ignore errors for NGTEE */ | if (ipfw == IP_FW_NGTEE) /* ignore errors for NGTEE */ | ||||
goto again; /* continue with packet */ | goto again; /* continue with packet */ | ||||
ret = PFIL_CONSUMED; | ret = PFIL_CONSUMED; | ||||
break; | break; | ||||
case IP_FW_NAT: | case IP_FW_NAT: | ||||
/* honor one-pass in case of successful nat */ | /* honor one-pass in case of successful nat */ | ||||
if (V_fw_one_pass) | if (V_fw_one_pass) | ||||
Show All 15 Lines | #endif /* INET || INET6 */ | ||||
return (ret); | return (ret); | ||||
} | } | ||||
/* | /* | ||||
* ipfw processing for ethernet packets (in and out). | * ipfw processing for ethernet packets (in and out). | ||||
*/ | */ | ||||
static pfil_return_t | static pfil_return_t | ||||
ipfw_check_frame(struct mbuf **m0, struct ifnet *ifp, int dir, | ipfw_check_frame(pfil_packet_t p, struct ifnet *ifp, int flags, | ||||
void *ruleset __unused, struct inpcb *inp) | void *ruleset __unused, struct inpcb *inp) | ||||
{ | { | ||||
struct ip_fw_args args; | struct ip_fw_args args; | ||||
struct ether_header save_eh; | |||||
struct ether_header *eh; | |||||
struct m_tag *mtag; | |||||
struct mbuf *m; | |||||
pfil_return_t ret; | pfil_return_t ret; | ||||
int i; | bool mem, realloc; | ||||
int ipfw; | |||||
if (flags & PFIL_MEMPTR) { | |||||
mem = true; | |||||
realloc = false; | |||||
args.flags = PFIL_LENGTH(flags) | IPFW_ARGS_ETHER; | |||||
args.mem = p.mem; | |||||
} else { | |||||
mem = realloc = false; | |||||
args.flags = IPFW_ARGS_ETHER; | args.flags = IPFW_ARGS_ETHER; | ||||
} | |||||
args.flags |= (flags & PFIL_IN) ? IPFW_ARGS_IN : IPFW_ARGS_OUT; | |||||
args.ifp = ifp; | |||||
args.inp = inp; | |||||
again: | again: | ||||
/* fetch start point from rule, if any. remove the tag if present. */ | if (!mem) { | ||||
mtag = m_tag_locate(*m0, MTAG_IPFW_RULE, 0, NULL); | /* | ||||
* Fetch start point from rule, if any. | |||||
* Remove the tag if present. | |||||
*/ | |||||
struct m_tag *mtag; | |||||
mtag = m_tag_locate(*p.m, MTAG_IPFW_RULE, 0, NULL); | |||||
if (mtag != NULL) { | if (mtag != NULL) { | ||||
args.rule = *((struct ipfw_rule_ref *)(mtag+1)); | args.rule = *((struct ipfw_rule_ref *)(mtag+1)); | ||||
m_tag_delete(*m0, mtag); | m_tag_delete(*p.m, mtag); | ||||
if (args.rule.info & IPFW_ONEPASS) | if (args.rule.info & IPFW_ONEPASS) | ||||
return (0); | return (PFIL_PASS); | ||||
args.flags |= IPFW_ARGS_REF; | args.flags |= IPFW_ARGS_REF; | ||||
} | } | ||||
args.m = *p.m; | |||||
/* I need some amt of data to be contiguous */ | |||||
m = *m0; | |||||
i = min(m->m_pkthdr.len, max_protohdr); | |||||
if (m->m_len < i) { | |||||
m = m_pullup(m, i); | |||||
if (m == NULL) { | |||||
*m0 = m; | |||||
return (0); | |||||
} | } | ||||
} | |||||
eh = mtod(m, struct ether_header *); | |||||
save_eh = *eh; /* save copy for restore below */ | |||||
m_adj(m, ETHER_HDR_LEN); /* strip ethernet header */ | |||||
args.m = m; /* the packet we are looking at */ | ipfw = ipfw_chk(&args); | ||||
args.oif = dir & PFIL_OUT ? ifp: NULL; /* destination, if any */ | |||||
args.eh = &save_eh; /* MAC header for bridged/MAC packets */ | |||||
args.inp = inp; /* used by ipfw uid/gid/jail rules */ | |||||
i = ipfw_chk(&args); | |||||
m = args.m; | |||||
if (m != NULL) { | |||||
/* | |||||
* Restore Ethernet header, as needed, in case the | |||||
* mbuf chain was replaced by ipfw. | |||||
*/ | |||||
M_PREPEND(m, ETHER_HDR_LEN, M_NOWAIT); | |||||
if (m == NULL) { | |||||
*m0 = NULL; | |||||
return (0); | |||||
} | |||||
if (eh != mtod(m, struct ether_header *)) | |||||
bcopy(&save_eh, mtod(m, struct ether_header *), | |||||
ETHER_HDR_LEN); | |||||
} | |||||
*m0 = m; | |||||
ret = PFIL_PASS; | ret = PFIL_PASS; | ||||
/* Check result of ipfw_chk() */ | switch (ipfw) { | ||||
switch (i) { | |||||
case IP_FW_PASS: | case IP_FW_PASS: | ||||
break; | break; | ||||
case IP_FW_DENY: | case IP_FW_DENY: | ||||
ret = PFIL_DROPPED; | ret = PFIL_DROPPED; | ||||
break; | break; | ||||
case IP_FW_DUMMYNET: | case IP_FW_DUMMYNET: | ||||
if (ip_dn_io_ptr == NULL) { | if (ip_dn_io_ptr == NULL) { | ||||
ret = PFIL_DROPPED; | ret = PFIL_DROPPED; | ||||
break; | break; | ||||
} | } | ||||
*m0 = NULL; | if (mem) { | ||||
dir = (dir & PFIL_IN) ? DIR_IN : DIR_OUT; | *p.m = m_devget(p.mem, PFIL_LENGTH(flags), 0, ifp, | ||||
NULL); | |||||
if (*p.m == NULL) { | |||||
ret = PFIL_DROPPED; | |||||
break; | |||||
} | |||||
mem = false; | |||||
realloc = true; | |||||
} | |||||
MPASS(args.flags & IPFW_ARGS_REF); | MPASS(args.flags & IPFW_ARGS_REF); | ||||
ip_dn_io_ptr(&m, dir | PROTO_LAYER2, &args); | ip_dn_io_ptr(p.m, &args); | ||||
return (PFIL_CONSUMED); | return (PFIL_CONSUMED); | ||||
case IP_FW_NGTEE: | case IP_FW_NGTEE: | ||||
case IP_FW_NETGRAPH: | case IP_FW_NETGRAPH: | ||||
if (ng_ipfw_input_p == NULL) { | if (ng_ipfw_input_p == NULL) { | ||||
ret = PFIL_DROPPED; | ret = PFIL_DROPPED; | ||||
break; | break; | ||||
} | } | ||||
if (mem) { | |||||
*p.m = m_devget(p.mem, PFIL_LENGTH(flags), 0, ifp, | |||||
NULL); | |||||
if (*p.m == NULL) { | |||||
ret = PFIL_DROPPED; | |||||
break; | |||||
} | |||||
mem = false; | |||||
realloc = true; | |||||
} | |||||
MPASS(args.flags & IPFW_ARGS_REF); | MPASS(args.flags & IPFW_ARGS_REF); | ||||
(void )ng_ipfw_input_p(m0, (dir & PFIL_IN) ? DIR_IN : DIR_OUT, | (void )ng_ipfw_input_p(p.m, &args, ipfw == IP_FW_NGTEE); | ||||
&args, (i == IP_FW_NGTEE) ? 1 : 0); | if (ipfw == IP_FW_NGTEE) /* ignore errors for NGTEE */ | ||||
if (i == IP_FW_NGTEE) /* ignore errors for NGTEE */ | |||||
goto again; /* continue with packet */ | goto again; /* continue with packet */ | ||||
ret = PFIL_CONSUMED; | ret = PFIL_CONSUMED; | ||||
break; | break; | ||||
default: | default: | ||||
KASSERT(0, ("%s: unknown retval", __func__)); | KASSERT(0, ("%s: unknown retval", __func__)); | ||||
} | } | ||||
if (ret != PFIL_PASS) { | if (!mem && ret != PFIL_PASS) { | ||||
if (*m0) | if (*p.m) | ||||
FREE_PKT(*m0); | FREE_PKT(*p.m); | ||||
*m0 = NULL; | *p.m = NULL; | ||||
} | } | ||||
if (realloc && ret == PFIL_PASS) | |||||
ret = PFIL_REALLOCED; | |||||
return (ret); | return (ret); | ||||
} | } | ||||
/* do the divert, return 1 on error 0 on success */ | /* do the divert, return 1 on error 0 on success */ | ||||
static int | static int | ||||
ipfw_divert(struct mbuf **m0, int incoming, struct ipfw_rule_ref *rule, | ipfw_divert(struct mbuf **m0, struct ip_fw_args *args, bool tee) | ||||
int tee) | |||||
{ | { | ||||
/* | /* | ||||
* ipfw_chk() has already tagged the packet with the divert tag. | * ipfw_chk() has already tagged the packet with the divert tag. | ||||
* If tee is set, copy packet and return original. | * If tee is set, copy packet and return original. | ||||
* If not tee, consume packet and send it to divert socket. | * If not tee, consume packet and send it to divert socket. | ||||
*/ | */ | ||||
struct mbuf *clone; | struct mbuf *clone; | ||||
struct ip *ip = mtod(*m0, struct ip *); | struct ip *ip = mtod(*m0, struct ip *); | ||||
struct m_tag *tag; | struct m_tag *tag; | ||||
/* Cloning needed for tee? */ | /* Cloning needed for tee? */ | ||||
if (tee == 0) { | if (tee == false) { | ||||
clone = *m0; /* use the original mbuf */ | clone = *m0; /* use the original mbuf */ | ||||
*m0 = NULL; | *m0 = NULL; | ||||
} else { | } else { | ||||
clone = m_dup(*m0, M_NOWAIT); | clone = m_dup(*m0, M_NOWAIT); | ||||
/* If we cannot duplicate the mbuf, we sacrifice the divert | /* If we cannot duplicate the mbuf, we sacrifice the divert | ||||
* chain and continue with the tee-ed packet. | * chain and continue with the tee-ed packet. | ||||
*/ | */ | ||||
if (clone == NULL) | if (clone == NULL) | ||||
return 1; | return 1; | ||||
} | } | ||||
/* | /* | ||||
* Divert listeners can normally handle non-fragmented packets, | * Divert listeners can normally handle non-fragmented packets, | ||||
* but we can only reass in the non-tee case. | * but we can only reass in the non-tee case. | ||||
* This means that listeners on a tee rule may get fragments, | * This means that listeners on a tee rule may get fragments, | ||||
* and have to live with that. | * and have to live with that. | ||||
* Note that we now have the 'reass' ipfw option so if we care | * Note that we now have the 'reass' ipfw option so if we care | ||||
* we can do it before a 'tee'. | * we can do it before a 'tee'. | ||||
*/ | */ | ||||
if (!tee) switch (ip->ip_v) { | if (tee == false) switch (ip->ip_v) { | ||||
case IPVERSION: | case IPVERSION: | ||||
if (ntohs(ip->ip_off) & (IP_MF | IP_OFFMASK)) { | if (ntohs(ip->ip_off) & (IP_MF | IP_OFFMASK)) { | ||||
int hlen; | int hlen; | ||||
struct mbuf *reass; | struct mbuf *reass; | ||||
reass = ip_reass(clone); /* Reassemble packet. */ | reass = ip_reass(clone); /* Reassemble packet. */ | ||||
if (reass == NULL) | if (reass == NULL) | ||||
return 0; /* not an error */ | return 0; /* not an error */ | ||||
Show All 32 Lines | #endif | ||||
/* attach a tag to the packet with the reinject info */ | /* attach a tag to the packet with the reinject info */ | ||||
tag = m_tag_alloc(MTAG_IPFW_RULE, 0, | tag = m_tag_alloc(MTAG_IPFW_RULE, 0, | ||||
sizeof(struct ipfw_rule_ref), M_NOWAIT); | sizeof(struct ipfw_rule_ref), M_NOWAIT); | ||||
if (tag == NULL) { | if (tag == NULL) { | ||||
FREE_PKT(clone); | FREE_PKT(clone); | ||||
return 1; | return 1; | ||||
} | } | ||||
*((struct ipfw_rule_ref *)(tag+1)) = *rule; | *((struct ipfw_rule_ref *)(tag+1)) = args->rule; | ||||
m_tag_prepend(clone, tag); | m_tag_prepend(clone, tag); | ||||
/* Do the dirty job... */ | /* Do the dirty job... */ | ||||
ip_divert_ptr(clone, incoming); | ip_divert_ptr(clone, args->flags & IPFW_ARGS_IN); | ||||
return 0; | return 0; | ||||
} | } | ||||
/* | /* | ||||
* attach or detach hooks for a given protocol family | * attach or detach hooks for a given protocol family | ||||
*/ | */ | ||||
VNET_DEFINE_STATIC(pfil_hook_t, ipfw_inet_hook); | VNET_DEFINE_STATIC(pfil_hook_t, ipfw_inet_hook); | ||||
#define V_ipfw_inet_hook VNET(ipfw_inet_hook) | #define V_ipfw_inet_hook VNET(ipfw_inet_hook) | ||||
#ifdef INET6 | #ifdef INET6 | ||||
VNET_DEFINE_STATIC(pfil_hook_t, ipfw_inet6_hook); | VNET_DEFINE_STATIC(pfil_hook_t, ipfw_inet6_hook); | ||||
#define V_ipfw_inet6_hook VNET(ipfw_inet6_hook) | #define V_ipfw_inet6_hook VNET(ipfw_inet6_hook) | ||||
#endif | #endif | ||||
VNET_DEFINE_STATIC(pfil_hook_t, ipfw_link_hook); | VNET_DEFINE_STATIC(pfil_hook_t, ipfw_link_hook); | ||||
#define V_ipfw_link_hook VNET(ipfw_link_hook) | #define V_ipfw_link_hook VNET(ipfw_link_hook) | ||||
static int | static int | ||||
ipfw_hook(int onoff, int pf) | ipfw_hook(int onoff, int pf) | ||||
{ | { | ||||
struct pfil_hook_args pha; | struct pfil_hook_args pha; | ||||
struct pfil_link_args pla; | struct pfil_link_args pla; | ||||
pfil_hook_t *h; | pfil_hook_t *h; | ||||
pha.pa_version = PFIL_VERSION; | pha.pa_version = PFIL_VERSION; | ||||
pha.pa_flags = PFIL_IN | PFIL_OUT; | pha.pa_flags = PFIL_IN | PFIL_OUT | PFIL_MEMPTR; | ||||
pha.pa_modname = "ipfw"; | pha.pa_modname = "ipfw"; | ||||
pha.pa_ruleset = NULL; | pha.pa_ruleset = NULL; | ||||
pla.pa_version = PFIL_VERSION; | pla.pa_version = PFIL_VERSION; | ||||
pla.pa_flags = PFIL_IN | PFIL_OUT | | pla.pa_flags = PFIL_IN | PFIL_OUT | | ||||
PFIL_HEADPTR | PFIL_HOOKPTR; | PFIL_HEADPTR | PFIL_HOOKPTR; | ||||
switch (pf) { | switch (pf) { | ||||
▲ Show 20 Lines • Show All 103 Lines • Show Last 20 Lines |