Skip to content

Commit

Permalink
[BRIDGE]: netfilter handle RCU during removal
Browse files Browse the repository at this point in the history
Bridge netfilter code needs to handle the case where device is
removed from bridge while packet in process. In these cases the
bridge_parent can become null while processing.

This should fix: http://bugzilla.kernel.org/show_bug.cgi?id=5803

Signed-off-by: Stephen Hemminger <[email protected]>
Signed-off-by: David S. Miller <[email protected]>
  • Loading branch information
Stephen Hemminger authored and davem330 committed Feb 10, 2006
1 parent b3f1be4 commit 5dce971
Showing 1 changed file with 38 additions and 15 deletions.
53 changes: 38 additions & 15 deletions net/bridge/br_netfilter.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,6 @@
#define store_orig_dstaddr(skb) (skb_origaddr(skb) = (skb)->nh.iph->daddr)
#define dnat_took_place(skb) (skb_origaddr(skb) != (skb)->nh.iph->daddr)

#define has_bridge_parent(device) ((device)->br_port != NULL)
#define bridge_parent(device) ((device)->br_port->br->dev)

#ifdef CONFIG_SYSCTL
static struct ctl_table_header *brnf_sysctl_header;
static int brnf_call_iptables = 1;
Expand Down Expand Up @@ -98,6 +95,12 @@ static struct rtable __fake_rtable = {
.rt_flags = 0,
};

static inline struct net_device *bridge_parent(const struct net_device *dev)
{
struct net_bridge_port *port = rcu_dereference(dev->br_port);

return port ? port->br->dev : NULL;
}

/* PF_BRIDGE/PRE_ROUTING *********************************************/
/* Undo the changes made for ip6tables PREROUTING and continue the
Expand Down Expand Up @@ -189,11 +192,15 @@ static int br_nf_pre_routing_finish_bridge(struct sk_buff *skb)
skb->nf_bridge->mask ^= BRNF_NF_BRIDGE_PREROUTING;

skb->dev = bridge_parent(skb->dev);
if (skb->protocol == __constant_htons(ETH_P_8021Q)) {
skb_pull(skb, VLAN_HLEN);
skb->nh.raw += VLAN_HLEN;
if (!skb->dev)
kfree_skb(skb);
else {
if (skb->protocol == __constant_htons(ETH_P_8021Q)) {
skb_pull(skb, VLAN_HLEN);
skb->nh.raw += VLAN_HLEN;
}
skb->dst->output(skb);
}
skb->dst->output(skb);
return 0;
}

Expand Down Expand Up @@ -270,7 +277,7 @@ static int br_nf_pre_routing_finish(struct sk_buff *skb)
}

/* Some common code for IPv4/IPv6 */
static void setup_pre_routing(struct sk_buff *skb)
static struct net_device *setup_pre_routing(struct sk_buff *skb)
{
struct nf_bridge_info *nf_bridge = skb->nf_bridge;

Expand All @@ -282,6 +289,8 @@ static void setup_pre_routing(struct sk_buff *skb)
nf_bridge->mask |= BRNF_NF_BRIDGE_PREROUTING;
nf_bridge->physindev = skb->dev;
skb->dev = bridge_parent(skb->dev);

return skb->dev;
}

/* We only check the length. A bridge shouldn't do any hop-by-hop stuff anyway */
Expand Down Expand Up @@ -376,7 +385,8 @@ static unsigned int br_nf_pre_routing_ipv6(unsigned int hook,
nf_bridge_put(skb->nf_bridge);
if ((nf_bridge = nf_bridge_alloc(skb)) == NULL)
return NF_DROP;
setup_pre_routing(skb);
if (!setup_pre_routing(skb))
return NF_DROP;

NF_HOOK(PF_INET6, NF_IP6_PRE_ROUTING, skb, skb->dev, NULL,
br_nf_pre_routing_finish_ipv6);
Expand Down Expand Up @@ -465,7 +475,8 @@ static unsigned int br_nf_pre_routing(unsigned int hook, struct sk_buff **pskb,
nf_bridge_put(skb->nf_bridge);
if ((nf_bridge = nf_bridge_alloc(skb)) == NULL)
return NF_DROP;
setup_pre_routing(skb);
if (!setup_pre_routing(skb))
return NF_DROP;
store_orig_dstaddr(skb);

NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, skb->dev, NULL,
Expand Down Expand Up @@ -539,11 +550,16 @@ static unsigned int br_nf_forward_ip(unsigned int hook, struct sk_buff **pskb,
struct sk_buff *skb = *pskb;
struct nf_bridge_info *nf_bridge;
struct vlan_ethhdr *hdr = vlan_eth_hdr(skb);
struct net_device *parent;
int pf;

if (!skb->nf_bridge)
return NF_ACCEPT;

parent = bridge_parent(out);
if (!parent)
return NF_DROP;

if (skb->protocol == __constant_htons(ETH_P_IP) || IS_VLAN_IP)
pf = PF_INET;
else
Expand All @@ -564,8 +580,8 @@ static unsigned int br_nf_forward_ip(unsigned int hook, struct sk_buff **pskb,
nf_bridge->mask |= BRNF_BRIDGED;
nf_bridge->physoutdev = skb->dev;

NF_HOOK(pf, NF_IP_FORWARD, skb, bridge_parent(in),
bridge_parent(out), br_nf_forward_finish);
NF_HOOK(pf, NF_IP_FORWARD, skb, bridge_parent(in), parent,
br_nf_forward_finish);

return NF_STOLEN;
}
Expand Down Expand Up @@ -688,6 +704,8 @@ static unsigned int br_nf_local_out(unsigned int hook, struct sk_buff **pskb,
goto out;
}
realoutdev = bridge_parent(skb->dev);
if (!realoutdev)
return NF_DROP;

#if defined(CONFIG_VLAN_8021Q) || defined(CONFIG_VLAN_8021Q_MODULE)
/* iptables should match -o br0.x */
Expand All @@ -701,9 +719,11 @@ static unsigned int br_nf_local_out(unsigned int hook, struct sk_buff **pskb,
/* IP forwarded traffic has a physindev, locally
* generated traffic hasn't. */
if (realindev != NULL) {
if (!(nf_bridge->mask & BRNF_DONT_TAKE_PARENT) &&
has_bridge_parent(realindev))
realindev = bridge_parent(realindev);
if (!(nf_bridge->mask & BRNF_DONT_TAKE_PARENT) ) {
struct net_device *parent = bridge_parent(realindev);
if (parent)
realindev = parent;
}

NF_HOOK_THRESH(pf, NF_IP_FORWARD, skb, realindev,
realoutdev, br_nf_local_out_finish,
Expand Down Expand Up @@ -743,6 +763,9 @@ static unsigned int br_nf_post_routing(unsigned int hook, struct sk_buff **pskb,
if (!nf_bridge)
return NF_ACCEPT;

if (!realoutdev)
return NF_DROP;

if (skb->protocol == __constant_htons(ETH_P_IP) || IS_VLAN_IP)
pf = PF_INET;
else
Expand Down

0 comments on commit 5dce971

Please sign in to comment.