auto_bridge-1.00.1.tar.gz from sdk-comcerto-openwrt-c2k_3.1-rc4
diff --git a/auto_bridge/Makefile b/auto_bridge/Makefile
new file mode 100644
index 0000000..89dce01
--- /dev/null
+++ b/auto_bridge/Makefile
@@ -0,0 +1,6 @@
+TOP_DIR := $(shell pwd)
+
+obj-m:=auto_bridge.o
+
+all:
+	$(MAKE) -C $(KERNEL_SOURCE) ARCH=arm SUBDIRS="$(TOP_DIR)" modules
diff --git a/auto_bridge/auto_bridge.c b/auto_bridge/auto_bridge.c
new file mode 100644
index 0000000..908cbb3
--- /dev/null
+++ b/auto_bridge/auto_bridge.c
@@ -0,0 +1,1436 @@
+/*
+ *
+ *  Copyright (C) 2007 Mindspeed Technologies, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+ 
+#include <linux/version.h>
+#include <linux/socket.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/netlink.h>
+#include <linux/skbuff.h>
+#include <net/netlink.h>
+#include <linux/timer.h>
+#include <linux/time.h>
+#include <linux/if_ether.h>
+#include <linux/jhash.h>
+#include <linux/list.h>
+#include <linux/netfilter_bridge.h>
+#include <net/net_namespace.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter_bridge/ebtables.h>
+#include <linux/proc_fs.h>
+#include <linux/ip.h>
+#include <net/ip.h>
+#include <net/ipv6.h>
+#include <linux/if_bridge.h>
+#include <linux/workqueue.h>
+
+
+
+#include "auto_bridge_private.h"
+#include "include/auto_bridge.h"
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Mindspeed Technologies");
+MODULE_DESCRIPTION("Automatic Bridging Module (ABM)");
+
+static char __initdata auto_bridge_version[] = "0.01";
+
+#define SECS * HZ
+#define MINS * 60 SECS
+#define HOURS * 60 MINS
+#define DAYS * 24 HOURS
+
+struct list_head			l2flow_table[L2FLOW_HASH_TABLE_SIZE];
+struct list_head			l2flow_table_by_src_mac[L2FLOW_HASH_BY_MAC_TABLE_SIZE];
+struct list_head			l2flow_table_by_dst_mac[L2FLOW_HASH_BY_MAC_TABLE_SIZE];
+
+struct list_head			l2flow_list_wait_for_ack;
+struct list_head			l2flow_list_msg_to_send;
+
+static struct kmem_cache		*l2flow_cache /*__read_mostly*/;
+static struct sock			*abm_nl = NULL;
+static char			abm_l3_filtering = 0;
+static unsigned int			abm_max_entries = ABM_DEFAULT_MAX_ENTRIES;
+static unsigned int			abm_nb_entries =	0;
+struct workqueue_struct		*kabm_wq;
+static int				abm_retransmit_time = 2 SECS;
+
+DEFINE_SPINLOCK(abm_lock);
+static DECLARE_WORK(abm_work_send_msg, abm_do_work_send_msg);
+static DECLARE_DELAYED_WORK(abm_work_retransmit, abm_do_work_retransmit);
+
+
+static unsigned int l2flow_timeouts[L2FLOW_STATE_MAX] /*__read_mostly*/ = {
+	[L2FLOW_STATE_SEEN]			= 10 SECS,
+	[L2FLOW_STATE_CONFIRMED]		= 2 MINS, 
+	[L2FLOW_STATE_LINUX]			= 10 SECS,
+	[L2FLOW_STATE_DYING]			= 2 MINS, // This state is here to give some time for retransmission
+};
+
+static const char *const l2flow_states_string[L2FLOW_STATE_MAX] __read_mostly = {
+	[L2FLOW_STATE_SEEN]			= "SEEN",
+	[L2FLOW_STATE_CONFIRMED]		= "CONFIRMED", //Should not timeout here
+	[L2FLOW_STATE_LINUX]			= "LINUX",	
+	[L2FLOW_STATE_FF]			= "FF",		
+	[L2FLOW_STATE_DYING]			= "DYING", 	
+
+};
+
+/***************************************************************************
+*
+* abm_do_work_send_msg
+* Used to send delayed msg
+*
+****************************************************************************/
+static void abm_do_work_send_msg(struct work_struct *work)
+{
+	struct list_head *entry, *tmp;
+	struct l2flowTable *table_entry;
+	char action = 0;
+
+	if (!netlink_has_listeners(abm_nl, L2FLOW_NL_GRP)){
+		return;
+	}
+	spin_lock_bh(&abm_lock);
+	//TODO : Need to limit the number of messages to sent while holding the lock.
+	list_for_each_safe(entry, tmp, &l2flow_list_msg_to_send){
+		table_entry = container_of(entry, struct l2flowTable, list_msg_to_send);
+		if((table_entry->state == L2FLOW_STATE_SEEN) 
+		|| (table_entry->state == L2FLOW_STATE_CONFIRMED)){
+			action = L2FLOW_ENTRY_NEW;		
+		}
+		else if((table_entry->state == L2FLOW_STATE_LINUX) 
+		|| (table_entry->state == L2FLOW_STATE_FF)){
+			action = L2FLOW_ENTRY_UPDATE;
+		}
+		else if (table_entry->state == L2FLOW_STATE_DYING){
+			action = L2FLOW_ENTRY_DEL;
+		}
+		if(abm_nl_send_l2flow_msg(abm_nl, action, 0, table_entry) != -ENOTCONN){
+			table_entry->flags &= ~L2FLOW_FL_PENDING_MSG;
+			table_entry->flags &= ~L2FLOW_FL_NEEDS_UPDATE;
+			list_del(&table_entry->list_msg_to_send);
+			table_entry->time_sent = jiffies;
+			if(!(table_entry->flags & L2FLOW_FL_WAIT_ACK)){
+				list_add(&table_entry->list_wait_for_ack, &l2flow_list_wait_for_ack);
+				table_entry->flags |= L2FLOW_FL_WAIT_ACK;
+			}
+		}
+	}
+	spin_unlock_bh(&abm_lock);
+}
+
+/***************************************************************************
+*
+* abm_do_work_retransmit
+* Periodic work used to check and re-transmit messages
+*
+****************************************************************************/
+static void abm_do_work_retransmit(struct work_struct *work)
+{
+	struct list_head *entry;
+	struct l2flowTable *table_entry;
+	char action = 0;
+
+	spin_lock_bh(&abm_lock);
+	
+	if(list_empty(&l2flow_list_wait_for_ack))
+		goto resched;
+			
+	if (!netlink_has_listeners(abm_nl, L2FLOW_NL_GRP))
+		goto resched;
+	
+
+	list_for_each(entry, &l2flow_list_wait_for_ack){
+		table_entry = container_of(entry, struct l2flowTable, list_wait_for_ack);
+		if(time_is_before_jiffies(table_entry->time_sent + abm_retransmit_time)){
+			if((table_entry->state == L2FLOW_STATE_SEEN) 
+			|| (table_entry->state == L2FLOW_STATE_CONFIRMED)){
+				action = L2FLOW_ENTRY_NEW;		
+			}
+			else if((table_entry->state == L2FLOW_STATE_LINUX) 
+			|| (table_entry->state == L2FLOW_STATE_FF)){
+				action = L2FLOW_ENTRY_UPDATE;
+			}
+			else if (table_entry->state == L2FLOW_STATE_DYING){
+				action = L2FLOW_ENTRY_DEL;
+			}
+			if (!abm_nl_send_l2flow_msg(abm_nl, action, 0, table_entry)){
+				/* Success : Update time and continue to next entry */
+				table_entry->time_sent = jiffies;
+			}
+			else /* Otherwise don't spend more time here and wait some more time */
+				goto resched;
+		}
+	}
+resched:
+	spin_unlock_bh(&abm_lock);
+	queue_delayed_work(kabm_wq, &abm_work_retransmit, abm_retransmit_time);
+}
+/***************************************************************************
+*
+* abm_br_event
+* The bridge notifier callback.
+* Can be called from mostly any context. Events to user-space are not sent here.
+*
+****************************************************************************/
+int abm_br_event(struct notifier_block *unused, unsigned long event, void *ptr){
+	int work_to_do = 0;
+
+	if (event == BREVENT_PORT_DOWN) {
+		struct net_device * dev = (struct net_device *) ptr;
+		int i;
+		struct list_head *entry;
+		struct l2flowTable *table_entry;
+
+		for (i = 0; i < L2FLOW_HASH_TABLE_SIZE; i++) {
+			spin_lock_bh(&abm_lock);
+			list_for_each(entry,  &l2flow_table[i]) {
+				table_entry = container_of(entry, struct l2flowTable, list);
+				if ((table_entry->state != L2FLOW_STATE_DYING)
+				&& ((table_entry->idev_ifi == dev->ifindex) || (table_entry->odev_ifi == dev->ifindex))){
+					unsigned int  no_timer = (table_entry->state == L2FLOW_STATE_FF);	
+					table_entry->state = L2FLOW_STATE_DYING;
+
+				if (!(table_entry->flags & L2FLOW_FL_PENDING_MSG)) {
+						table_entry->flags |= L2FLOW_FL_PENDING_MSG;
+						list_add(&table_entry->list_msg_to_send, &l2flow_list_msg_to_send);
+						work_to_do = 1;
+				}
+				if (del_timer(&table_entry->timeout) || no_timer)
+					__abm_go_dying(table_entry);
+				}
+			}
+			spin_unlock_bh(&abm_lock);
+		}
+	}
+	else if (event == BREVENT_FDB_UPDATE){
+		struct brevent_fdb_update * fdb_update;
+		int key;
+		struct list_head *entry;
+		struct l2flowTable *table_entry;
+
+		fdb_update = (struct brevent_fdb_update *) ptr;
+		key = abm_l2flow_hash_mac(fdb_update->mac_addr);
+
+		spin_lock_bh(&abm_lock);
+		list_for_each(entry,  &l2flow_table_by_dst_mac[key]){
+			table_entry = container_of(entry, struct l2flowTable, list_by_dst_mac);
+			/* Send the event in every other state different than DYING */
+			/* There is no issue sending more events than needed */
+			if (!compare_ether_addr(fdb_update->mac_addr, table_entry->l2flow.daddr)
+			&& (fdb_update->dev->ifindex != table_entry->odev_ifi)
+			&& (table_entry->state != L2FLOW_STATE_DYING)) {
+				table_entry->odev_ifi = fdb_update->dev->ifindex;
+
+				if (!(table_entry->flags & L2FLOW_FL_PENDING_MSG)) {
+					table_entry->flags |= L2FLOW_FL_PENDING_MSG;
+					list_add(&table_entry->list_msg_to_send, &l2flow_list_msg_to_send);
+					work_to_do = 1;
+				}
+			}
+		}
+		spin_unlock_bh(&abm_lock);
+	}
+	if(work_to_do)
+		queue_work(kabm_wq, &abm_work_send_msg);
+
+	return NOTIFY_DONE;
+}
+struct notifier_block abm_br_notifier = {
+	.notifier_call = abm_br_event
+};
+
+
+/***************************************************************************
+*
+* abm_fdb_can_expire
+* Callback registered in br_fb. 
+* Return 0 if an entry is fast-forwarded, 1 otherwise
+*
+****************************************************************************/
+int abm_fdb_can_expire(unsigned char *mac_addr, struct net_device *dev)
+{
+	int key;
+	struct l2flowTable *table_entry;
+	struct list_head *entry;
+
+	key = abm_l2flow_hash_mac(mac_addr);
+
+	spin_lock(&abm_lock);
+	list_for_each(entry,  &l2flow_table_by_src_mac[key]){
+		table_entry = container_of(entry, struct l2flowTable, list_by_src_mac);
+		if(!compare_ether_addr(mac_addr, table_entry->l2flow.saddr)
+		&& (dev->ifindex == table_entry->idev_ifi)){
+
+			if(table_entry->state == L2FLOW_STATE_FF){
+				spin_unlock(&abm_lock);
+				return 0;
+			}
+		}
+	}
+	spin_unlock(&abm_lock);
+	return 1;
+}
+static inline size_t abm_l2flow_msg_size(void)
+{
+	return NLMSG_ALIGN(sizeof(struct l2flow_msg))
+		+ nla_total_size(sizeof(u32))		/* L2FLOWA_VLAN_TAG */
+		+ nla_total_size(sizeof(u32))		/* L2FLOWA_PPP_S_ID */
+		+ nla_total_size(sizeof(u32))		/* L2FLOWA_IIF_IDX */
+		+ nla_total_size(sizeof(u32))		/* L2FLOWA_OIF_DIX */
+		+ nla_total_size(4 * sizeof(u32))		/* L2FLOWA_IP_SRC */
+		+ nla_total_size(4 * sizeof(u32))		/* L2FLOWA_IP_DST */
+		+ nla_total_size(sizeof(u8))		/* L2FLOWA_IP_PROTO */
+		+ nla_total_size(sizeof(u16))		/* L2FLOWA_SPORT */
+		+ nla_total_size(sizeof(u16))		/* L2FLOWA_DPORT */
+		+ nla_total_size(sizeof(u16))		/* L2FLOWA_MARK */
+		;
+}
+
+/***************************************************************************
+*
+* abm_nl_send_rst_msg
+* Send L2FLOW_MSG_RESET msg types to user-space
+* 
+****************************************************************************/
+static int abm_nl_send_rst_msg(struct sock *s)
+{
+	struct sk_buff *skb;
+	struct nlmsghdr *nlh;
+	int err = 0;
+	
+	skb = nlmsg_new(0, GFP_KERNEL);
+	if(skb == NULL){
+		err = -ENOMEM;
+		goto err;
+	}
+	
+	nlh = nlmsg_put(skb, 0, 0, L2FLOW_MSG_RESET, 0, 0);
+	if(nlh == NULL){
+		err = -ENOMEM;
+		goto err2;
+	}
+	
+	nlmsg_end(skb, nlh);
+
+	if (netlink_has_listeners(s, L2FLOW_NL_GRP)){
+		NETLINK_CB(skb).pid = 0;	/* from kernel */
+		NETLINK_CB(skb).dst_group = L2FLOW_NL_GRP;
+
+		return netlink_broadcast(s, skb, 0, L2FLOW_NL_GRP, GFP_KERNEL);
+
+	}
+	else{
+		err = -ENOTCONN;
+		goto err2;
+	}
+
+err2:
+	kfree_skb(skb);
+err:
+	return err;
+}
+
+/***************************************************************************
+*
+* abm_nl_send_l2flow_msg
+* Send L2FLOW_MSG_ENTRY msg types to user-space
+* 
+****************************************************************************/
+static int abm_nl_send_l2flow_msg(struct sock *s, char action, int flags, struct l2flowTable *table_entry)
+{
+	struct sk_buff *skb;
+	struct nlmsghdr *nlh;
+	struct l2flow_msg *l2flow_msg;
+	int err = 0;
+
+	skb = nlmsg_new(abm_l2flow_msg_size(), GFP_ATOMIC);
+	if(skb == NULL){
+		err = -ENOMEM;
+		goto err;
+	}
+	
+	nlh = nlmsg_put(skb, 0, 0, L2FLOW_MSG_ENTRY, sizeof(*l2flow_msg), 0);
+	if(nlh == NULL){
+		err = -ENOMEM;
+		goto err2;
+	}
+
+	l2flow_msg = nlmsg_data(nlh);
+	l2flow_msg->action = action;
+	l2flow_msg->flags = flags;
+	memcpy(l2flow_msg->saddr, table_entry->l2flow.saddr, 6);
+	memcpy(l2flow_msg->daddr, table_entry->l2flow.daddr, 6);
+	l2flow_msg->ethertype = table_entry->l2flow.ethertype;
+
+	if(action == L2FLOW_ENTRY_NEW || action == L2FLOW_ENTRY_UPDATE){
+		NLA_PUT_U32(skb, L2FLOWA_IIF_IDX, table_entry->idev_ifi);
+		NLA_PUT_U32(skb, L2FLOWA_OIF_IDX, table_entry->odev_ifi);
+		NLA_PUT_U16(skb, L2FLOWA_MARK, table_entry->packet_mark);
+	}
+	if(table_entry->l2flow.ethertype == htons(ETH_P_8021Q))
+		NLA_PUT_U16(skb, L2FLOWA_VLAN_TAG, table_entry->l2flow.vlan_tag);
+	else if (table_entry->l2flow.ethertype == htons(ETH_P_PPP_SES))
+		NLA_PUT_U16(skb, L2FLOWA_PPP_S_ID, table_entry->l2flow.session_id);
+
+	if(abm_l3_filtering){
+
+		if(table_entry->l2flow.ethertype != htons(ETH_P_PPP_SES))
+			NLA_PUT_U8(skb, L2FLOWA_IP_PROTO, table_entry->l2flow.l3.proto);
+		
+		if(table_entry->l2flow.ethertype == htons(ETH_P_IP)){
+			NLA_PUT_U32(skb, L2FLOWA_IP_SRC, table_entry->l2flow.l3.saddr.ip);
+			NLA_PUT_U32(skb, L2FLOWA_IP_DST, table_entry->l2flow.l3.daddr.ip);
+		}
+		else if (table_entry->l2flow.ethertype == htons(ETH_P_IPV6)){
+			NLA_PUT(skb, L2FLOWA_IP_SRC, sizeof(u_int32_t) * 4, 
+					table_entry->l2flow.l3.saddr.ip6);
+			NLA_PUT(skb, L2FLOWA_IP_DST, sizeof(u_int32_t) * 4, 
+					table_entry->l2flow.l3.daddr.ip6);
+		}
+		
+		if((table_entry->l2flow.l3.proto == IPPROTO_UDP) 
+		|| (table_entry->l2flow.l3.proto == IPPROTO_TCP)){
+			NLA_PUT_U16(skb, L2FLOWA_SPORT, table_entry->l2flow.l4.sport);
+			NLA_PUT_U16(skb, L2FLOWA_DPORT, table_entry->l2flow.l4.dport);
+		}
+	}
+	nlmsg_end(skb, nlh);
+	
+	if (netlink_has_listeners(s, L2FLOW_NL_GRP)){
+		NETLINK_CB(skb).pid = 0;	/* from kernel */
+		NETLINK_CB(skb).dst_group = L2FLOW_NL_GRP;
+
+		return netlink_broadcast(s, skb, 0, L2FLOW_NL_GRP, GFP_ATOMIC);
+	}
+	else{
+		err = -ENOTCONN;
+		goto err2;
+	}
+	
+nla_put_failure:
+	nlmsg_cancel(skb, nlh);
+	err = -EMSGSIZE;
+err2:
+	kfree_skb(skb);
+err:
+	return err;
+
+}
+
+/***************************************************************************
+*
+* abm_nl_rcv_msg
+* Handle NL message from user-space
+* 
+****************************************************************************/
+static int abm_nl_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
+{
+	int type, err = 0;
+	struct l2flow l2flow_temp;
+	struct l2flow_msg *l2flow_msg;
+	struct nlattr *tb[L2FLOWA_MAX + 1];
+
+	type = nlh->nlmsg_type;
+
+	if(type >= L2FLOW_MSG_MAX){
+		err = -EAGAIN;
+		goto out;
+	}
+
+	err = nlmsg_parse(nlh, sizeof(*l2flow_msg), tb, L2FLOWA_MAX, NULL);
+	if(err < 0)
+		goto out;
+	
+	switch(type)
+	{
+		case L2FLOW_MSG_ENTRY:
+			/*Messages must have at least l2flow_msg length */
+			if (nlh->nlmsg_len < NLMSG_LENGTH(sizeof(struct l2flow_msg))){
+				err = -EAGAIN;
+				goto out;
+			}
+	
+			memset(&l2flow_temp, 0, sizeof(l2flow_temp));
+			l2flow_msg = NLMSG_DATA(nlh);
+			
+			/* Here we don't really care of message sanity, if parameters are wrong entry won't be found */
+			/* No entry is created here */
+			memcpy(l2flow_temp.saddr, l2flow_msg->saddr, ETH_ALEN);
+			memcpy(l2flow_temp.daddr, l2flow_msg->daddr, ETH_ALEN);
+			l2flow_temp.ethertype = l2flow_msg->ethertype;
+
+			if(tb[L2FLOWA_VLAN_TAG])
+				l2flow_temp.vlan_tag = nla_get_u16(tb[L2FLOWA_VLAN_TAG]);
+
+			if(tb[L2FLOWA_PPP_S_ID])
+				l2flow_temp.session_id = nla_get_u16(tb[L2FLOWA_PPP_S_ID]);
+
+			if(tb[L2FLOWA_IP_SRC])
+				memcpy(&l2flow_temp.l3.saddr.all, nla_data(tb[L2FLOWA_IP_SRC]), nla_len(tb[L2FLOWA_IP_SRC]));
+
+			if(tb[L2FLOWA_IP_DST])
+				memcpy(&l2flow_temp.l3.daddr.all, nla_data(tb[L2FLOWA_IP_DST]), nla_len(tb[L2FLOWA_IP_DST]));
+
+			if(tb[L2FLOWA_IP_PROTO])
+				l2flow_temp.l3.proto= nla_get_u8(tb[L2FLOWA_IP_PROTO]);
+			
+			if(tb[L2FLOWA_SPORT])
+				l2flow_temp.l4.sport= nla_get_u16(tb[L2FLOWA_SPORT]);
+
+			if(tb[L2FLOWA_DPORT])
+				l2flow_temp.l4.dport= nla_get_u16(tb[L2FLOWA_DPORT]);
+			
+			err = abm_l2flow_msg_handle(l2flow_msg->action, l2flow_msg->flags, &l2flow_temp);
+			
+			
+		break;
+		case L2FLOW_MSG_RESET:
+		break;	
+	}
+out:
+	return err;
+}
+
+static void abm_nl_rcv_skb(struct sk_buff *skb)
+{
+	netlink_rcv_skb(skb, &abm_nl_rcv_msg);
+}
+/***************************************************************************
+*
+* __abm_go_dying
+* Move an entry to the the L2FLOW_STATE_DYING or delete the entry depending
+* on L2FLOW_FL_DEAD flag.
+****************************************************************************/
+static void __abm_go_dying(struct l2flowTable *table_entry)
+{
+	/* This function can be called from bridge event notifier, timer and netlink */
+	if(!(table_entry->flags & L2FLOW_FL_DEAD)){
+
+		/* Skip Netlink message sending if already pending but if we come from another state send it anyway */
+		if(!(table_entry->flags & L2FLOW_FL_PENDING_MSG)  || (table_entry->state != L2FLOW_STATE_DYING))
+			if(abm_nl_send_l2flow_msg(abm_nl, L2FLOW_ENTRY_DEL, 0, table_entry) != -ENOTCONN){
+				/* If message is succesully sent we expect an ack */
+				table_entry->flags |= L2FLOW_FL_WAIT_ACK;
+				list_add(&table_entry->list_wait_for_ack, &l2flow_list_wait_for_ack);
+			}
+		table_entry->state = L2FLOW_STATE_DYING;
+		table_entry->flags |= L2FLOW_FL_DEAD;
+		table_entry->timeout.expires = jiffies + l2flow_timeouts[L2FLOW_STATE_DYING];
+		add_timer(&table_entry->timeout);
+	}
+	else // Really die :)
+	{
+		abm_l2flow_del(table_entry);
+	}
+}
+
+/***************************************************************************
+*
+* abm_death_by_timeout
+* Timers callback
+*
+****************************************************************************/
+static void  abm_death_by_timeout(unsigned long arg)
+{
+	struct l2flowTable *table_entry = (struct l2flowTable *)arg;
+	
+	spin_lock_bh(&abm_lock);
+	__abm_go_dying(table_entry);
+	spin_unlock_bh(&abm_lock);
+}
+
+
+static int abm_nl_init(void)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,33)
+	if((abm_nl = netlink_kernel_create (&init_net, NETLINK_L2FLOW, L2FLOW_NL_GRP, 
+				abm_nl_rcv_skb, NULL, THIS_MODULE)) == 0)
+		return -ENOMEM;
+						
+#endif
+
+	return 0;
+}
+static void abm_nl_exit(void)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,33)
+	netlink_kernel_release(abm_nl);
+#endif
+}
+/***************************************************************************
+*
+* abm_l2flow_cmp
+* Compare two L2 flows
+*
+****************************************************************************/
+static inline int abm_l2flow_cmp(struct l2flow *flow_a, struct l2flow *flow_b)
+{
+	return memcmp(flow_a, flow_b, sizeof(struct l2flow));
+}
+
+/***************************************************************************
+*
+* abm_l2flow_find
+* Find a L2 flow table entry from a temporary L2 flow
+*
+****************************************************************************/
+static struct l2flowTable * abm_l2flow_find(struct l2flow *l2flowtmp)
+{
+	int key;
+	struct list_head *entry;
+	struct l2flowTable *table_entry;
+	
+	key = abm_l2flow_hash(l2flowtmp);
+
+	list_for_each(entry,  &l2flow_table[key]){
+		table_entry = container_of(entry, struct l2flowTable, list);
+		if(!abm_l2flow_cmp(&table_entry->l2flow, l2flowtmp))
+			return table_entry; // Found
+	}
+	return NULL; // Not found 
+}
+
+/***************************************************************************
+*
+* abm_l2flow_update
+* Update L2 flow entry state when status received from user-space
+*
+****************************************************************************/
+static void abm_l2flow_update(int flags, struct l2flowTable *table_entry)
+{
+	if(flags & L2FLOW_OFFLOADED){
+		/* Flow is programmed in FPP */
+		table_entry->state = L2FLOW_STATE_FF;
+		/* If timer already expired we'll die, it's ok though... */
+		del_timer(&table_entry->timeout);
+	}
+	else if(flags & L2FLOW_DENIED){
+		/* Flow is not programmed in FPP */
+		table_entry->state = L2FLOW_STATE_LINUX;
+		mod_timer(&table_entry->timeout, jiffies + l2flow_timeouts[table_entry->state]);
+	}
+	if(table_entry->flags & L2FLOW_FL_WAIT_ACK){
+		table_entry->flags &= ~L2FLOW_FL_WAIT_ACK;
+		list_del(&table_entry->list_wait_for_ack);
+	}
+}
+
+/***************************************************************************
+*
+* abm_l2flow_del
+* Remove entry from table and free entry
+*
+****************************************************************************/
+static void abm_l2flow_del(struct l2flowTable *table_entry)
+{
+	list_del(&table_entry->list);
+	list_del(&table_entry->list_by_src_mac);
+	list_del(&table_entry->list_by_dst_mac);
+	if(table_entry->flags & L2FLOW_FL_PENDING_MSG)
+		list_del(&table_entry->list_msg_to_send);
+	if(table_entry->flags & L2FLOW_FL_WAIT_ACK)
+		list_del(&table_entry->list_wait_for_ack);
+	kmem_cache_free(l2flow_cache, table_entry);
+	abm_nb_entries--;
+}
+
+/***************************************************************************
+*
+* abm_l2flow_msg_handle
+* Handle Netlink messages from user-space
+*
+****************************************************************************/
+static int abm_l2flow_msg_handle(char action, int flags, struct l2flow *l2flowtmp)
+{
+	struct l2flowTable *table_entry = NULL;
+	int rc = 0;
+	spin_lock_bh(&abm_lock);
+	
+	table_entry = abm_l2flow_find(l2flowtmp);
+
+	if(!table_entry){
+		rc = -ENOENT;
+		goto out;
+	}
+
+	if(action == L2FLOW_ENTRY_UPDATE){
+		abm_l2flow_update(flags, table_entry);
+	}
+	else if(action == L2FLOW_ENTRY_DEL){
+		/* No need to wait in dying state as event is coming from user-space app */
+		table_entry->flags |= L2FLOW_FL_DEAD;
+
+		if(table_entry->flags & L2FLOW_FL_WAIT_ACK){
+			table_entry->flags &= ~L2FLOW_FL_WAIT_ACK;
+			list_del(&table_entry->list_wait_for_ack);
+		}
+
+		/* Die soon or now */
+		if(del_timer(&table_entry->timeout) || (table_entry->state == L2FLOW_STATE_FF))
+			__abm_go_dying(table_entry);
+	}
+	else{
+		rc = -ENOMSG;
+		goto out;
+	}
+out:
+	spin_unlock_bh(&abm_lock);
+	return rc;
+}
+
+/***************************************************************************
+*
+* abm_l2flow_add
+* This function allocates and add an entry into flow_table from a temporary l2flow
+*
+****************************************************************************/
+static struct l2flowTable * abm_l2flow_add(struct l2flow *l2flowtmp)
+{
+	unsigned int key, key_src_mac, key_dst_mac;
+	struct l2flowTable* l2flow_entry = NULL;
+
+	if(abm_nb_entries >= abm_max_entries)
+		goto out;
+
+	key = abm_l2flow_hash(l2flowtmp);
+	key_src_mac = abm_l2flow_hash_mac(l2flowtmp->saddr);
+	key_dst_mac = abm_l2flow_hash_mac(l2flowtmp->daddr);
+	
+	l2flow_entry = kmem_cache_alloc(l2flow_cache, GFP_KERNEL); // Can be IRQ CTX ?? Atomic ?
+	if(!l2flow_entry){
+		printk(KERN_ERR "Automatic bridging module error l2flow_cache OOM\n");
+		goto out;
+	}
+	memset(l2flow_entry, 0, sizeof(*l2flow_entry));
+	memcpy(&l2flow_entry->l2flow, l2flowtmp, sizeof(*l2flowtmp));
+	/* Timer not yet started here */
+	setup_timer(&l2flow_entry->timeout, abm_death_by_timeout, (unsigned long) l2flow_entry);
+	
+	list_add(&l2flow_entry->list, &l2flow_table[key]);
+	list_add(&l2flow_entry->list_by_src_mac, &l2flow_table_by_src_mac[key_src_mac]);
+	list_add(&l2flow_entry->list_by_dst_mac, &l2flow_table_by_dst_mac[key_dst_mac]);
+
+	abm_nb_entries++;
+out:
+	return l2flow_entry;
+}
+
+struct tcpudphdr {
+	__be16 src;
+	__be16 dst;
+};
+
+/***************************************************************************
+*
+* abm_build_l2flow
+* Build the temporary L2 flow
+*
+****************************************************************************/
+static inline int abm_build_l2flow(struct sk_buff *skb, struct l2flow *l2flow_temp, unsigned short ethertype)
+{
+	memcpy(l2flow_temp->saddr, eth_hdr(skb)->h_source, ETH_ALEN);
+	memcpy(l2flow_temp->daddr, eth_hdr(skb)->h_dest, ETH_ALEN);
+	l2flow_temp->ethertype = ethertype;
+
+	if(ethertype == htons(ETH_P_8021Q)){
+		if (vlan_tx_tag_present(skb)) {
+			l2flow_temp->vlan_tag = htons(vlan_tx_tag_get(skb));
+		}
+		else {
+			struct vlan_hdr *vlanh;
+			struct vlan_hdr _vlanh;
+
+			vlanh = skb_header_pointer(skb, 0, sizeof(_vlanh), &_vlanh);
+			if(!vlanh)
+				return -1;
+
+			l2flow_temp->vlan_tag = vlanh->h_vlan_TCI;
+		}
+		return 0;
+	}
+	else if (ethertype == htons(ETH_P_PPP_SES)){
+		struct pppoe_hdr *ph;
+		struct pppoe_hdr _ph;
+		
+		ph = skb_header_pointer(skb, 0, sizeof(_ph), &_ph);
+		if(!ph)
+			return -1;
+
+		l2flow_temp->session_id = ph->sid;
+
+		return 0;
+	}
+
+	if(abm_l3_filtering){
+		int l3_hdr_len;
+		
+		if(ethertype == htons(ETH_P_IP)){
+			struct iphdr *iph;
+			struct iphdr _iph;
+			
+			iph = skb_header_pointer(skb, 0, sizeof(_iph), &_iph);
+			if(!iph)
+				return -1;
+			
+			l2flow_temp->l3.saddr.ip = iph->saddr;
+			l2flow_temp->l3.daddr.ip = iph->daddr;
+			l2flow_temp->l3.proto = iph->protocol;
+			l3_hdr_len = iph->ihl * 4;
+			/* If Packet is fragmented, don't update L4 information */
+			if(iph->frag_off & htons(IP_MF | IP_OFFSET))
+				return 0;
+		}
+		else if (ethertype == htons(ETH_P_IPV6)){
+			struct ipv6hdr *ip6h;
+			struct ipv6hdr _ip6h;
+			uint8_t nexthdr;
+			
+			ip6h = skb_header_pointer(skb, 0, sizeof(_ip6h), &_ip6h);
+			if(!ip6h)
+				return -1;
+			
+			memcpy(l2flow_temp->l3.saddr.ip6, ip6h->saddr.s6_addr, 16);
+			memcpy(l2flow_temp->l3.daddr.ip6, ip6h->daddr.s6_addr, 16);
+			nexthdr = ip6h->nexthdr;
+
+			l3_hdr_len = ipv6_skip_exthdr(skb, sizeof(_ip6h), &nexthdr);
+			if(l3_hdr_len == -1)
+				return -1;
+			
+			l2flow_temp->l3.proto = nexthdr;
+		}
+		else
+			return -1; //We don't support
+
+		if((l2flow_temp->l3.proto == IPPROTO_UDP) || (l2flow_temp->l3.proto == IPPROTO_TCP)){
+			struct tcpudphdr *tcpudph;
+			struct tcpudphdr _tcpudph;
+
+			tcpudph = skb_header_pointer(skb, l3_hdr_len, sizeof(_tcpudph), &_tcpudph);
+			if(!tcpudph)
+				return -1;
+			
+			l2flow_temp->l4.sport = tcpudph->src;
+			l2flow_temp->l4.dport = tcpudph->dst;
+		}
+	}
+	return 0; //Success
+}
+
+/***************************************************************************
+*
+* abm_ebt_hook
+* Core l2flow detection mechanism
+*
+****************************************************************************/
+static unsigned int abm_ebt_hook(unsigned int hooknum,
+			struct sk_buff *skb,
+			const struct net_device *in,
+			const struct net_device *out,
+			int (*okfn)(struct sk_buff *))
+{
+	struct l2flow l2flow_temp;
+	struct l2flowTable *l2flow_entry;
+	unsigned short ethertype;
+
+	if (vlan_tx_tag_present(skb))
+		ethertype = htons(ETH_P_8021Q);
+	else
+		ethertype = eth_hdr(skb)->h_proto;
+
+	if(!skb->cb[4])
+		goto exit0;
+	
+	if((ethertype != htons(ETH_P_IP))
+	&& (ethertype != htons(ETH_P_IPV6))
+	&& (ethertype != htons(ETH_P_PPP_SES))
+	&& (ethertype != htons(ETH_P_8021Q))
+	)
+		goto exit0;
+
+	memset(&l2flow_temp, 0, sizeof(l2flow_temp));
+	if(abm_build_l2flow(skb, &l2flow_temp, ethertype) < 0)
+		goto exit0;
+
+	spin_lock(&abm_lock);
+
+	if (hooknum == NF_BR_FORWARD) {
+		if((l2flow_entry = abm_l2flow_find(&l2flow_temp)) == NULL){
+			/* New entry */
+			if((l2flow_entry = abm_l2flow_add(&l2flow_temp)) == NULL)
+				goto exit1;
+				
+			l2flow_entry->state = L2FLOW_STATE_SEEN;
+			l2flow_entry->idev_ifi = in->ifindex;
+			mod_timer(&l2flow_entry->timeout, jiffies + l2flow_timeouts[l2flow_entry->state]);
+		}
+		else{
+			if(in->ifindex != l2flow_entry->idev_ifi){
+				l2flow_entry->flags |= L2FLOW_FL_NEEDS_UPDATE;
+				l2flow_entry->idev_ifi = in->ifindex;
+			}
+		}
+	}
+	else if(hooknum == NF_BR_POST_ROUTING){
+		if((l2flow_entry = abm_l2flow_find(&l2flow_temp)) != NULL){
+			int rc;
+
+			if(out->ifindex != l2flow_entry->odev_ifi){
+				l2flow_entry->flags |= L2FLOW_FL_NEEDS_UPDATE;
+				l2flow_entry->odev_ifi = out->ifindex;
+			}
+			l2flow_entry->packet_mark = skb->mark & 0xFFFF;
+
+			switch(l2flow_entry->state)
+			{
+				case L2FLOW_STATE_SEEN:
+					if((rc = abm_nl_send_l2flow_msg(abm_nl, L2FLOW_ENTRY_NEW, 0, l2flow_entry)) != -ENOTCONN){
+						l2flow_entry->flags &= ~L2FLOW_FL_NEEDS_UPDATE;
+						l2flow_entry->flags |= L2FLOW_FL_WAIT_ACK;
+						l2flow_entry->time_sent = jiffies;
+						list_add(&l2flow_entry->list_wait_for_ack, &l2flow_list_wait_for_ack);
+					}
+					l2flow_entry->state = L2FLOW_STATE_CONFIRMED;
+					break;
+				case L2FLOW_STATE_FF:
+				case L2FLOW_STATE_LINUX:
+					/* Updates are already handled via notifiers but we need this to update input interface in some cases*/
+					/* However if we know that there is a pending message don't send it here */
+					if(!(l2flow_entry->flags & L2FLOW_FL_PENDING_MSG) 
+					&& (l2flow_entry->flags & L2FLOW_FL_NEEDS_UPDATE)){
+						if((rc = abm_nl_send_l2flow_msg(abm_nl, L2FLOW_ENTRY_UPDATE, 0, l2flow_entry)) != -ENOTCONN){
+							l2flow_entry->flags &= ~L2FLOW_FL_NEEDS_UPDATE;
+							l2flow_entry->time_sent = jiffies;
+
+							if(!(l2flow_entry->flags & L2FLOW_FL_WAIT_ACK)){
+								list_add(&l2flow_entry->list_wait_for_ack, &l2flow_list_wait_for_ack);
+								l2flow_entry->flags |= L2FLOW_FL_WAIT_ACK;
+							}
+						}
+					}
+					break;
+				default:
+					break;
+			}//End switch
+
+			if((l2flow_entry->state != L2FLOW_STATE_FF) && (l2flow_entry->state != L2FLOW_STATE_DYING)){
+				mod_timer_pending(&l2flow_entry->timeout, jiffies + l2flow_timeouts[l2flow_entry->state]);
+			}
+		}
+	}
+exit1:
+	spin_unlock(&abm_lock);
+exit0:
+	return NF_ACCEPT;
+}
+
+static struct nf_hook_ops abm_ebt_ops[] /*__read_mostly*/ = {
+	{
+		.hook		= abm_ebt_hook,
+		.owner		= THIS_MODULE,
+		.pf		= NFPROTO_BRIDGE,
+		.hooknum	= NF_BR_FORWARD,
+		.priority		= NF_BR_PRI_LAST,
+	},
+	{
+		.hook		= abm_ebt_hook,
+		.owner		= THIS_MODULE,
+		.pf		= NFPROTO_BRIDGE,
+		.hooknum	= NF_BR_POST_ROUTING,
+		.priority		= NF_BR_PRI_LAST - 1,  //Just before bridge_netilter hook
+	},
+};
+
+/***************************************************************************
+*
+* abm_l2flow_table_flush
+* Flush l2flow table (called in user-context)
+*
+****************************************************************************/
+static  void abm_l2flow_table_flush(void)
+{
+	int i;
+	struct list_head *entry, *tmp;
+	struct l2flowTable *table_entry;
+
+	spin_lock_bh(&abm_lock);
+	for(i = 0; i < L2FLOW_HASH_TABLE_SIZE; i++){
+		list_for_each_safe(entry, tmp, &l2flow_table[i]){
+			table_entry = container_of(entry, struct l2flowTable, list);
+			table_entry->flags |= L2FLOW_FL_DEAD;
+			if(del_timer(&table_entry->timeout) || table_entry->state == L2FLOW_STATE_FF)
+				__abm_go_dying(table_entry);
+		}
+	}
+	spin_unlock_bh(&abm_lock);
+}
+
+/***************************************************************************
+*
+* abm_l2flow_table_flush
+* Small busy loop to wait for already expired timers 
+*
+****************************************************************************/
+static  __inline void abm_l2flow_table_wait_timers(void)
+{
+	int i, empty;
+test_list:
+	empty = 1;
+	for(i = 0; i < L2FLOW_HASH_TABLE_SIZE; i++)
+		if(!list_empty(&l2flow_table[i])){
+			empty = 0;
+			break;
+		}
+		
+	if(empty)
+		return;
+	else{
+		schedule();
+	}	goto test_list;
+}
+
+/***************************************************************************
+*
+* abm_l2flow_table_init
+* Init l2flow table and l2flow cache
+*
+****************************************************************************/
+static int abm_l2flow_table_init(void)
+{
+	int i;
+	
+	for(i = 0; i < L2FLOW_HASH_TABLE_SIZE; i++){
+		INIT_LIST_HEAD(&l2flow_table[i]);
+	}
+	for(i = 0; i < L2FLOW_HASH_BY_MAC_TABLE_SIZE; i++){
+		INIT_LIST_HEAD(&l2flow_table_by_src_mac[i]);
+		INIT_LIST_HEAD(&l2flow_table_by_dst_mac[i]);
+	}
+	INIT_LIST_HEAD(&l2flow_list_msg_to_send);
+	INIT_LIST_HEAD(&l2flow_list_wait_for_ack);
+	
+	l2flow_cache = kmem_cache_create("l2flow_cache",
+					 sizeof(struct l2flowTable), 0, 0, NULL);
+	if (!l2flow_cache)
+		return -ENOMEM;
+
+	return 0;
+}
+
+/***************************************************************************
+*
+* abm_l2flow_table_exit
+* Flush all entries and de-allocate cache
+*
+****************************************************************************/
+static void abm_l2flow_table_exit(void)
+{
+	abm_l2flow_table_flush();
+	abm_l2flow_table_wait_timers();
+	kmem_cache_destroy(l2flow_cache);
+}
+
+#ifdef CONFIG_PROC_FS
+/***************************************************************************
+*
+*    Seq file implementation
+*    Allow user to get L2 flow table via /proc/net/abm
+*
+****************************************************************************/
+
+struct abm_seq_state{
+	unsigned int bucket;
+};
+
+struct l2flowTable * abm_get_first(struct seq_file *seq)
+{
+	struct abm_seq_state *state = seq->private;
+	int bucket = 0;
+
+	while (bucket < L2FLOW_HASH_TABLE_SIZE){
+		/* Return first entry present in a bucket */
+		if(&l2flow_table[bucket] != l2flow_table[bucket].next){
+			state->bucket = bucket;
+			return container_of(l2flow_table[bucket].next, struct l2flowTable, list);
+		}
+		else
+			bucket++;
+	}
+	return NULL; // Not found 
+}
+
+struct l2flowTable * abm_get_next(struct seq_file *seq, struct l2flowTable *table_entry)
+{
+	struct abm_seq_state *state = seq->private;
+	struct list_head *entry;
+	int bucket = state->bucket;
+
+	entry = table_entry->list.next;
+
+	if(entry != &l2flow_table[bucket]) // Next != Head
+		return container_of(entry, struct l2flowTable, list);
+
+	/* Move to next bucket */
+	bucket++;
+
+	/* Scan the hash-table and return the first entry linearly */
+	while (bucket < L2FLOW_HASH_TABLE_SIZE){
+		if(&l2flow_table[bucket] != l2flow_table[bucket].next){
+			state->bucket = bucket;
+			return container_of(l2flow_table[bucket].next, struct l2flowTable, list);
+		}
+		else
+			bucket++;
+	}
+	return NULL; // Not found 
+}
+
+struct l2flowTable * abm_get_idx(struct seq_file *seq, loff_t *pos)
+{
+	loff_t idxpos = *pos; // > 1
+	struct l2flowTable * entry = abm_get_first(seq);
+
+	if(entry){
+		idxpos--;
+		while(idxpos){
+			entry = abm_get_next(seq, entry);
+			if(entry)
+				idxpos--;
+			else
+				return NULL;
+		}	
+	}
+	return entry;
+}
+
+void * abm_seq_next(struct seq_file *seq, void *v, loff_t *pos)
+{
+	void *rc;
+	
+	if (v == SEQ_START_TOKEN) {
+		rc = abm_get_first(seq);
+	}
+	else
+		rc = abm_get_next(seq, v);
+
+	++(*pos);
+	return rc;
+}
+
+static int abm_seq_show(struct seq_file *seq, void *v)
+{
+	if (v == SEQ_START_TOKEN) {
+		seq_puts(seq, "ABM L2 Flow entries dump\n------------------------\n");
+	} else {
+		struct l2flowTable* entry = (struct l2flowTable*)v;	
+		struct l2flow *l2flowtmp = &entry->l2flow;
+
+		seq_printf(seq, "  Saddr=%02x:%02x:%02x:%02x:%02x:%02x", l2flowtmp->saddr[0], l2flowtmp->saddr[1], l2flowtmp->saddr[2],
+															l2flowtmp->saddr[3], l2flowtmp->saddr[4], l2flowtmp->saddr[5]);
+		seq_printf(seq, "  Daddr=%02x:%02x:%02x:%02x:%02x:%02x", l2flowtmp->daddr[0], l2flowtmp->daddr[1], l2flowtmp->daddr[2],
+															l2flowtmp->daddr[3], l2flowtmp->daddr[4], l2flowtmp->daddr[5]);
+		seq_printf(seq, "  Ethertype=0x%04x", htons(l2flowtmp->ethertype));
+		seq_printf(seq, "  Input itf=%d", entry->idev_ifi);
+		seq_printf(seq, "  Output itf=%d", entry->odev_ifi);
+		seq_printf(seq, "  Mark=0x%04x", entry->packet_mark);
+		
+		if(entry->l2flow.ethertype == htons(ETH_P_PPP_SES))
+			seq_printf(seq, "  PPPoE Session id=%d", ntohs(l2flowtmp->session_id));
+		else if(entry->l2flow.ethertype == htons(ETH_P_8021Q))
+			seq_printf(seq, "  VLAN TCI=0x%04x", ntohs(l2flowtmp->vlan_tag));
+		
+		seq_printf(seq, "  State=[%s]", l2flow_states_string[entry->state]);
+		
+		if(entry->state != L2FLOW_STATE_FF)
+			seq_printf(seq, "  Timeout=%ds",(int) (entry->timeout.expires- jiffies)/HZ);
+
+		if(abm_l3_filtering){
+			if(l2flowtmp->ethertype == htons(ETH_P_IP)){
+				seq_printf(seq, " Src=%pI4", &l2flowtmp->l3.saddr.ip);
+				seq_printf(seq, " Dst=%pI4", &l2flowtmp->l3.daddr.ip);
+				seq_printf(seq, " Proto=%d", l2flowtmp->l3.proto);
+			}
+			else if (l2flowtmp->ethertype == htons(ETH_P_IPV6)){
+				seq_printf(seq, " Src=%pI6", l2flowtmp->l3.saddr.ip6);
+				seq_printf(seq, " Dst=%pI6", l2flowtmp->l3.daddr.ip6);
+				seq_printf(seq, " Proto=%d", l2flowtmp->l3.proto);
+			}
+			if((l2flowtmp->l3.proto == IPPROTO_UDP) || (l2flowtmp->l3.proto == IPPROTO_TCP)){
+				seq_printf(seq, " Sport=%d", ntohs(l2flowtmp->l4.sport));
+				seq_printf(seq, " Dport=%d", ntohs(l2flowtmp->l4.dport));
+			}
+		}
+		seq_printf(seq, "\n");
+	}
+	return 0;
+}
+
+static void *abm_seq_start(struct seq_file *seq, loff_t *pos)
+{
+	struct abm_seq_state * state = seq->private;
+	
+	state->bucket = 0;
+	spin_lock_bh(&abm_lock);
+	
+	return *pos ? abm_get_idx(seq, pos) : SEQ_START_TOKEN;
+}
+
+static void abm_seq_stop(struct seq_file *seq, void *v)
+{
+	spin_unlock_bh(&abm_lock);
+
+	return;
+}
+
+static const struct seq_operations abm_seq_ops = {
+	.start  = abm_seq_start,
+	.next   = abm_seq_next,
+	.stop   = abm_seq_stop,
+	.show   = abm_seq_show,
+};
+
+static int abm_seq_open(struct inode *inode, struct file *file)
+{
+	return seq_open_net(inode, file, &abm_seq_ops,
+			    sizeof(struct abm_seq_state));
+}
+
+static const struct file_operations abm_seq_fops = {
+	.owner		= THIS_MODULE,
+	.open		= abm_seq_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release		= seq_release_net,
+};
+
+static int __init abm_proc_init(void)
+{
+	if (!proc_net_fops_create(&init_net, "abm", S_IRUGO, &abm_seq_fops))
+		return -ENOMEM;
+	return 0;
+}
+
+static void  abm_proc_fini(void)
+{
+	proc_net_remove(&init_net, "abm");
+}
+
+#else /* CONFIG_PROC_FS */
+
+static int __init abm_proc_init(void)
+{
+	return 0;
+}
+
+static void  abm_proc_fini(void)
+{
+
+}
+
+#endif /* CONFIG_PROC_FS */
+
+#ifdef CONFIG_SYSCTL
+/***************************************************************************
+*
+*    Sysctl implementation
+*    Allow user to modify ABM parameters in /proc/sys/net/abm/...
+*
+****************************************************************************/
+
+struct ctl_table_header * abm_sysctl_hdr;
+
+static int abm_sysctl_l3_filtering(ctl_table *ctl, int write,
+				  void __user *buffer,
+				  size_t *lenp, loff_t *ppos)
+{
+	int *valp = ctl->data;
+	int val = *valp;
+	int rc;
+	int old_abm_l3_filtering = abm_l3_filtering;
+	int ret = proc_dointvec(ctl, write, buffer, lenp, ppos);
+
+	if (write && *valp != val) {
+		if(((!old_abm_l3_filtering) && *valp) || (old_abm_l3_filtering && (!*valp))){
+			abm_l2flow_table_flush();
+			
+			if((rc = abm_nl_send_rst_msg(abm_nl)) < 0)
+				ABM_PRINT(KERN_ERR, " Netlink send rst msg error = %d\n", rc);
+			
+		}
+		abm_l3_filtering = (*valp) ? 1 : 0;
+	}
+	return ret;
+}
+
+struct ctl_path abm_sysctl_path[] = {
+	{ .procname = "net", },
+	{ .procname = "abm", },
+	{ }
+};
+
+static ctl_table abm_sysctl_table[] = {
+	{
+		.procname	= "abm_l3_filtering",
+		.data		= &abm_l3_filtering,
+		.maxlen		= sizeof(unsigned int),
+		.mode		= 0644,
+		.proc_handler	= abm_sysctl_l3_filtering,
+	},
+	{
+		.procname	= "abm_timeout_seen",
+		.data		= &l2flow_timeouts[L2FLOW_STATE_SEEN],
+		.maxlen		= sizeof(unsigned int),
+		.mode		= 0644,
+		.proc_handler	= proc_dointvec_jiffies,
+	},
+	{
+		.procname	= "abm_timeout_confirmed",
+		.data		= &l2flow_timeouts[L2FLOW_STATE_CONFIRMED],
+		.maxlen		= sizeof(unsigned int),
+		.mode		= 0644,
+		.proc_handler	= proc_dointvec_jiffies,
+	},
+	{
+		.procname	= "abm_timeout_linux",
+		.data		= &l2flow_timeouts[L2FLOW_STATE_LINUX],
+		.maxlen		= sizeof(unsigned int),
+		.mode		= 0644,
+		.proc_handler	= proc_dointvec_jiffies,
+	},
+	{
+		.procname	= "abm_timeout_dying",
+		.data		= &l2flow_timeouts[L2FLOW_STATE_DYING],
+		.maxlen 	= sizeof(unsigned int),
+		.mode		= 0644,
+		.proc_handler	= proc_dointvec_jiffies,
+	},
+	{
+		.procname	= "abm_retransmit_delay",
+		.data		= &abm_retransmit_time,
+		.maxlen 		= sizeof(unsigned int),
+		.mode		= 0644,
+		.proc_handler	= proc_dointvec_jiffies,
+	},
+	{
+		.procname	= "abm_max_entries",
+		.data		= &abm_max_entries,
+		.maxlen 		= sizeof(unsigned int),
+		.mode		= 0644,
+		.proc_handler	= proc_dointvec,
+	},
+	{ }
+};
+
+static int __init abm_sysctl_init(void)
+{
+	if((abm_sysctl_hdr = register_net_sysctl_table(&init_net, abm_sysctl_path,
+						abm_sysctl_table)) == NULL)
+		return -1;
+
+	return 0;
+}
+
+static void abm_sysctl_fini(void)
+{
+	unregister_net_sysctl_table(abm_sysctl_hdr);
+}
+
+#else
+static int __init abm_sysctl_init(void)
+{
+	return 0;
+}
+
+static void abm_sysctl_fini(void)
+{
+
+}
+
+#endif
+
+/***************************************************************************
+*
+*   abm_init 
+*   Module initialisation
+*
+****************************************************************************/
+static int abm_init(void)
+{
+	int rc = 0;
+
+	printk(KERN_DEBUG "Initializing Automatic bridging module v%s\n", auto_bridge_version);
+	if((kabm_wq = create_singlethread_workqueue("abm_wq")) == NULL){
+		rc = -ENOMEM;
+		ABM_PRINT(KERN_ERR, "Automatic bridging module error creating wq rc = %d \n", rc);
+		return rc;
+	}
+	if((rc = abm_l2flow_table_init()) < 0){
+		ABM_PRINT(KERN_ERR, "Automatic bridging module error l2flow_table init rc = %d \n", rc);
+		return rc;
+	}
+	br_fdb_register_can_expire_cb(&abm_fdb_can_expire);
+	if((rc = abm_nl_init()) < 0){
+		ABM_PRINT(KERN_ERR, "Automatic bridging module error netlink init int rc = %d \n", rc);
+		return rc;
+	}
+	if((rc = abm_proc_init()) < 0){
+		ABM_PRINT(KERN_ERR, "Automatic bridging module error can't create /proc file rc = %d \n", rc);
+		return rc;
+	}
+	if((rc = abm_sysctl_init()) < 0){
+		ABM_PRINT(KERN_ERR, "Automatic bridging module error can't create sysctl rc = %d \n", rc);
+		return rc;
+	}
+	if((rc = nf_register_hooks(abm_ebt_ops, ARRAY_SIZE(abm_ebt_ops))) < 0){
+	ABM_PRINT(KERN_ERR, "Automatic bridging module error can't register hooks int rc = %d \n", rc);
+		return rc;
+	}
+	register_brevent_notifier(&abm_br_notifier);
+	queue_delayed_work(kabm_wq, &abm_work_retransmit, abm_retransmit_time);
+	
+	return 0;
+}
+
+/***************************************************************************
+*
+*   abm_exit 
+*   Module exit, can't fail
+*
+****************************************************************************/
+static void abm_exit(void)
+{
+	printk(KERN_DEBUG "Exiting Automatic bridging module \n");
+	unregister_brevent_notifier(&abm_br_notifier);
+	cancel_work_sync(&abm_work_send_msg);
+	cancel_delayed_work_sync(&abm_work_retransmit);
+	destroy_workqueue(kabm_wq);
+	nf_unregister_hooks(abm_ebt_ops, ARRAY_SIZE(abm_ebt_ops));
+	abm_nl_exit();
+	br_fdb_deregister_can_expire_cb();
+	abm_l2flow_table_exit();
+	abm_proc_fini();
+	abm_sysctl_fini();
+}
+
+
+module_init(abm_init);
+module_exit(abm_exit);
+
diff --git a/auto_bridge/auto_bridge_private.h b/auto_bridge/auto_bridge_private.h
new file mode 100644
index 0000000..9795e31
--- /dev/null
+++ b/auto_bridge/auto_bridge_private.h
@@ -0,0 +1,153 @@
+/* 
+ *
+ *  Copyright (C) 2007 Mindspeed Technologies, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef _AUTO_BRIDGE_PRIVATE_H
+#define _AUTO_BRIDGE_PRIVATE_H
+
+
+#define L2FLOW_HASH_TABLE_SIZE		1024
+#define L2FLOW_HASH_BY_MAC_TABLE_SIZE 	128
+
+#define ABM_DEFAULT_MAX_ENTRIES		5000
+
+/* Internal flags */
+#define L2FLOW_FL_NEEDS_UPDATE	0x1
+#define L2FLOW_FL_DEAD			0x2
+#define L2FLOW_FL_WAIT_ACK		0x4
+#define L2FLOW_FL_PENDING_MSG		0x8
+
+enum l2flow_state{
+	L2FLOW_STATE_SEEN,
+	L2FLOW_STATE_CONFIRMED,
+	L2FLOW_STATE_LINUX,
+	L2FLOW_STATE_FF,
+	L2FLOW_STATE_DYING, // Intermediate state before effective deletion to give some time to CMM
+	L2FLOW_STATE_MAX,
+};
+ 
+/* L2flow definition*/
+struct l2flow
+{
+	u8 saddr[ETH_ALEN];
+	u8 daddr[ETH_ALEN];
+	u16 ethertype; 
+	u16 session_id;
+	u16 vlan_tag; /* TCI only */ 
+	/* L3 info optional */
+	struct{
+		union {
+			u32 all[4];
+			u32 ip;
+			u32 ip6[4];
+		}saddr;
+		union {
+			u32 all[4];
+			u32 ip;
+			u32 ip6[4];
+		}daddr;
+		u8 proto;
+	}l3;
+	struct{
+		/* L4 info optional */
+		u16 sport;
+		u16 dport;
+	}l4;
+};
+
+
+/* L2flow table entry definition*/
+struct l2flowTable
+{
+	struct list_head list;
+	struct list_head list_by_src_mac;
+	struct list_head list_by_dst_mac;
+	struct list_head list_wait_for_ack;
+	struct list_head list_msg_to_send;
+
+	unsigned char state;
+	unsigned long time_sent;
+	unsigned int flags;
+	struct timer_list timeout;
+	u32 idev_ifi;
+	u32 odev_ifi;
+	u16 packet_mark;
+	struct l2flow l2flow;
+};
+
+#define ABM_PRINT(type, info, args...) do {printk(type "ABM :" info, ## args);} while(0)
+
+static inline void print_l2flow(struct l2flow *l2flowtmp)
+{
+	ABM_PRINT(KERN_DEBUG, "  Saddr : %02x:%02x:%02x:%02x:%02x:%02x\n", l2flowtmp->saddr[0], l2flowtmp->saddr[1], l2flowtmp->saddr[2],
+															l2flowtmp->saddr[3], l2flowtmp->saddr[4], l2flowtmp->saddr[5]);
+	ABM_PRINT(KERN_DEBUG, "  Daddr : %02x:%02x:%02x:%02x:%02x:%02x\n", l2flowtmp->daddr[0], l2flowtmp->daddr[1], l2flowtmp->daddr[2],
+															l2flowtmp->daddr[3], l2flowtmp->daddr[4], l2flowtmp->daddr[5]);
+	ABM_PRINT(KERN_DEBUG, "  Ethertype : %04x\n", htons(l2flowtmp->ethertype));
+	ABM_PRINT(KERN_DEBUG, "  PPPoE Session id : %d\n", l2flowtmp->session_id);
+}
+
+#if 0
+static inline unsigned int abm_l2_flow_hash(u8 *saddr,  u8 *daddr, u16 ethertype, 
+	u32 session_id, u32 *ipsaddr, u32 *ipdaddr, u8 proto, u16 sport, u16 dport)
+{
+	u32 a, b, c, d , e;
+	
+	a = jhash((void *) saddr, 6, ethertype);
+	b = jhash((void *) daddr, 6, session_id);
+	c = 0;
+	d = 0;
+
+	if (ethertype == htons(ETH_P_IP))
+	{
+		c = jhash_2words(*ipsaddr, *ipdaddr, sport | (dport << 16));
+	}
+	else if (ethertype == htons(ETH_P_IPV6))
+	{
+		c = jhash2((void *) ipsaddr, 4, sport);
+		d =jhash2((void *) ipdaddr, 4, dport);
+	}
+	
+	return jhash_3words(a, b, c, d);
+}
+#endif
+static inline unsigned int abm_l2flow_hash(struct l2flow *l2flowtmp)
+{	
+	return (jhash(l2flowtmp, sizeof(struct l2flow), 0x12345678) & (L2FLOW_HASH_TABLE_SIZE - 1));
+}
+static inline unsigned int abm_l2flow_hash_mac(char *src_mac)
+{	
+	return (jhash(src_mac, ETH_ALEN, 0x12345678) & (L2FLOW_HASH_BY_MAC_TABLE_SIZE - 1));
+}
+
+static inline int abm_l2flow_cmp(struct l2flow *flow_a, struct l2flow *flow_b);
+static struct l2flowTable * abm_l2flow_find(struct l2flow *l2flowtmp);
+static int abm_l2flow_msg_handle(char action, int flags, struct l2flow *l2flowtmp);
+static struct l2flowTable *  abm_l2flow_add(struct l2flow *l2flowtmp);
+static void abm_l2flow_del(struct l2flowTable *l2flow_entry);
+static void abm_l2flow_update(int flags, struct l2flowTable *table_entry);
+static  void abm_l2flow_table_flush(void);
+extern void br_fdb_register_can_expire_cb(int(*cb)(unsigned char *mac_addr, struct net_device *dev));
+extern void br_fdb_deregister_can_expire_cb(void);
+static void abm_do_work_send_msg(struct work_struct *work);
+static void abm_do_work_retransmit(struct work_struct *work);
+static int abm_nl_send_l2flow_msg(struct sock *s, char action, int flags, struct l2flowTable *table_entry);
+static void __abm_go_dying(struct l2flowTable *table_entry);
+
+
+#endif
diff --git a/auto_bridge/include/auto_bridge.h b/auto_bridge/include/auto_bridge.h
new file mode 100644
index 0000000..c712b9d
--- /dev/null
+++ b/auto_bridge/include/auto_bridge.h
@@ -0,0 +1,86 @@
+/*
+ *
+ *  Copyright (C) 2007 Mindspeed Technologies, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#ifndef _AUTO_BRIDGE_H
+#define _AUTO_BRIDGE_H
+
+#include <linux/if_ether.h>
+#include <linux/types.h>
+
+#ifndef NETLINK_L2FLOW
+#define NETLINK_L2FLOW             33
+#endif
+
+#define	L2FLOW_NL_GRP			1
+
+/* Msg type */
+enum l2flow_msg_types{
+	L2FLOW_MSG_BASE = 16, //msg_types < 0x10 are reserved control msg types
+	L2FLOW_MSG_ENTRY,
+	L2FLOW_MSG_RESET,
+	L2FLOW_MSG_MAX
+};
+
+/* Flags used for CMM answers */
+#define L2FLOW_OFFLOADED			0x1
+#define L2FLOW_DENIED				0x2
+#define L2FLOW_ACK				0x4
+
+/* Actions */
+enum l2flow_msg_actions{
+	L2FLOW_ENTRY_NEW,
+	L2FLOW_ENTRY_UPDATE,
+	L2FLOW_ENTRY_DEL,
+	L2FLOW_ENTRY_MAX,
+};
+
+
+/* L2flow netlink message header base */
+struct l2flow_msg
+{
+	/* Minimal parameters */
+	u_int8_t action;
+	u_int32_t flags;
+	u_int8_t saddr[ETH_ALEN];
+	u_int8_t daddr[ETH_ALEN];
+	u_int16_t ethertype;
+};
+
+enum{
+	L2FLOWA_UNSPEC,
+	L2FLOWA_VLAN_TAG,
+	L2FLOWA_PPP_S_ID,
+	L2FLOWA_IIF_IDX,
+	L2FLOWA_OIF_IDX,
+	L2FLOWA_IP_SRC,
+	L2FLOWA_IP_DST,
+	L2FLOWA_IP_PROTO,
+	L2FLOWA_SPORT,
+	L2FLOWA_DPORT,
+	L2FLOWA_MARK,
+	__L2FLOWA_MAX,
+};
+
+#define L2FLOWA_MAX (__L2FLOWA_MAX - 1)
+
+/* RtNetlink style helper macros for CMM */
+#define L2FLOWA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct l2flow_msg))
+#define L2FLOWA_RTA(r)  ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct l2flow_msg))))
+
+
+#endif