/* * Bridge isolate bridge clients through netfilter * * vim:set noexpandtab shiftwidth=8 softtabstop=8: * * Copyright (c) 2015 AVM GmbH * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer, * without modification. * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * Alternatively, this software may be distributed and/or modified under the * terms of the GNU General Public License as published by the Free Software * Foundation. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 #include #include #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,38) #include #endif #include #include #include #include #include #include #include #include #include "br_private.h" /* 64 should be enough to completely avoid hash collisions in practice, * so lookup should be really fast */ #define HASH_SIZE 64 #define HASH_MASK (HASH_SIZE-1) struct isol_entry { int ifindex; struct hlist_node list; struct rcu_head rcu; }; /* A global list is OK (instead of per-bridge), since ports can only be in one * bridge at a time, and forwarding between two bridges is not possible. So * the hook won't be called cross-bridge. Saves memory and keeps the code simpler. */ static struct hlist_head isolated[HASH_SIZE]; static struct isol_entry *find_if(int ifindex, struct hlist_head *map) { struct isol_entry *pos; hlist_for_each_entry_rcu(pos, &map[ifindex & HASH_MASK], list) { if (pos->ifindex == ifindex) return pos; } return NULL; } static bool contains_if(int ifindex, struct hlist_head *map) { if (hlist_empty(&map[ifindex & HASH_MASK])) return false; return find_if(ifindex, map) != NULL; } static int add_if(int ifindex, struct hlist_head *map) { struct isol_entry *e; if (contains_if(ifindex, map)) return 0; e = kzalloc(sizeof(struct isol_entry), GFP_ATOMIC); if (!e) return -ENOMEM; e->ifindex = ifindex; hlist_add_head_rcu(&e->list, &map[ifindex & HASH_MASK]); return 0; } static int del_if(int ifindex, struct hlist_head *map) { struct isol_entry *pos = find_if(ifindex, map); if (pos) { hlist_del_rcu(&pos->list); kfree_rcu(pos, rcu); } return 0; } unsigned int br_isol_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { struct net_bridge_port *in = br_port_get_rcu(state->in); struct net_bridge_port *out = br_port_get_rcu(state->out); if (!in || !out) return NF_DROP; /* Block traffic to/from isolated ports. * Only traffic between two isolated ports is blocked. Traffic flowing from/to * non-isolated ports flows normally. See JZ-5555, comment #53. */ if (contains_if(in->dev->ifindex, isolated) && contains_if(out->dev->ifindex, isolated)) return NF_DROP; return NF_ACCEPT; } /* Call with p->br->lock held */ ssize_t br_isol_show(struct net_bridge_port *p, char *buf) { return sprintf(buf, "%d\n", contains_if(p->dev->ifindex, isolated)); } /* Call with p->br->lock held */ int br_isol_store(struct net_bridge_port *p, unsigned long v) { int err = 0; int ifindex = p->dev->ifindex; if (v && !contains_if(ifindex, isolated)) { err = add_if(ifindex, isolated); /* flush out avm_pa, otherwise the hook will see no packets */ avm_pa_flush_sessions_for_pid(AVM_PA_DEVINFO(p->dev)->pid_handle); } else if (!v && contains_if(ifindex, isolated)) { del_if(ifindex, isolated); } return err; } /* Call with p->br->lock held */ void br_isol_remove_port(struct net_bridge_port *p) { /* this ensures that no reference to the ifindex is left in the hash tables */ br_isol_store(p, 0); }