Commit 6ec6f76e authored by David S. Miller's avatar David S. Miller

[NETFILTER]: Teach ip_fw_compat and modules to handle non-linear SKBs.

Much help provided by Rusty Russell in fixing device leak
and TOS modification handling bugs.
parent a3bc528c
......@@ -21,20 +21,20 @@ struct firewall_ops
{
struct firewall_ops *next;
int (*fw_forward)(struct firewall_ops *this, int pf,
struct net_device *dev, void *phdr, void *arg,
struct net_device *dev, void *arg,
struct sk_buff **pskb);
int (*fw_input)(struct firewall_ops *this, int pf,
struct net_device *dev, void *phdr, void *arg,
struct net_device *dev, void *arg,
struct sk_buff **pskb);
int (*fw_output)(struct firewall_ops *this, int pf,
struct net_device *dev, void *phdr, void *arg,
struct net_device *dev, void *arg,
struct sk_buff **pskb);
/* These may be NULL. */
int (*fw_acct_in)(struct firewall_ops *this, int pf,
struct net_device *dev, void *phdr, void *arg,
struct net_device *dev, void *arg,
struct sk_buff **pskb);
int (*fw_acct_out)(struct firewall_ops *this, int pf,
struct net_device *dev, void *phdr, void *arg,
struct net_device *dev, void *arg,
struct sk_buff **pskb);
};
......
......@@ -250,7 +250,7 @@ extern int ip_masq_ctl(int, void *, int);
extern int ip_fw_masq_timeouts(void *user, int len);
extern int ip_fw_chk(struct iphdr *, struct net_device *, __u16 *,
extern int ip_fw_chk(struct sk_buff **, struct net_device *, __u16 *,
struct ip_fw *, int, int);
#endif /* KERNEL */
#endif /* _IP_FW_H */
......@@ -51,31 +51,17 @@ fw_in(unsigned int hooknum,
int ret = FW_BLOCK;
u_int16_t redirpt;
/* FIXME: Push down to extensions --RR */
if (skb_is_nonlinear(*pskb) && skb_linearize(*pskb, GFP_ATOMIC) != 0)
return NF_DROP;
/* Assume worse case: any hook could change packet */
(*pskb)->nfcache |= NFC_UNKNOWN | NFC_ALTERED;
if ((*pskb)->ip_summed == CHECKSUM_HW)
(*pskb)->ip_summed = CHECKSUM_NONE;
/* Firewall rules can alter TOS: raw socket (tcpdump) may have
clone of incoming skb: don't disturb it --RR */
if (skb_cloned(*pskb) && !(*pskb)->sk) {
struct sk_buff *nskb = skb_copy(*pskb, GFP_ATOMIC);
if (!nskb)
return NF_DROP;
kfree_skb(*pskb);
*pskb = nskb;
}
switch (hooknum) {
case NF_IP_PRE_ROUTING:
if (fwops->fw_acct_in)
fwops->fw_acct_in(fwops, PF_INET,
(struct net_device *)in,
(*pskb)->nh.raw, &redirpt, pskb);
&redirpt, pskb);
if ((*pskb)->nh.iph->frag_off & htons(IP_MF|IP_OFFSET)) {
*pskb = ip_ct_gather_frags(*pskb);
......@@ -85,7 +71,7 @@ fw_in(unsigned int hooknum,
}
ret = fwops->fw_input(fwops, PF_INET, (struct net_device *)in,
(*pskb)->nh.raw, &redirpt, pskb);
&redirpt, pskb);
break;
case NF_IP_FORWARD:
......@@ -95,18 +81,18 @@ fw_in(unsigned int hooknum,
ret = FW_ACCEPT;
else ret = fwops->fw_forward(fwops, PF_INET,
(struct net_device *)out,
(*pskb)->nh.raw, &redirpt, pskb);
&redirpt, pskb);
break;
case NF_IP_POST_ROUTING:
ret = fwops->fw_output(fwops, PF_INET,
(struct net_device *)out,
(*pskb)->nh.raw, &redirpt, pskb);
&redirpt, pskb);
if (ret == FW_ACCEPT || ret == FW_SKIP) {
if (fwops->fw_acct_out)
fwops->fw_acct_out(fwops, PF_INET,
(struct net_device *)out,
(*pskb)->nh.raw, &redirpt,
&redirpt,
pskb);
/* ip_conntrack_confirm return NF_DROP or NF_ACCEPT */
......@@ -169,10 +155,6 @@ static unsigned int fw_confirm(unsigned int hooknum,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
/* FIXME: Push down to extensions --RR */
if (skb_is_nonlinear(*pskb) && skb_linearize(*pskb, GFP_ATOMIC) != 0)
return NF_DROP;
return ip_conntrack_confirm(*pskb);
}
......
......@@ -94,6 +94,7 @@
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4/compat_firewall.h>
#include <linux/netfilter_ipv4/ipchains_core.h>
#include <linux/netfilter_ipv4/ip_nat_core.h>
#include <net/checksum.h>
#include <linux/proc_fs.h>
......@@ -280,11 +281,13 @@ extern inline int port_match(__u16 min, __u16 max, __u16 port,
/* Returns whether matches rule or not. */
static int ip_rule_match(struct ip_fwkernel *f,
const char *ifname,
struct iphdr *ip,
struct sk_buff **pskb,
char tcpsyn,
__u16 src_port, __u16 dst_port,
char isfrag)
{
struct iphdr *ip = (*pskb)->nh.iph;
#define FWINV(bool,invflg) ((bool) ^ !!(f->ipfw.fw_invflg & invflg))
/*
* This is a bit simpler as we don't have to walk
......@@ -401,7 +404,7 @@ static const char *branchname(struct ip_chain *branch,int simplebranch)
* VERY ugly piece of code which actually
* makes kernel printf for matching packets...
*/
static void dump_packet(const struct iphdr *ip,
static void dump_packet(struct sk_buff **pskb,
const char *ifname,
struct ip_fwkernel *f,
const ip_chainlabel chainlabel,
......@@ -410,7 +413,7 @@ static void dump_packet(const struct iphdr *ip,
unsigned int count,
int syn)
{
__u32 *opt = (__u32 *) (ip + 1);
__u32 *opt = (__u32 *) ((*pskb)->nh.iph + 1);
int opti;
if (f) {
......@@ -422,13 +425,18 @@ static void dump_packet(const struct iphdr *ip,
printk("%s PROTO=%d %u.%u.%u.%u:%hu %u.%u.%u.%u:%hu"
" L=%hu S=0x%2.2hX I=%hu F=0x%4.4hX T=%hu",
ifname, ip->protocol, NIPQUAD(ip->saddr),
src_port, NIPQUAD(ip->daddr),
ifname, (*pskb)->nh.iph->protocol,
NIPQUAD((*pskb)->nh.iph->saddr),
src_port,
NIPQUAD((*pskb)->nh.iph->daddr),
dst_port,
ntohs(ip->tot_len), ip->tos, ntohs(ip->id),
ntohs(ip->frag_off), ip->ttl);
ntohs((*pskb)->nh.iph->tot_len),
(*pskb)->nh.iph->tos,
ntohs((*pskb)->nh.iph->id),
ntohs((*pskb)->nh.iph->frag_off),
(*pskb)->nh.iph->ttl);
for (opti = 0; opti < (ip->ihl - sizeof(struct iphdr) / 4); opti++)
for (opti = 0; opti < ((*pskb)->nh.iph->ihl - sizeof(struct iphdr) / 4); opti++)
printk(" O=0x%8.8X", *opt++);
printk(" %s(#%d)\n", syn ? "SYN " : /* "PENANCE" */ "", count);
}
......@@ -509,34 +517,35 @@ static void cleanup(struct ip_chain *chain,
static inline int
ip_fw_domatch(struct ip_fwkernel *f,
struct iphdr *ip,
const char *rif,
const ip_chainlabel label,
struct sk_buff *skb,
struct sk_buff **pskb,
unsigned int slot,
__u16 src_port, __u16 dst_port,
unsigned int count,
int tcpsyn)
int tcpsyn,
unsigned char *tos)
{
f->counters[slot].bcnt+=ntohs(ip->tot_len);
f->counters[slot].bcnt+=ntohs((*pskb)->nh.iph->tot_len);
f->counters[slot].pcnt++;
if (f->ipfw.fw_flg & IP_FW_F_PRN) {
dump_packet(ip,rif,f,label,src_port,dst_port,count,tcpsyn);
dump_packet(pskb,rif,f,label,src_port,dst_port,count,tcpsyn);
}
ip->tos = (ip->tos & f->ipfw.fw_tosand) ^ f->ipfw.fw_tosxor;
*tos = (*tos & f->ipfw.fw_tosand) ^ f->ipfw.fw_tosxor;
/* This functionality is useless in stock 2.0.x series, but we don't
* discard the mark thing altogether, to avoid breaking ipchains (and,
* more importantly, the ipfwadm wrapper) --PR */
if (f->ipfw.fw_flg & IP_FW_F_MARKABS) {
skb->nfmark = f->ipfw.fw_mark;
(*pskb)->nfmark = f->ipfw.fw_mark;
} else {
skb->nfmark += f->ipfw.fw_mark;
(*pskb)->nfmark += f->ipfw.fw_mark;
}
if (f->ipfw.fw_flg & IP_FW_F_NETLINK) {
#if defined(CONFIG_NETLINK_DEV) || defined(CONFIG_NETLINK_DEV_MODULE)
size_t len = min_t(unsigned int, f->ipfw.fw_outputsize, ntohs(ip->tot_len))
+ sizeof(__u32) + sizeof(skb->nfmark) + IFNAMSIZ;
size_t len = min_t(unsigned int, f->ipfw.fw_outputsize, ntohs((*pskb)->nh.iph->tot_len))
+ sizeof(__u32) + sizeof((*pskb)->nfmark) + IFNAMSIZ;
struct sk_buff *outskb=alloc_skb(len, GFP_ATOMIC);
duprintf("Sending packet out NETLINK (length = %u).\n",
......@@ -545,10 +554,13 @@ ip_fw_domatch(struct ip_fwkernel *f,
/* Prepend length, mark & interface */
skb_put(outskb, len);
*((__u32 *)outskb->data) = (__u32)len;
*((__u32 *)(outskb->data+sizeof(__u32))) = skb->nfmark;
*((__u32 *)(outskb->data+sizeof(__u32))) =
(*pskb)->nfmark;
strcpy(outskb->data+sizeof(__u32)*2, rif);
memcpy(outskb->data+sizeof(__u32)*2+IFNAMSIZ, ip,
len-(sizeof(__u32)*2+IFNAMSIZ));
skb_copy_bits(*pskb,
((char *)(*pskb)->nh.iph - (char *)(*pskb)->data),
outskb->data+sizeof(__u32)*2+IFNAMSIZ,
len-(sizeof(__u32)*2+IFNAMSIZ));
netlink_broadcast(ipfwsk, outskb, 0, ~0, GFP_ATOMIC);
}
else {
......@@ -571,22 +583,18 @@ ip_fw_domatch(struct ip_fwkernel *f,
* user checking mode (counters are not updated, TOS & mark not done).
*/
static int
ip_fw_check(struct iphdr *ip,
const char *rif,
ip_fw_check(const char *rif,
__u16 *redirport,
struct ip_chain *chain,
struct sk_buff *skb,
struct sk_buff **pskb,
unsigned int slot,
int testing)
{
struct tcphdr *tcp=(struct tcphdr *)((__u32 *)ip+ip->ihl);
struct udphdr *udp=(struct udphdr *)((__u32 *)ip+ip->ihl);
struct icmphdr *icmp=(struct icmphdr *)((__u32 *)ip+ip->ihl);
__u32 src, dst;
__u16 src_port = 0xFFFF, dst_port = 0xFFFF;
char tcpsyn=0;
__u16 offset;
unsigned char oldtos;
unsigned char tos;
struct ip_fwkernel *f;
int ret = FW_SKIP+2;
unsigned int count;
......@@ -598,7 +606,7 @@ ip_fw_check(struct iphdr *ip,
* rule is also a fragment-specific rule, non-fragments won't
* match it. */
offset = ntohs(ip->frag_off) & IP_OFFSET;
offset = ntohs((*pskb)->nh.iph->frag_off) & IP_OFFSET;
/*
* Don't allow a fragment of TCP 8 bytes in. Nobody
......@@ -606,10 +614,10 @@ ip_fw_check(struct iphdr *ip,
* in by doing a flag overwrite to pass the direction
* checks.
*/
if (offset == 1 && ip->protocol == IPPROTO_TCP) {
if (offset == 1 && (*pskb)->nh.iph->protocol == IPPROTO_TCP) {
if (!testing && net_ratelimit()) {
printk("Suspect TCP fragment.\n");
dump_packet(ip,rif,NULL,NULL,0,0,0,0);
dump_packet(pskb,rif,NULL,NULL,0,0,0,0);
}
return FW_BLOCK;
}
......@@ -621,7 +629,7 @@ ip_fw_check(struct iphdr *ip,
*/
if (offset == 0) {
unsigned int size_req;
switch (ip->protocol) {
switch ((*pskb)->nh.iph->protocol) {
case IPPROTO_TCP:
/* Don't care about things past flags word */
size_req = 16;
......@@ -640,18 +648,19 @@ ip_fw_check(struct iphdr *ip,
* used to rewrite port information, and thus should
* be blocked.
*/
if (ntohs(ip->tot_len) < (ip->ihl<<2)+size_req) {
if (ntohs((*pskb)->nh.iph->tot_len) <
((*pskb)->nh.iph->ihl<<2)+size_req) {
if (!testing && net_ratelimit()) {
printk("Suspect short first fragment.\n");
dump_packet(ip,rif,NULL,NULL,0,0,0,0);
dump_packet(pskb,rif,NULL,NULL,0,0,0,0);
}
return FW_BLOCK;
}
}
src = ip->saddr;
dst = ip->daddr;
oldtos = ip->tos;
src = (*pskb)->nh.iph->saddr;
dst = (*pskb)->nh.iph->daddr;
tos = (*pskb)->nh.iph->tos;
/*
* If we got interface from which packet came
......@@ -662,47 +671,68 @@ ip_fw_check(struct iphdr *ip,
*/
dprintf("Packet ");
switch(ip->protocol)
{
switch ((*pskb)->nh.iph->protocol) {
case IPPROTO_TCP:
dprintf("TCP ");
if (!offset) {
src_port=ntohs(tcp->source);
dst_port=ntohs(tcp->dest);
struct tcphdr tcph;
if (skb_copy_bits(*pskb,
(*pskb)->nh.iph->ihl * 4,
&tcph, sizeof(tcph)))
return FW_BLOCK;
src_port = ntohs(tcph.source);
dst_port = ntohs(tcph.dest);
/* Connection initilisation can only
* be made when the syn bit is set and
* neither of the ack or reset is
* set. */
if(tcp->syn && !(tcp->ack || tcp->rst))
tcpsyn=1;
if (tcph.syn && !(tcph.ack || tcph.rst))
tcpsyn = 1;
}
break;
case IPPROTO_UDP:
dprintf("UDP ");
if (!offset) {
src_port=ntohs(udp->source);
dst_port=ntohs(udp->dest);
struct udphdr udph;
if (skb_copy_bits(*pskb,
(*pskb)->nh.iph->ihl * 4,
&udph, sizeof(udph)))
return FW_BLOCK;
src_port = ntohs(udph.source);
dst_port = ntohs(udph.dest);
}
break;
case IPPROTO_ICMP:
if (!offset) {
src_port=(__u16)icmp->type;
dst_port=(__u16)icmp->code;
struct icmphdr icmph;
if (skb_copy_bits(*pskb,
(*pskb)->nh.iph->ihl * 4,
&icmph, sizeof(icmph)))
return FW_BLOCK;
src_port = (__u16) icmph.type;
dst_port = (__u16) icmph.code;
}
dprintf("ICMP ");
break;
default:
dprintf("p=%d ",ip->protocol);
dprintf("p=%d ", (*pskb)->nh.iph->protocol);
break;
}
#ifdef DEBUG_IP_FIREWALL
print_ip(ip->saddr);
print_ip((*pskb)->nh.iph->saddr);
if (offset)
dprintf(":fragment (%i) ", ((int)offset)<<2);
else if (ip->protocol==IPPROTO_TCP || ip->protocol==IPPROTO_UDP
|| ip->protocol==IPPROTO_ICMP)
else if ((*pskb)->nh.iph->protocol == IPPROTO_TCP ||
(*pskb)->nh.iph->protocol == IPPROTO_UDP ||
(*pskb)->nh.iph->protocol == IPPROTO_ICMP)
dprintf(":%hu:%hu", src_port, dst_port);
dprintf("\n");
#endif
......@@ -715,13 +745,14 @@ ip_fw_check(struct iphdr *ip,
count = 0;
for (; f; f = f->next) {
count++;
if (ip_rule_match(f,rif,ip,
tcpsyn,src_port,dst_port,offset)) {
if (ip_rule_match(f, rif, pskb,
tcpsyn, src_port, dst_port,
offset)) {
if (!testing
&& !ip_fw_domatch(f, ip, rif, chain->label,
skb, slot,
&& !ip_fw_domatch(f, rif, chain->label,
pskb, slot,
src_port, dst_port,
count, tcpsyn)) {
count, tcpsyn, &tos)) {
ret = FW_BLOCK;
cleanup(chain, 0, slot);
goto out;
......@@ -780,7 +811,7 @@ ip_fw_check(struct iphdr *ip,
if (!testing) {
chain->reent[slot].counters.pcnt++;
chain->reent[slot].counters.bcnt
+= ntohs(ip->tot_len);
+= ntohs((*pskb)->nh.iph->tot_len);
}
}
}
......@@ -790,10 +821,16 @@ ip_fw_check(struct iphdr *ip,
if (!testing) FWC_READ_UNLOCK(&ip_fw_lock);
/* Recalculate checksum if not going to reject, and TOS changed. */
if (ip->tos != oldtos
if ((*pskb)->nh.iph->tos != tos
&& ret != FW_REJECT && ret != FW_BLOCK
&& !testing)
ip_send_check(ip);
&& !testing) {
if (!skb_ip_make_writable(pskb, offsetof(struct iphdr, tos)+1))
ret = FW_BLOCK;
else {
(*pskb)->nh.iph->tos = tos;
ip_send_check((*pskb)->nh.iph);
}
}
if (ret == FW_REDIRECT && redirport) {
if ((*redirport = htons(f->ipfw.fw_redirpt)) == 0) {
......@@ -1349,18 +1386,40 @@ int ip_fw_ctl(int cmd, void *m, int len)
if ((chain = find_label(new->fwt_label)) == NULL)
ret = ENOENT;
else {
struct sk_buff *tmp_skb;
int hdrlen;
hdrlen = sizeof(struct ip_fwpkt) -
sizeof(struct in_addr) -
IFNAMSIZ;
ip = &(new->fwt_packet.fwp_iph);
if (ip->ihl != sizeof(struct iphdr) / sizeof(int)) {
duprintf("ip_fw_ctl: ip->ihl=%d, want %d\n",
ip->ihl,
sizeof(struct iphdr) / sizeof(int));
ret = EINVAL;
}
else {
ret = ip_fw_check(ip, new->fwt_packet.fwp_vianame,
/* Fix this one up by hand, who knows how many
* tools will break if we start to barf on this.
*/
if (ntohs(ip->tot_len) > hdrlen)
ip->tot_len = htons(hdrlen);
if (ip->ihl != sizeof(struct iphdr) / sizeof(u32)) {
duprintf("ip_fw_ctl: ip->ihl=%d, want %d\n",
ip->ihl,
sizeof(struct iphdr) / sizeof(u32));
ret = EINVAL;
} else if ((tmp_skb = alloc_skb(hdrlen,
GFP_ATOMIC)) == NULL) {
duprintf("ip_fw_ctl: tmp_skb alloc failure\n");
ret = EFAULT;
} else {
skb_reserve(tmp_skb, hdrlen);
skb_push(tmp_skb, hdrlen);
memcpy(tmp_skb->data, ip, hdrlen);
tmp_skb->nh.raw =
(unsigned char *) tmp_skb->data;
ret = ip_fw_check(new->fwt_packet.fwp_vianame,
NULL, chain,
NULL, SLOT_NUMBER(), 1);
&tmp_skb, SLOT_NUMBER(), 1);
kfree_skb(tmp_skb);
switch (ret) {
case FW_ACCEPT:
ret = 0; break;
......@@ -1690,41 +1749,37 @@ static int ip_chain_name_procinfo(char *buffer, char **start,
* Interface to the generic firewall chains.
*/
int ipfw_input_check(struct firewall_ops *this, int pf,
struct net_device *dev, void *phdr, void *arg,
struct net_device *dev, void *arg,
struct sk_buff **pskb)
{
return ip_fw_check(phdr, dev->name,
arg, IP_FW_INPUT_CHAIN, *pskb, SLOT_NUMBER(), 0);
return ip_fw_check(dev->name,
arg, IP_FW_INPUT_CHAIN, pskb, SLOT_NUMBER(), 0);
}
int ipfw_output_check(struct firewall_ops *this, int pf,
struct net_device *dev, void *phdr, void *arg,
struct net_device *dev, void *arg,
struct sk_buff **pskb)
{
/* Locally generated bogus packets by root. <SIGH>. */
if (((struct iphdr *)phdr)->ihl * 4 < sizeof(struct iphdr)
|| (*pskb)->len < sizeof(struct iphdr))
if ((*pskb)->len < sizeof(struct iphdr) ||
(*pskb)->nh.iph->ihl * 4 < sizeof(struct iphdr))
return FW_ACCEPT;
return ip_fw_check(phdr, dev->name,
arg, IP_FW_OUTPUT_CHAIN, *pskb, SLOT_NUMBER(), 0);
return ip_fw_check(dev->name,
arg, IP_FW_OUTPUT_CHAIN, pskb, SLOT_NUMBER(), 0);
}
int ipfw_forward_check(struct firewall_ops *this, int pf,
struct net_device *dev, void *phdr, void *arg,
struct net_device *dev, void *arg,
struct sk_buff **pskb)
{
return ip_fw_check(phdr, dev->name,
arg, IP_FW_FORWARD_CHAIN, *pskb, SLOT_NUMBER(), 0);
return ip_fw_check(dev->name,
arg, IP_FW_FORWARD_CHAIN, pskb, SLOT_NUMBER(), 0);
}
struct firewall_ops ipfw_ops=
{
NULL,
ipfw_forward_check,
ipfw_input_check,
ipfw_output_check,
NULL,
NULL
struct firewall_ops ipfw_ops = {
.fw_forward = ipfw_forward_check,
.fw_input = ipfw_input_check,
.fw_output = ipfw_output_check,
};
int ipfw_init_or_cleanup(int init)
......
......@@ -126,6 +126,7 @@
#include <linux/netfilter_ipv4/ipfwadm_core.h>
#include <linux/netfilter_ipv4/compat_firewall.h>
#include <linux/netfilter_ipv4/lockhelp.h>
#include <linux/netfilter_ipv4/ip_nat_core.h>
#include <net/checksum.h>
#include <linux/proc_fs.h>
......@@ -259,18 +260,18 @@ static char *rule_name(struct ip_fw *f, int mode, char *buf)
}
}
static void print_packet(struct iphdr *ip,
static void print_packet(struct sk_buff **pskb,
u16 src_port, u16 dst_port, u16 icmp_type,
char *chain, char *rule, char *devname)
{
__u32 *opt = (__u32 *) (ip + 1);
__u32 *opt = (__u32 *) ((*pskb)->nh.iph + 1);
int opti;
__u16 foff = ntohs(ip->frag_off);
__u16 foff = ntohs((*pskb)->nh.iph->frag_off);
int protocol = (*pskb)->nh.iph->protocol;
printk(KERN_INFO "IP %s %s%s", chain, rule, devname);
switch(ip->protocol)
{
switch (protocol) {
case IPPROTO_TCP:
printk(" TCP ");
break;
......@@ -281,22 +282,28 @@ static void print_packet(struct iphdr *ip,
printk(" ICMP/%d ", icmp_type);
break;
default:
printk(" PROTO=%d ", ip->protocol);
printk(" PROTO=%d ", protocol);
break;
}
print_ip(ip->saddr);
if(ip->protocol == IPPROTO_TCP || ip->protocol == IPPROTO_UDP)
};
print_ip((*pskb)->nh.iph->saddr);
if (protocol == IPPROTO_TCP || protocol == IPPROTO_UDP)
printk(":%hu", src_port);
printk(" ");
print_ip(ip->daddr);
if(ip->protocol == IPPROTO_TCP || ip->protocol == IPPROTO_UDP)
print_ip((*pskb)->nh.iph->daddr);
if (protocol == IPPROTO_TCP || protocol == IPPROTO_UDP)
printk(":%hu", dst_port);
printk(" L=%hu S=0x%2.2hX I=%hu FO=0x%4.4hX T=%hu",
ntohs(ip->tot_len), ip->tos, ntohs(ip->id),
foff & IP_OFFSET, ip->ttl);
if (foff & IP_DF) printk(" DF=1");
if (foff & IP_MF) printk(" MF=1");
for (opti = 0; opti < (ip->ihl - sizeof(struct iphdr) / 4); opti++)
ntohs((*pskb)->nh.iph->tot_len),
(*pskb)->nh.iph->tos,
ntohs((*pskb)->nh.iph->id),
foff & IP_OFFSET,
(*pskb)->nh.iph->ttl);
if (foff & IP_DF)
printk(" DF=1");
if (foff & IP_MF)
printk(" MF=1");
for (opti = 0; opti < ((*pskb)->nh.iph->ihl - sizeof(struct iphdr) / 4); opti++)
printk(" O=0x%8.8X", *opt++);
printk("\n");
}
......@@ -314,13 +321,11 @@ static void print_packet(struct iphdr *ip,
*/
int ip_fw_chk(struct iphdr *ip, struct net_device *rif, __u16 *redirport,
int ip_fw_chk(struct sk_buff **pskb,
struct net_device *rif, __u16 *redirport,
struct ip_fw *chain, int policy, int mode)
{
struct ip_fw *f;
struct tcphdr *tcp=(struct tcphdr *)((__u32 *)ip+ip->ihl);
struct udphdr *udp=(struct udphdr *)((__u32 *)ip+ip->ihl);
struct icmphdr *icmp=(struct icmphdr *)((__u32 *)ip+ip->ihl);
__u32 src, dst;
__u16 src_port=0xFFFF, dst_port=0xFFFF, icmp_type=0xFF;
unsigned short f_prt=0, prt;
......@@ -328,6 +333,7 @@ int ip_fw_chk(struct iphdr *ip, struct net_device *rif, __u16 *redirport,
unsigned short offset;
int answer;
unsigned char tosand, tosxor;
int protocol;
/*
* If the chain is empty follow policy. The BSD one
......@@ -335,9 +341,6 @@ int ip_fw_chk(struct iphdr *ip, struct net_device *rif, __u16 *redirport,
* flushing and rebuilding the tables.
*/
src = ip->saddr;
dst = ip->daddr;
/*
* This way we handle fragmented packets.
* we ignore all fragments but the first one
......@@ -352,7 +355,8 @@ int ip_fw_chk(struct iphdr *ip, struct net_device *rif, __u16 *redirport,
* of system.
*/
offset = ntohs(ip->frag_off) & IP_OFFSET;
offset = ntohs((*pskb)->nh.iph->frag_off) & IP_OFFSET;
protocol = (*pskb)->nh.iph->protocol;
/*
* Don't allow a fragment of TCP 8 bytes in. Nobody
......@@ -361,19 +365,21 @@ int ip_fw_chk(struct iphdr *ip, struct net_device *rif, __u16 *redirport,
* checks.
*/
if (offset == 1 && ip->protocol == IPPROTO_TCP)
if (offset == 1 && protocol == IPPROTO_TCP)
return FW_BLOCK;
if (offset!=0 && !(mode & (IP_FW_MODE_ACCT_IN|IP_FW_MODE_ACCT_OUT)) &&
(ip->protocol == IPPROTO_TCP || ip->protocol == IPPROTO_UDP ||
ip->protocol == IPPROTO_ICMP))
(protocol == IPPROTO_TCP ||
protocol == IPPROTO_UDP ||
protocol == IPPROTO_ICMP))
return FW_ACCEPT;
/*
* Header fragment for TCP is too small to check the bits.
*/
if(ip->protocol==IPPROTO_TCP && (ip->ihl<<2)+16 > ntohs(ip->tot_len))
if (protocol == IPPROTO_TCP &&
((*pskb)->nh.iph->ihl<<2)+16 > ntohs((*pskb)->nh.iph->tot_len))
return FW_BLOCK;
/*
......@@ -382,11 +388,13 @@ int ip_fw_chk(struct iphdr *ip, struct net_device *rif, __u16 *redirport,
* But only too short for a packet with ports...
*/
else if((ntohs(ip->tot_len)<8+(ip->ihl<<2))&&(ip->protocol==IPPROTO_TCP || ip->protocol==IPPROTO_UDP))
else if ((ntohs((*pskb)->nh.iph->tot_len) <
8 + ((*pskb)->nh.iph->ihl << 2)) &&
(protocol == IPPROTO_TCP || protocol == IPPROTO_UDP))
return FW_BLOCK;
src = ip->saddr;
dst = ip->daddr;
src = (*pskb)->nh.iph->saddr;
dst = (*pskb)->nh.iph->daddr;
/*
* If we got interface from which packet came
......@@ -397,54 +405,76 @@ int ip_fw_chk(struct iphdr *ip, struct net_device *rif, __u16 *redirport,
*/
dprintf1("Packet ");
switch(ip->protocol)
{
switch (protocol) {
case IPPROTO_TCP:
dprintf1("TCP ");
/* ports stay 0xFFFF if it is not the first fragment */
if (!offset) {
src_port=ntohs(tcp->source);
dst_port=ntohs(tcp->dest);
if(!tcp->ack && !tcp->rst)
struct tcphdr tcph;
if (skb_copy_bits(*pskb,
(*pskb)->nh.iph->ihl * 4,
&tcph, sizeof(tcph)))
return FW_BLOCK;
src_port = ntohs(tcph.source);
dst_port = ntohs(tcph.dest);
if(!tcph.ack && !tcph.rst)
/* We do NOT have ACK, value TRUE */
notcpack=1;
if(!tcp->syn || !notcpack)
notcpack = 1;
if(!tcph.syn || !notcpack)
/* We do NOT have SYN, value TRUE */
notcpsyn=1;
notcpsyn = 1;
}
prt=IP_FW_F_TCP;
prt = IP_FW_F_TCP;
break;
case IPPROTO_UDP:
dprintf1("UDP ");
/* ports stay 0xFFFF if it is not the first fragment */
if (!offset) {
src_port=ntohs(udp->source);
dst_port=ntohs(udp->dest);
struct udphdr udph;
if (skb_copy_bits(*pskb,
(*pskb)->nh.iph->ihl * 4,
&udph, sizeof(udph)))
return FW_BLOCK;
src_port = ntohs(udph.source);
dst_port = ntohs(udph.dest);
}
prt=IP_FW_F_UDP;
prt = IP_FW_F_UDP;
break;
case IPPROTO_ICMP:
/* icmp_type stays 255 if it is not the first fragment */
if (!offset)
icmp_type=(__u16)(icmp->type);
dprintf2("ICMP:%d ",icmp_type);
prt=IP_FW_F_ICMP;
if (!offset) {
struct icmphdr icmph;
if (skb_copy_bits(*pskb,
(*pskb)->nh.iph->ihl * 4,
&icmph, sizeof(icmph)))
return FW_BLOCK;
icmp_type = (__u16) icmph.type;
}
dprintf2("ICMP:%d ", icmp_type);
prt = IP_FW_F_ICMP;
break;
default:
dprintf2("p=%d ",ip->protocol);
prt=IP_FW_F_ALL;
dprintf2("p=%d ", protocol);
prt = IP_FW_F_ALL;
break;
}
#ifdef DEBUG_IP_FIREWALL
dprint_ip(ip->saddr);
dprint_ip(src);
if (ip->protocol==IPPROTO_TCP || ip->protocol==IPPROTO_UDP)
if (protocol == IPPROTO_TCP || protocol == IPPROTO_UDP)
/* This will print 65535 when it is not the first fragment! */
dprintf2(":%d ", src_port);
dprint_ip(ip->daddr);
if (ip->protocol==IPPROTO_TCP || ip->protocol==IPPROTO_UDP)
dprint_ip(dst);
if (protocol == IPPROTO_TCP || protocol == IPPROTO_UDP)
/* This will print 65535 when it is not the first fragment! */
dprintf2(":%d ",dst_port);
dprintf2(":%d ", dst_port);
dprintf1("\n");
#endif
......@@ -453,8 +483,7 @@ int ip_fw_chk(struct iphdr *ip, struct net_device *rif, __u16 *redirport,
else
WRITE_LOCK(&ip_fw_lock);
for (f=chain;f;f=f->fw_next)
{
for (f = chain; f; f = f->fw_next) {
/*
* This is a bit simpler as we don't have to walk
* an interface chain as you do in BSD - same logic
......@@ -474,14 +503,14 @@ int ip_fw_chk(struct iphdr *ip, struct net_device *rif, __u16 *redirport,
*/
match = 0x00;
if ((src&f->fw_smsk.s_addr)==f->fw_src.s_addr
&& (dst&f->fw_dmsk.s_addr)==f->fw_dst.s_addr)
if ((src & f->fw_smsk.s_addr) == f->fw_src.s_addr &&
(dst & f->fw_dmsk.s_addr) == f->fw_dst.s_addr)
/* normal direction */
match |= 0x01;
if ((f->fw_flg & IP_FW_F_BIDIR) &&
(dst&f->fw_smsk.s_addr)==f->fw_src.s_addr
&& (src&f->fw_dmsk.s_addr)==f->fw_dst.s_addr)
(dst & f->fw_smsk.s_addr) == f->fw_src.s_addr &&
(src & f->fw_dmsk.s_addr) == f->fw_dst.s_addr)
/* reverse direction */
match |= 0x02;
......@@ -491,9 +520,8 @@ int ip_fw_chk(struct iphdr *ip, struct net_device *rif, __u16 *redirport,
/*
* Look for a VIA device match
*/
if(f->fw_viadev)
{
if(rif!=f->fw_viadev)
if (f->fw_viadev) {
if (rif != f->fw_viadev)
continue; /* Mismatch */
}
......@@ -560,14 +588,13 @@ int ip_fw_chk(struct iphdr *ip, struct net_device *rif, __u16 *redirport,
continue;
f_prt=f->fw_flg&IP_FW_F_KIND;
if (f_prt!=IP_FW_F_ALL)
{
if (f_prt != IP_FW_F_ALL) {
/*
* Specific firewall - packet's protocol
* must match firewall's.
*/
if(prt!=f_prt)
if (prt != f_prt)
continue;
if((prt==IP_FW_F_ICMP &&
......@@ -592,14 +619,14 @@ int ip_fw_chk(struct iphdr *ip, struct net_device *rif, __u16 *redirport,
{
char buf[16];
print_packet(ip, src_port, dst_port, icmp_type,
print_packet(pskb, src_port, dst_port, icmp_type,
chain_name(chain, mode),
rule_name(f, mode, buf),
rif ? rif->name : "-");
}
#endif
if (mode != IP_FW_MODE_CHK) {
f->fw_bcnt+=ntohs(ip->tot_len);
f->fw_bcnt += ntohs((*pskb)->nh.iph->tot_len);
f->fw_pcnt++;
}
if (!(mode & (IP_FW_MODE_ACCT_IN|IP_FW_MODE_ACCT_OUT)))
......@@ -614,23 +641,30 @@ int ip_fw_chk(struct iphdr *ip, struct net_device *rif, __u16 *redirport,
* of firewall.
*/
if (f!=NULL) {
policy=f->fw_flg;
tosand=f->fw_tosand;
tosxor=f->fw_tosxor;
if (f != NULL) {
policy = f->fw_flg;
tosand = f->fw_tosand;
tosxor = f->fw_tosxor;
} else {
tosand=0xFF;
tosxor=0x00;
tosand = 0xFF;
tosxor = 0x00;
}
if (policy&IP_FW_F_ACCEPT) {
if (policy & IP_FW_F_ACCEPT) {
/* Adjust priority and recompute checksum */
__u8 old_tos = ip->tos;
ip->tos = (old_tos & tosand) ^ tosxor;
if (ip->tos != old_tos)
ip_send_check(ip);
__u8 tos = (*pskb)->nh.iph->tos;
if (((tos & tosand) ^ tosxor) != tos) {
if (!skb_ip_make_writable(pskb,
offsetof(struct iphdr, tos)+1))
goto drop_it;
(*pskb)->nh.iph->tos = (tos & tosand) ^ tosxor;
ip_send_check((*pskb)->nh.iph);
}
#ifdef CONFIG_IP_TRANSPARENT_PROXY
if (policy&IP_FW_F_REDIR) {
if (policy & IP_FW_F_REDIR) {
if (redirport)
if ((*redirport = htons(f->fw_pts[f->fw_nsp+f->fw_ndp])) == 0) {
/* Wildcard redirection.
......@@ -643,30 +677,36 @@ int ip_fw_chk(struct iphdr *ip, struct net_device *rif, __u16 *redirport,
} else
#endif
#ifdef CONFIG_IP_MASQUERADE
if (policy&IP_FW_F_MASQ)
if (policy & IP_FW_F_MASQ)
answer = FW_MASQUERADE;
else
#endif
answer = FW_ACCEPT;
} else if(policy&IP_FW_F_ICMPRPL)
} else if (policy & IP_FW_F_ICMPRPL)
answer = FW_REJECT;
else
else {
drop_it:
answer = FW_BLOCK;
}
#ifdef CONFIG_IP_FIREWALL_NETLINK
if((policy&IP_FW_F_PRN) && (answer == FW_REJECT || answer == FW_BLOCK))
if ((policy & IP_FW_F_PRN) && (answer == FW_REJECT || answer == FW_BLOCK))
{
struct sk_buff *skb=alloc_skb(128, (mode == IP_FW_MODE_CHK) ?
GFP_KERNEL : GFP_ATOMIC);
if(skb)
{
struct sk_buff *skb = alloc_skb(128,
(mode == IP_FW_MODE_CHK) ?
GFP_KERNEL : GFP_ATOMIC);
if (skb) {
int len = min_t(unsigned int,
128, ntohs(ip->tot_len));
skb_put(skb,len);
memcpy(skb->data,ip,len);
if(netlink_post(NETLINK_FIREWALL, skb))
128,
ntohs((*pskb)->nh.iph->tot_len));
skb_put(skb, len);
skb_copy_bits(*pskb,
((char *)(*pskb)->nh.iph -
(char *)(*pskb)->data),
skb->data, len);
if (netlink_post(NETLINK_FIREWALL, skb))
kfree_skb(skb);
}
}
......@@ -706,6 +746,9 @@ static void free_fw_chain(struct ip_fw *volatile* chainptr)
struct ip_fw *ftmp;
ftmp = *chainptr;
*chainptr = ftmp->fw_next;
if (ftmp->fw_viadev
&& ftmp->fw_viadev != (struct net_device *)-1)
dev_put(ftmp->fw_viadev);
kfree(ftmp);
/* We will block in cleanup's unregister sockopt if unloaded,
so this is safe. */
......@@ -856,6 +899,9 @@ static int del_from_chain(struct ip_fw *volatile*chainptr, struct ip_fw *frwl)
if(matches)
{
was_found=1;
if (ftmp->fw_viadev
&& ftmp->fw_viadev != (struct net_device *)-1)
dev_put(ftmp->fw_viadev);
if (ltmp)
{
ltmp->fw_next=ftmp->fw_next;
......@@ -1035,9 +1081,15 @@ int ip_fw_ctl(int stage, void *m, int len)
if ( cmd == IP_FW_CHECK )
{
struct sk_buff *tmp_skb;
struct net_device *viadev;
struct ip_fwpkt *ipfwp;
struct iphdr *ip;
int hdrlen, ret;
hdrlen = sizeof(struct ip_fwpkt) -
sizeof(struct in_addr) -
IFNAMSIZ;
if ( len != sizeof(struct ip_fwpkt) )
{
......@@ -1061,12 +1113,34 @@ int ip_fw_ctl(int stage, void *m, int len)
printk("ip_fw_ctl: ip->ihl=%d, want %d\n",ip->ihl,
sizeof(struct iphdr)/sizeof(int));
#endif
dev_put(viadev);
return(EINVAL);
}
switch (ip_fw_chk(ip, viadev, NULL, *chains[fwtype],
*policies[fwtype], IP_FW_MODE_CHK))
{
/* Fix this one up by hand, who knows how many
* tools will break if we start to barf on this.
*/
if (ntohs(ip->tot_len) > hdrlen)
ip->tot_len = htons(hdrlen);
if ((tmp_skb = alloc_skb(hdrlen, GFP_ATOMIC)) == NULL) {
#ifdef DEBUG_IP_FIREWALL
printk("ip_fw_ctl: tmp_skb alloc failure\n");
#endif
dev_put(viadev);
return(EFAULT);
}
skb_reserve(tmp_skb, hdrlen);
skb_push(tmp_skb, hdrlen);
memcpy(tmp_skb->data, ip, hdrlen);
ret = ip_fw_chk(&tmp_skb, viadev, NULL, *chains[fwtype],
*policies[fwtype], IP_FW_MODE_CHK);
kfree_skb(tmp_skb);
dev_put(viadev);
switch (ret) {
case FW_ACCEPT:
return(0);
case FW_REDIRECT:
......@@ -1242,55 +1316,50 @@ static int ip_fw_fwd_procinfo(char *buffer, char **start, off_t offset,
*/
int ipfw_input_check(struct firewall_ops *this, int pf,
struct net_device *dev, void *phdr, void *arg,
struct net_device *dev, void *arg,
struct sk_buff **pskb)
{
return ip_fw_chk(phdr, dev, arg, ip_fw_in_chain, ip_fw_in_policy,
return ip_fw_chk(pskb, dev, arg, ip_fw_in_chain, ip_fw_in_policy,
IP_FW_MODE_FW);
}
int ipfw_output_check(struct firewall_ops *this, int pf,
struct net_device *dev, void *phdr, void *arg,
struct net_device *dev, void *arg,
struct sk_buff **pskb)
{
return ip_fw_chk(phdr, dev, arg, ip_fw_out_chain, ip_fw_out_policy,
return ip_fw_chk(pskb, dev, arg, ip_fw_out_chain, ip_fw_out_policy,
IP_FW_MODE_FW);
}
int ipfw_forward_check(struct firewall_ops *this, int pf,
struct net_device *dev, void *phdr, void *arg,
struct net_device *dev, void *arg,
struct sk_buff **pskb)
{
return ip_fw_chk(phdr, dev, arg, ip_fw_fwd_chain, ip_fw_fwd_policy,
return ip_fw_chk(pskb, dev, arg, ip_fw_fwd_chain, ip_fw_fwd_policy,
IP_FW_MODE_FW);
}
#ifdef CONFIG_IP_ACCT
int ipfw_acct_in(struct firewall_ops *this, int pf, struct net_device *dev,
void *phdr, void *arg, struct sk_buff **pskb)
void *arg, struct sk_buff **pskb)
{
return ip_fw_chk(phdr,dev,NULL,ip_acct_chain,0,IP_FW_MODE_ACCT_IN);
return ip_fw_chk(pskb,dev,NULL,ip_acct_chain,0,IP_FW_MODE_ACCT_IN);
}
int ipfw_acct_out(struct firewall_ops *this, int pf, struct net_device *dev,
void *phdr, void *arg, struct sk_buff **pskb)
void *arg, struct sk_buff **pskb)
{
return ip_fw_chk(phdr,dev,NULL,ip_acct_chain,0,IP_FW_MODE_ACCT_OUT);
return ip_fw_chk(pskb,dev,NULL,ip_acct_chain,0,IP_FW_MODE_ACCT_OUT);
}
#endif
struct firewall_ops ipfw_ops=
{
NULL,
ipfw_forward_check,
ipfw_input_check,
ipfw_output_check,
struct firewall_ops ipfw_ops = {
.fw_forward = ipfw_forward_check,
.fw_input = ipfw_input_check,
.fw_output = ipfw_output_check,
#ifdef CONFIG_IP_ACCT
ipfw_acct_in,
ipfw_acct_out
#else
NULL,
NULL
.fw_acct_in = ipfw_acct_in,
.fw_acct_out = ipfw_acct_out,
#endif
};
......@@ -1311,23 +1380,29 @@ int ipfw_device_event(struct notifier_block *this, unsigned long event, void *pt
for (chn = 0; chn < IP_FW_CHAINS; chn++)
for (fw = *chains[chn]; fw; fw = fw->fw_next)
if ((fw->fw_vianame)[0] && !strncmp(devname,
fw->fw_vianame, IFNAMSIZ))
fw->fw_vianame, IFNAMSIZ)) {
dev_hold(dev);
fw->fw_viadev = dev;
}
} else if (event == NETDEV_DOWN) {
for (chn = 0; chn < IP_FW_CHAINS; chn++)
for (fw = *chains[chn]; fw; fw = fw->fw_next)
/* we could compare just the pointers ... */
if ((fw->fw_vianame)[0] && !strncmp(devname,
fw->fw_vianame, IFNAMSIZ))
fw->fw_vianame, IFNAMSIZ)){
if (fw->fw_viadev
&& fw->fw_viadev != (struct net_device *)-1)
dev_put(fw->fw_viadev);
fw->fw_viadev = (struct net_device*)-1;
}
}
WRITE_UNLOCK(&ip_fw_lock);
return NOTIFY_DONE;
}
static struct notifier_block ipfw_dev_notifier={
.notifier_call = ipfw_device_event,
static struct notifier_block ipfw_dev_notifier = {
.notifier_call = ipfw_device_event,
};
#endif
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment