Browse Source

client: move deciding which peers frames are sent to into FrameDecider

ambrop7 15 years ago
parent
commit
291eb4e7d9

+ 1 - 0
blog_channels.txt

@@ -10,3 +10,4 @@ ServerConnection 4
 flooder 4
 flooder 4
 Listener 4
 Listener 4
 DataProto 4
 DataProto 4
+FrameDecider 4

+ 1 - 0
client/CMakeLists.txt

@@ -5,6 +5,7 @@ add_executable(badvpn-client
     PasswordListener.c
     PasswordListener.c
     DataProto.c
     DataProto.c
     PasswordSender.c
     PasswordSender.c
+    FrameDecider.c
 )
 )
 target_link_libraries(badvpn-client system flow tuntap server_conection security ${NSPR_LIBRARIES} ${NSS_LIBRARIES})
 target_link_libraries(badvpn-client system flow tuntap server_conection security ${NSPR_LIBRARIES} ${NSS_LIBRARIES})
 
 

+ 826 - 0
client/FrameDecider.c

@@ -0,0 +1,826 @@
+/**
+ * @file FrameDecider.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * This file is part of BadVPN.
+ * 
+ * BadVPN is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ * 
+ * BadVPN 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <string.h>
+#include <stddef.h>
+#include <inttypes.h>
+
+#include <misc/debug.h>
+#include <misc/offset.h>
+#include <misc/balloc.h>
+#include <misc/ethernet_proto.h>
+#include <misc/ipv4_proto.h>
+#include <misc/igmp_proto.h>
+#include <misc/byteorder.h>
+#include <system/BLog.h>
+
+#include <client/FrameDecider.h>
+
+#include <generated/blog_channel_FrameDecider.h>
+
+#define DECIDE_STATE_NONE 1
+#define DECIDE_STATE_UNICAST 2
+#define DECIDE_STATE_FLOOD 3
+#define DECIDE_STATE_MULTICAST 4
+
+static int mac_comparator (void *user, uint8_t *mac1, uint8_t *mac2)
+{
+    int c = memcmp(mac1, mac2, 6);
+    if (c < 0) {
+        return -1;
+    }
+    if (c > 0) {
+        return 1;
+    }
+    return 0;
+}
+
+static int uint32_comparator (void *user, uint32_t *v1, uint32_t *v2)
+{
+    if (*v1 < *v2) {
+        return -1;
+    }
+    if (*v1 > *v2) {
+        return 1;
+    }
+    return 0;
+}
+
+static void add_mac_to_peer (FrameDeciderPeer *o, uint8_t mac[6])
+{
+    FrameDecider *d = o->d;
+    
+    // locate entry in tree
+    BAVLNode *tree_node = BAVL_LookupExact(&d->macs_tree, mac);
+    if (tree_node) {
+        struct _FrameDecider_mac_entry *entry = UPPER_OBJECT(tree_node, struct _FrameDecider_mac_entry, tree_node);
+        
+        if (entry->peer == o) {
+            // this is our MAC; only move it to the end of the used list
+            LinkedList2_Remove(&o->mac_entries_used, &entry->list_node);
+            LinkedList2_Append(&o->mac_entries_used, &entry->list_node);
+            return;
+        }
+        
+        // some other peer has that MAC; disassociate it
+        BAVL_Remove(&d->macs_tree, &entry->tree_node);
+        LinkedList2_Remove(&entry->peer->mac_entries_used, &entry->list_node);
+        LinkedList2_Append(&entry->peer->mac_entries_free, &entry->list_node);
+    }
+    
+    // aquire MAC address entry, if there are no free ones reuse the oldest used one
+    LinkedList2Node *list_node;
+    struct _FrameDecider_mac_entry *entry;
+    if (list_node = LinkedList2_GetFirst(&o->mac_entries_free)) {
+        entry = UPPER_OBJECT(list_node, struct _FrameDecider_mac_entry, list_node);
+        ASSERT(entry->peer == o)
+        
+        // remove from free
+        LinkedList2_Remove(&o->mac_entries_free, &entry->list_node);
+    } else {
+        list_node = LinkedList2_GetFirst(&o->mac_entries_used);
+        ASSERT(list_node)
+        entry = UPPER_OBJECT(list_node, struct _FrameDecider_mac_entry, list_node);
+        ASSERT(entry->peer == o)
+        
+        // remove from used
+        BAVL_Remove(&d->macs_tree, &entry->tree_node);
+        LinkedList2_Remove(&o->mac_entries_used, &entry->list_node);
+    }
+    
+    BLog(BLOG_INFO, "adding MAC %02"PRIx8":%02"PRIx8":%02"PRIx8":%02"PRIx8":%02"PRIx8":%02"PRIx8"", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+    
+    // set MAC in entry
+    memcpy(entry->mac, mac, sizeof(entry->mac));
+    
+    // add to used
+    LinkedList2_Append(&o->mac_entries_used, &entry->list_node);
+    ASSERT_EXECUTE(BAVL_Insert(&d->macs_tree, &entry->tree_node, NULL))
+}
+
+static uint32_t compute_sig_for_group (uint32_t group)
+{
+    return hton32(ntoh32(group)&0x7FFFFF);
+}
+
+static uint32_t compute_sig_for_mac (uint8_t *mac)
+{
+    uint32_t sig;
+    memcpy(&sig, mac + 2, 4);
+    sig = hton32(ntoh32(sig)&0x7FFFFF);
+    return sig;
+}
+
+static void add_to_multicast (FrameDecider *d, struct _FrameDecider_group_entry *group_entry)
+{
+    // compute sig
+    uint32_t sig = compute_sig_for_group(group_entry->group);
+    
+    BAVLNode *node;
+    if (node = BAVL_LookupExact(&d->multicast_tree, &sig)) {
+        // use existing master
+        struct _FrameDecider_group_entry *master = UPPER_OBJECT(node, struct _FrameDecider_group_entry, master.tree_node);
+        ASSERT(master->is_master)
+        
+        // set not master
+        group_entry->is_master = 0;
+        
+        // insert to list
+        LinkedList3Node_InitAfter(&group_entry->sig_list_node, &master->sig_list_node);
+    } else {
+        // make this entry master
+        
+        // set master
+        group_entry->is_master = 1;
+        
+        // set sig
+        group_entry->master.sig = sig;
+        
+        // insert to multicast tree
+        ASSERT_EXECUTE(BAVL_Insert(&d->multicast_tree, &group_entry->master.tree_node, NULL))
+        
+        // init list node
+        LinkedList3Node_InitLonely(&group_entry->sig_list_node);
+    }
+}
+
+static void remove_from_multicast (FrameDecider *d, struct _FrameDecider_group_entry *group_entry)
+{
+    // compute sig
+    uint32_t sig = compute_sig_for_group(group_entry->group);
+    
+    if (group_entry->is_master) {
+        // remove master from multicast tree
+        BAVL_Remove(&d->multicast_tree, &group_entry->master.tree_node);
+        
+        if (!LinkedList3Node_IsLonely(&group_entry->sig_list_node)) {
+            // at least one more group entry for this sig; make another entry the master
+            
+            // get an entry
+            LinkedList3Node *list_node = LinkedList3Node_NextOrPrev(&group_entry->sig_list_node);
+            struct _FrameDecider_group_entry *newmaster = UPPER_OBJECT(list_node, struct _FrameDecider_group_entry, sig_list_node);
+            ASSERT(!newmaster->is_master)
+            
+            // set master
+            newmaster->is_master = 1;
+            
+            // set sig
+            newmaster->master.sig = sig;
+            
+            // insert to multicast tree
+            ASSERT_EXECUTE(BAVL_Insert(&d->multicast_tree, &newmaster->master.tree_node, NULL))
+        }
+    }
+    
+    // free linked list node
+    LinkedList3Node_Free(&group_entry->sig_list_node);
+}
+
+static void add_group_to_peer (FrameDeciderPeer *o, uint32_t group)
+{
+    FrameDecider *d = o->d;
+    
+    struct _FrameDecider_group_entry *group_entry;
+    
+    // lookup if the peer already has that group
+    BAVLNode *old_tree_node;
+    if ((old_tree_node = BAVL_LookupExact(&o->groups_tree, &group))) {
+        group_entry = UPPER_OBJECT(old_tree_node, struct _FrameDecider_group_entry, tree_node);
+        
+        // move to end of used list
+        LinkedList2_Remove(&o->group_entries_used, &group_entry->list_node);
+        LinkedList2_Append(&o->group_entries_used, &group_entry->list_node);
+    } else {
+        BLog(BLOG_INFO, "joined group %"PRIu8".%"PRIu8".%"PRIu8".%"PRIu8"",
+            ((uint8_t *)&group)[0], ((uint8_t *)&group)[1], ((uint8_t *)&group)[2], ((uint8_t *)&group)[3]
+        );
+        
+        // aquire group entry, if there are no free ones reuse the earliest used one
+        LinkedList2Node *node;
+        if (node = LinkedList2_GetFirst(&o->group_entries_free)) {
+            group_entry = UPPER_OBJECT(node, struct _FrameDecider_group_entry, list_node);
+            
+            // remove from free list
+            LinkedList2_Remove(&o->group_entries_free, &group_entry->list_node);
+        } else {
+            node = LinkedList2_GetFirst(&o->group_entries_used);
+            ASSERT(node)
+            group_entry = UPPER_OBJECT(node, struct _FrameDecider_group_entry, list_node);
+            
+            // remove from multicast
+            remove_from_multicast(d, group_entry);
+            
+            // remove from peer's groups tree
+            BAVL_Remove(&o->groups_tree, &group_entry->tree_node);
+            
+            // remove from used list
+            LinkedList2_Remove(&o->group_entries_used, &group_entry->list_node);
+        }
+        
+        // add entry to used list
+        LinkedList2_Append(&o->group_entries_used, &group_entry->list_node);
+        
+        // set group address
+        group_entry->group = group;
+        
+        // insert to peer's groups tree
+        ASSERT_EXECUTE(BAVL_Insert(&o->groups_tree, &group_entry->tree_node, NULL))
+        
+        // add to multicast
+        add_to_multicast(d, group_entry);
+    }
+    
+    // set timer
+    group_entry->timer_endtime = btime_gettime() + d->igmp_group_membership_interval;
+    BReactor_SetTimerAbsolute(d->reactor, &group_entry->timer, group_entry->timer_endtime);
+}
+
+static void remove_group_entry (struct _FrameDecider_group_entry *group_entry)
+{
+    FrameDeciderPeer *peer = group_entry->peer;
+    FrameDecider *d = peer->d;
+    
+    uint32_t group = group_entry->group;
+    
+    BLog(BLOG_INFO, "left group %"PRIu8".%"PRIu8".%"PRIu8".%"PRIu8"",
+        ((uint8_t *)&group)[0], ((uint8_t *)&group)[1], ((uint8_t *)&group)[2], ((uint8_t *)&group)[3]
+    );
+    
+    // remove from multicast
+    remove_from_multicast(d, group_entry);
+    
+    // remove from peer's groups tree
+    BAVL_Remove(&peer->groups_tree, &group_entry->tree_node);
+    
+    // remove from used list
+    LinkedList2_Remove(&peer->group_entries_used, &group_entry->list_node);
+    
+    // add to free list
+    LinkedList2_Append(&peer->group_entries_free, &group_entry->list_node);
+    
+    // stop timer
+    BReactor_RemoveTimer(d->reactor, &group_entry->timer);
+}
+
+static void lower_group_timers_to_lmqt (FrameDecider *d, uint32_t group)
+{
+    // have to lower all the group timers of this group down to LMQT
+    
+    // compute sig
+    uint32_t sig = compute_sig_for_group(group);
+    
+    // look up the sig in multicast tree
+    BAVLNode *tree_node;
+    if (!(tree_node = BAVL_LookupExact(&d->multicast_tree, &sig))) {
+        return;
+    }
+    struct _FrameDecider_group_entry *master = UPPER_OBJECT(tree_node, struct _FrameDecider_group_entry, master.tree_node);
+    ASSERT(master->is_master)
+    
+    // iterate all group entries with this sig
+    LinkedList3Iterator it;
+    LinkedList3Iterator_Init(&it, LinkedList3Node_First(&master->sig_list_node), 1);
+    LinkedList3Node *sig_list_node;
+    while (sig_list_node = LinkedList3Iterator_Next(&it)) {
+        struct _FrameDecider_group_entry *group_entry = UPPER_OBJECT(sig_list_node, struct _FrameDecider_group_entry, sig_list_node);
+        
+        // skip wrong groups
+        if (group_entry->group != group) {
+            continue;
+        }
+        
+        // lower timer down to LMQT
+        btime_t now = btime_gettime();
+        if (group_entry->timer_endtime > now + d->igmp_last_member_query_time) {
+            group_entry->timer_endtime = now + d->igmp_last_member_query_time;
+            BReactor_SetTimerAbsolute(d->reactor, &group_entry->timer, group_entry->timer_endtime);
+        }
+    }
+}
+
+static int check_ipv4_packet (uint8_t *data, int data_len, struct ipv4_header **out_header, uint8_t **out_payload, int *out_payload_len)
+{
+    // check base header
+    if (data_len < sizeof(struct ipv4_header)) {
+        BLog(BLOG_DEBUG, "check ipv4: packet too short (base header)");
+        return 0;
+    }
+    struct ipv4_header *header = (struct ipv4_header *)data;
+    
+    // check version
+    if (IPV4_GET_VERSION(*header) != 4) {
+        BLog(BLOG_DEBUG, "check ipv4: version not 4");
+        return 0;
+    }
+    
+    // check options
+    int header_len = IPV4_GET_IHL(*header) * 4;
+    if (header_len < sizeof(struct ipv4_header)) {
+        BLog(BLOG_DEBUG, "check ipv4: ihl too small");
+        return 0;
+    }
+    if (header_len > data_len) {
+        BLog(BLOG_DEBUG, "check ipv4: packet too short for ihl");
+        return 0;
+    }
+    
+    // check total length
+    uint16_t total_length = ntoh16(header->total_length);
+    if (total_length < header_len) {
+        BLog(BLOG_DEBUG, "check ipv4: total length too small");
+        return 0;
+    }
+    if (total_length > data_len) {
+        BLog(BLOG_DEBUG, "check ipv4: total length too large");
+        return 0;
+    }
+    
+    *out_header = header;
+    *out_payload = data + header_len;
+    *out_payload_len = total_length - header_len;
+    
+    return 1;
+}
+
+void FrameDecider_Init (FrameDecider *o, int max_peer_macs, int max_peer_groups, btime_t igmp_group_membership_interval, btime_t igmp_last_member_query_time, BReactor *reactor)
+{
+    ASSERT(max_peer_macs > 0)
+    ASSERT(max_peer_groups > 0)
+    
+    // init arguments
+    o->max_peer_macs = max_peer_macs;
+    o->max_peer_groups = max_peer_groups;
+    o->igmp_group_membership_interval = igmp_group_membership_interval;
+    o->igmp_last_member_query_time = igmp_last_member_query_time;
+    o->reactor = reactor;
+    
+    // init peers list
+    LinkedList2_Init(&o->peers_list);
+    
+    // init MAC tree
+    BAVL_Init(&o->macs_tree, OFFSET_DIFF(struct _FrameDecider_mac_entry, mac, tree_node), (BAVL_comparator)mac_comparator, NULL);
+    
+    // init multicast tree
+    BAVL_Init(&o->multicast_tree, OFFSET_DIFF(struct _FrameDecider_group_entry, master.sig, master.tree_node), (BAVL_comparator)uint32_comparator, NULL);
+    
+    // init decide state
+    o->decide_state = DECIDE_STATE_NONE;
+    
+    DebugObject_Init(&o->d_obj);
+}
+
+void FrameDecider_Free (FrameDecider *o)
+{
+    ASSERT(BAVL_IsEmpty(&o->multicast_tree))
+    ASSERT(BAVL_IsEmpty(&o->macs_tree))
+    ASSERT(LinkedList2_IsEmpty(&o->peers_list))
+    DebugObject_Free(&o->d_obj);
+}
+
+void FrameDecider_AnalyzeAndDecide (FrameDecider *o, uint8_t *frame, int frame_len)
+{
+    ASSERT(frame_len >= 0)
+    DebugObject_Access(&o->d_obj);
+    
+    // reset decide state
+    switch (o->decide_state) {
+        case DECIDE_STATE_NONE:
+            break;
+        case DECIDE_STATE_UNICAST:
+            break;
+        case DECIDE_STATE_FLOOD:
+            LinkedList2Iterator_Free(&o->decide_flood_it);
+            break;
+        case DECIDE_STATE_MULTICAST:
+            LinkedList3Iterator_Free(&o->decide_multicast_it);
+            return;
+        default:
+            ASSERT(0);
+    }
+    o->decide_state = DECIDE_STATE_NONE;
+    
+    // analyze frame
+    
+    uint8_t *pos = frame;
+    int len = frame_len;
+    
+    if (len < sizeof(struct ethernet_header)) {
+        return;
+    }
+    struct ethernet_header *eh = (struct ethernet_header *)pos;
+    pos += sizeof(struct ethernet_header);
+    len -= sizeof(struct ethernet_header);
+    
+    int is_igmp = 0;
+    
+    switch (ntoh16(eh->type)) {
+        case ETHERTYPE_IPV4: {
+            // check IPv4 header
+            struct ipv4_header *ipv4_header;
+            if (!check_ipv4_packet(pos, len, &ipv4_header, &pos, &len)) {
+                BLog(BLOG_INFO, "decide: wrong IP packet");
+                goto out;
+            }
+            
+            // check if it's IGMP
+            if (ntoh8(ipv4_header->protocol) != IPV4_PROTOCOL_IGMP) {
+                goto out;
+            }
+            
+            // remember that it's IGMP; we have to flood IGMP frames
+            is_igmp = 1;
+            
+            // check IGMP header
+            if (len < sizeof(struct igmp_base)) {
+                BLog(BLOG_INFO, "decide: IGMP: short packet");
+                goto out;
+            }
+            struct igmp_base *igmp_base = (struct igmp_base *)pos;
+            pos += sizeof(struct igmp_base);
+            len -= sizeof(struct igmp_base);
+            
+            switch (ntoh8(igmp_base->type)) {
+                case IGMP_TYPE_MEMBERSHIP_QUERY: {
+                    if (len == sizeof(struct igmp_v2_extra) && ntoh8(igmp_base->max_resp_code) != 0) {
+                        // V2 query
+                        struct igmp_v2_extra *query = (struct igmp_v2_extra *)pos;
+                        pos += sizeof(struct igmp_v2_extra);
+                        len -= sizeof(struct igmp_v2_extra);
+                        
+                        if (ntoh32(query->group) != 0) {
+                            // got a Group-Specific Query, lower group timers to LMQT
+                            lower_group_timers_to_lmqt(o, query->group);
+                        }
+                    }
+                    else if (len >= sizeof(struct igmp_v3_query_extra)) {
+                        // V3 query
+                        struct igmp_v3_query_extra *query = (struct igmp_v3_query_extra *)pos;
+                        pos += sizeof(struct igmp_v3_query_extra);
+                        len -= sizeof(struct igmp_v3_query_extra);
+                        
+                        // iterate sources
+                        uint16_t num_sources = ntoh16(query->number_of_sources);
+                        int i;
+                        for (i = 0; i < num_sources; i++) {
+                            // check source
+                            if (len < sizeof(struct igmp_source)) {
+                                BLog(BLOG_NOTICE, "decide: IGMP: short source");
+                                goto out;
+                            }
+                            pos += sizeof(struct igmp_source);
+                            len -= sizeof(struct igmp_source);
+                        }
+                        
+                        if (ntoh32(query->group) != 0 && num_sources == 0) {
+                            // got a Group-Specific Query, lower group timers to LMQT
+                            lower_group_timers_to_lmqt(o, query->group);
+                        }
+                    }
+                } break;
+            }
+        } break;
+    }
+    
+out:;
+    
+    const uint8_t broadcast_mac[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+    const uint8_t multicast_mac_header[] = {0x01, 0x00, 0x5e};
+    
+    // if it's broadcast or IGMP, flood it
+    if (is_igmp || !memcmp(eh->dest, broadcast_mac, sizeof(broadcast_mac))) {
+        o->decide_state = DECIDE_STATE_FLOOD;
+        LinkedList2Iterator_InitForward(&o->decide_flood_it, &o->peers_list);
+        return;
+    }
+    
+    // if it's multicast, forward to all peers with the given sig
+    if (!memcmp(eh->dest, multicast_mac_header, sizeof(multicast_mac_header))) {
+        // extract group's sig from destination MAC
+        uint32_t sig = compute_sig_for_mac(eh->dest);
+        
+        // look up the sig in multicast tree
+        BAVLNode *node;
+        if (node = BAVL_LookupExact(&o->multicast_tree, &sig)) {
+            struct _FrameDecider_group_entry *master = UPPER_OBJECT(node, struct _FrameDecider_group_entry, master.tree_node);
+            ASSERT(master->is_master)
+            
+            o->decide_state = DECIDE_STATE_MULTICAST;
+            LinkedList3Iterator_Init(&o->decide_multicast_it, LinkedList3Node_First(&master->sig_list_node), 1);
+        }
+        
+        return;
+    }
+    
+    // look for MAC entry
+    BAVLNode *tree_node;
+    if (tree_node = BAVL_LookupExact(&o->macs_tree, eh->dest)) {
+        struct _FrameDecider_mac_entry *entry = UPPER_OBJECT(tree_node, struct _FrameDecider_mac_entry, tree_node);
+        o->decide_state = DECIDE_STATE_UNICAST;
+        o->decide_unicast_peer = entry->peer;
+        return;
+    }
+    
+    // unknown destination MAC, flood
+    o->decide_state = DECIDE_STATE_FLOOD;
+    LinkedList2Iterator_InitForward(&o->decide_flood_it, &o->peers_list);
+    return;
+}
+
+FrameDeciderPeer * FrameDecider_NextDestination (FrameDecider *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    switch (o->decide_state) {
+        case DECIDE_STATE_NONE: {
+            return NULL;
+        } break;
+            
+        case DECIDE_STATE_UNICAST: {
+            o->decide_state = DECIDE_STATE_NONE;
+            
+            return o->decide_unicast_peer;
+        } break;
+        
+        case DECIDE_STATE_FLOOD: {
+            LinkedList2Node *list_node = LinkedList2Iterator_Next(&o->decide_flood_it);
+            if (!list_node) {
+                o->decide_state = DECIDE_STATE_NONE;
+                return NULL;
+            }
+            FrameDeciderPeer *peer = UPPER_OBJECT(list_node, FrameDeciderPeer, list_node);
+            
+            return peer;
+        } break;
+        
+        case DECIDE_STATE_MULTICAST: {
+            LinkedList3Node *list_node = LinkedList3Iterator_Next(&o->decide_multicast_it);
+            if (!list_node) {
+                o->decide_state = DECIDE_STATE_NONE;
+                return NULL;
+            }
+            struct _FrameDecider_group_entry *group_entry = UPPER_OBJECT(list_node, struct _FrameDecider_group_entry, sig_list_node);
+            
+            return group_entry->peer;
+        } break;
+        
+        default:
+            ASSERT(0);
+    }
+}
+
+static void group_entry_timer_handler (struct _FrameDecider_group_entry *group_entry)
+{
+    remove_group_entry(group_entry);
+}
+
+int FrameDeciderPeer_Init (FrameDeciderPeer *o, FrameDecider *d)
+{
+    // init arguments
+    o->d = d;
+    
+    // allocate MAC entries
+    if (!(o->mac_entries = BAllocArray(d->max_peer_macs, sizeof(struct _FrameDecider_mac_entry)))) {
+        DEBUG("failed to allocate MAC entries");
+        goto fail0;
+    }
+    
+    // allocate group entries
+    if (!(o->group_entries = BAllocArray(d->max_peer_groups, sizeof(struct _FrameDecider_group_entry)))) {
+        DEBUG("failed to allocate group entries");
+        goto fail1;
+    }
+    
+    // insert to peers list
+    LinkedList2_Append(&d->peers_list, &o->list_node);
+    
+    // init MAC entry lists
+    LinkedList2_Init(&o->mac_entries_free);
+    LinkedList2_Init(&o->mac_entries_used);
+    
+    // initialize MAC entries
+    for (int i = 0; i < d->max_peer_macs; i++) {
+        struct _FrameDecider_mac_entry *entry = &o->mac_entries[i];
+        
+        // set peer
+        entry->peer = o;
+        
+        // insert to free list
+        LinkedList2_Append(&o->mac_entries_free, &entry->list_node);
+    }
+    
+    // init group entry lists
+    LinkedList2_Init(&o->group_entries_free);
+    LinkedList2_Init(&o->group_entries_used);
+    
+    // initialize group entries
+    for (int i = 0; i < d->max_peer_groups; i++) {
+        struct _FrameDecider_group_entry *entry = &o->group_entries[i];
+        
+        // set peer
+        entry->peer = o;
+        
+        // insert to free list
+        LinkedList2_Append(&o->group_entries_free, &entry->list_node);
+        
+        // init timer
+        BTimer_Init(&entry->timer, 0, (BTimer_handler)group_entry_timer_handler, entry);
+    }
+    
+    // initialize groups tree
+    BAVL_Init(&o->groups_tree, OFFSET_DIFF(struct _FrameDecider_group_entry, group, tree_node), (BAVL_comparator)uint32_comparator, NULL);
+    
+    DebugObject_Init(&o->d_obj);
+    
+    return 1;
+    
+fail1:
+    BFree(o->mac_entries);
+fail0:
+    return 0;
+}
+
+void FrameDeciderPeer_Free (FrameDeciderPeer *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    FrameDecider *d = o->d;
+    
+    // remove decide unicast reference
+    if (d->decide_state == DECIDE_STATE_UNICAST && d->decide_unicast_peer == o) {
+        d->decide_state = DECIDE_STATE_NONE;
+    }
+    
+    LinkedList2Iterator it;
+    LinkedList2Node *node;
+    
+    // free group entries
+    LinkedList2Iterator_InitForward(&it, &o->group_entries_used);
+    while (node = LinkedList2Iterator_Next(&it)) {
+        struct _FrameDecider_group_entry *entry = UPPER_OBJECT(node, struct _FrameDecider_group_entry, list_node);
+        
+        // remove from multicast
+        remove_from_multicast(d, entry);
+        
+        // stop timer
+        BReactor_RemoveTimer(d->reactor, &entry->timer);
+    }
+    
+    // remove used MAC entries from tree
+    LinkedList2Iterator_InitForward(&it, &o->mac_entries_used);
+    while (node = LinkedList2Iterator_Next(&it)) {
+        struct _FrameDecider_mac_entry *entry = UPPER_OBJECT(node, struct _FrameDecider_mac_entry, list_node);
+        
+        // remove from tree
+        BAVL_Remove(&d->macs_tree, &entry->tree_node);
+    }
+    
+    // remove from peers list
+    LinkedList2_Remove(&d->peers_list, &o->list_node);
+    
+    // free group entries
+    BFree(o->group_entries);
+    
+    // free MAC entries
+    BFree(o->mac_entries);
+}
+
+void FrameDeciderPeer_Analyze (FrameDeciderPeer *o, uint8_t *frame, int frame_len)
+{
+    ASSERT(frame_len >= 0)
+    DebugObject_Access(&o->d_obj);
+    
+    uint8_t *pos = frame;
+    int len = frame_len;
+    
+    if (len < sizeof(struct ethernet_header)) {
+        goto out;
+    }
+    struct ethernet_header *eh = (struct ethernet_header *)pos;
+    pos += sizeof(struct ethernet_header);
+    len -= sizeof(struct ethernet_header);
+    
+    // register source MAC address with this peer
+    add_mac_to_peer(o, eh->source);
+    
+    switch (ntoh16(eh->type)) {
+        case ETHERTYPE_IPV4: {
+            // check IPv4 header
+            struct ipv4_header *ipv4_header;
+            if (!check_ipv4_packet(pos, len, &ipv4_header, &pos, &len)) {
+                BLog(BLOG_INFO, "analyze: wrong IP packet");
+                goto out;
+            }
+            
+            // check if it's IGMP
+            if (ntoh8(ipv4_header->protocol) != IPV4_PROTOCOL_IGMP) {
+                goto out;
+            }
+            
+            // check IGMP header
+            if (len < sizeof(struct igmp_base)) {
+                BLog(BLOG_INFO, "analyze: IGMP: short packet");
+                goto out;
+            }
+            struct igmp_base *igmp_base = (struct igmp_base *)pos;
+            pos += sizeof(struct igmp_base);
+            len -= sizeof(struct igmp_base);
+            
+            switch (ntoh8(igmp_base->type)) {
+                case IGMP_TYPE_V2_MEMBERSHIP_REPORT: {
+                    // check extra
+                    if (len < sizeof(struct igmp_v2_extra)) {
+                        BLog(BLOG_INFO, "analyze: IGMP: short v2 report");
+                        goto out;
+                    }
+                    struct igmp_v2_extra *report = (struct igmp_v2_extra *)pos;
+                    pos += sizeof(struct igmp_v2_extra);
+                    len -= sizeof(struct igmp_v2_extra);
+                    
+                    // add to group
+                    add_group_to_peer(o, report->group);
+                } break;
+                
+                case IGMP_TYPE_V3_MEMBERSHIP_REPORT: {
+                    // check extra
+                    if (len < sizeof(struct igmp_v3_report_extra)) {
+                        BLog(BLOG_INFO, "analyze: IGMP: short v3 report");
+                        goto out;
+                    }
+                    struct igmp_v3_report_extra *report = (struct igmp_v3_report_extra *)pos;
+                    pos += sizeof(struct igmp_v3_report_extra);
+                    len -= sizeof(struct igmp_v3_report_extra);
+                    
+                    // iterate records
+                    uint16_t num_records = ntoh16(report->number_of_group_records);
+                    for (int i = 0; i < num_records; i++) {
+                        // check record
+                        if (len < sizeof(struct igmp_v3_report_record)) {
+                            BLog(BLOG_INFO, "analyze: IGMP: short record header");
+                            goto out;
+                        }
+                        struct igmp_v3_report_record *record = (struct igmp_v3_report_record *)pos;
+                        pos += sizeof(struct igmp_v3_report_record);
+                        len -= sizeof(struct igmp_v3_report_record);
+                        
+                        // iterate sources
+                        uint16_t num_sources = ntoh16(record->number_of_sources);
+                        int j;
+                        for (j = 0; j < num_sources; j++) {
+                            // check source
+                            if (len < sizeof(struct igmp_source)) {
+                                BLog(BLOG_INFO, "analyze: IGMP: short source");
+                                goto out;
+                            }
+                            struct igmp_source *source = (struct igmp_source *)pos;
+                            pos += sizeof(struct igmp_source);
+                            len -= sizeof(struct igmp_source);
+                        }
+                        
+                        // check aux data
+                        uint16_t aux_len = ntoh16(record->aux_data_len);
+                        if (len < aux_len) {
+                            BLog(BLOG_INFO, "analyze: IGMP: short record aux data");
+                            goto out;
+                        }
+                        pos += aux_len;
+                        len -= aux_len;
+                        
+                        switch (record->type) {
+                            case IGMP_RECORD_TYPE_MODE_IS_INCLUDE:
+                            case IGMP_RECORD_TYPE_CHANGE_TO_INCLUDE_MODE:
+                                if (num_sources != 0) {
+                                    add_group_to_peer(o, record->group);
+                                }
+                                break;
+                            case IGMP_RECORD_TYPE_MODE_IS_EXCLUDE:
+                            case IGMP_RECORD_TYPE_CHANGE_TO_EXCLUDE_MODE:
+                                add_group_to_peer(o, record->group);
+                                break;
+                        }
+                    }
+                } break;
+            }
+        } break;
+    }
+    
+out:;
+}

+ 172 - 0
client/FrameDecider.h

@@ -0,0 +1,172 @@
+/**
+ * @file FrameDecider.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * This file is part of BadVPN.
+ * 
+ * BadVPN is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ * 
+ * BadVPN 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * 
+ * @section DESCRIPTION
+ * 
+ * Mudule which decides to which peers frames from the device are to be
+ * forwarded.
+ */
+
+#ifndef BADVPN_CLIENT_FRAMEDECIDER_H
+#define BADVPN_CLIENT_FRAMEDECIDER_H
+
+#include <stdint.h>
+
+#include <structure/BAVL.h>
+#include <structure/LinkedList2.h>
+#include <structure/LinkedList3.h>
+#include <system/DebugObject.h>
+#include <system/BReactor.h>
+
+struct _FrameDeciderPeer;
+struct _FrameDecider_mac_entry;
+
+/**
+ * Object that represents a local device.
+ */
+typedef struct {
+    int max_peer_macs;
+    int max_peer_groups;
+    btime_t igmp_group_membership_interval;
+    btime_t igmp_last_member_query_time;
+    BReactor *reactor;
+    LinkedList2 peers_list;
+    BAVL macs_tree;
+    BAVL multicast_tree;
+    int decide_state;
+    LinkedList2Iterator decide_flood_it;
+    struct _FrameDeciderPeer *decide_unicast_peer;
+    LinkedList3Iterator decide_multicast_it;
+    DebugObject d_obj;
+} FrameDecider;
+
+/**
+ * Object that represents a peer that a local device can send frames to.
+ */
+typedef struct _FrameDeciderPeer {
+    FrameDecider *d;
+    struct _FrameDecider_mac_entry *mac_entries;
+    struct _FrameDecider_group_entry *group_entries;
+    LinkedList2Node list_node; // node in FrameDecider.peers_list
+    LinkedList2 mac_entries_free;
+    LinkedList2 mac_entries_used;
+    LinkedList2 group_entries_free;
+    LinkedList2 group_entries_used;
+    BAVL groups_tree;
+    DebugObject d_obj;
+} FrameDeciderPeer;
+
+struct _FrameDecider_mac_entry {
+    FrameDeciderPeer *peer;
+    LinkedList2Node list_node; // node in FrameDeciderPeer.mac_entries_free or FrameDeciderPeer.mac_entries_used
+    // defined when used:
+    uint8_t mac[6];
+    BAVLNode tree_node; // node in FrameDecider.macs_tree, indexed by mac
+};
+
+struct _FrameDecider_group_entry {
+    FrameDeciderPeer *peer;
+    LinkedList2Node list_node; // node in FrameDeciderPeer.group_entries_free or FrameDeciderPeer.group_entries_used
+    BTimer timer; // timer for removing the group entry, running when used
+    // defined when used:
+    // basic group data
+    uint32_t group; // group address
+    BAVLNode tree_node; // node in FrameDeciderPeer.groups_tree, indexed by group
+    // all that folows is managed by add_to_multicast() and remove_from_multicast()
+    LinkedList3Node sig_list_node; // node in list of group entries with the same sig
+    btime_t timer_endtime;
+    int is_master;
+    // defined when used and we are master:
+    struct {
+        uint32_t sig; // last 23 bits of group address
+        BAVLNode tree_node; // node in FrameDecider.multicast_tree, indexed by sig
+    } master;
+};
+
+/**
+ * Initializes the object.
+ * 
+ * @param o the object
+ * @param max_peer_macs maximum number of MAC addresses a peer may posess. Must be >0.
+ * @param max_peer_groups maximum number of multicast groups a peer may belong to. Must be >0.
+ * @param igmp_group_membership_interval IGMP Group Membership Interval value. When a join
+ *        is detected for a peer in {@link FrameDeciderPeer_Analyze}, this is how long we wait
+ *        for another join before we remove the group from the peer. Note that the group may
+ *        be removed sooner if the peer fails to respond to a Group-Specific Query (see below).
+ * @param igmp_last_member_query_time IGMP Last Member Query Time value. When a Group-Specific
+ *        Query is detected in {@link FrameDecider_AnalyzeAndDecide}, this is how long we wait for a peer
+ *        belonging to the group to send a join before we remove the group from it.
+ */
+void FrameDecider_Init (FrameDecider *o, int max_peer_macs, int max_peer_groups, btime_t igmp_group_membership_interval, btime_t igmp_last_member_query_time, BReactor *reactor);
+
+/**
+ * Frees the object.
+ * There must be no {@link FrameDeciderPeer} objects using this decider.
+ * 
+ * @param o the object
+ */
+void FrameDecider_Free (FrameDecider *o);
+
+/**
+ * Analyzes a frame read from the local device and starts deciding which peers
+ * the frame should be forwarded to.
+ * 
+ * @param o the object
+ * @param frame frame data
+ * @param frame_len frame length. Must be >=0.
+ */
+void FrameDecider_AnalyzeAndDecide (FrameDecider *o, uint8_t *frame, int frame_len);
+
+/**
+ * Returns the next peer that the frame submitted to {@link FrameDecider_AnalyzeAndDecide} should be
+ * forwarded to.
+ * 
+ * @param o the object
+ * @return peer to forward the frame to, or NULL if no more
+ */
+FrameDeciderPeer * FrameDecider_NextDestination (FrameDecider *o);
+
+/**
+ * Initializes the object.
+ * 
+ * @param o the object
+ * @param d decider this peer will belong to
+ * @return 1 on success, 0 on failure
+ */
+int FrameDeciderPeer_Init (FrameDeciderPeer *o, FrameDecider *d) WARN_UNUSED;
+
+/**
+ * Frees the object.
+ * 
+ * @param o the object
+ */
+void FrameDeciderPeer_Free (FrameDeciderPeer *o);
+
+/**
+ * Analyzes a frame received from the peer.
+ * 
+ * @param o the object
+ * @param frame frame data
+ * @param frame_len frame length. Must be >=0.
+ */
+void FrameDeciderPeer_Analyze (FrameDeciderPeer *o, uint8_t *frame, int frame_len);
+
+#endif

+ 21 - 682
client/client.c

@@ -46,8 +46,6 @@
 #include <misc/jenkins_hash.h>
 #include <misc/jenkins_hash.h>
 #include <misc/byteorder.h>
 #include <misc/byteorder.h>
 #include <misc/ethernet_proto.h>
 #include <misc/ethernet_proto.h>
-#include <misc/ipv4_proto.h>
-#include <misc/igmp_proto.h>
 #include <misc/nsskey.h>
 #include <misc/nsskey.h>
 #include <misc/loglevel.h>
 #include <misc/loglevel.h>
 #include <misc/loggers_string.h>
 #include <misc/loggers_string.h>
@@ -167,17 +165,8 @@ int num_peers;
 HashTable peers_by_id;
 HashTable peers_by_id;
 uint32_t peers_by_id_initval;
 uint32_t peers_by_id_initval;
 
 
-// MAC addresses hash table
-HashTable mac_table;
-uint32_t mac_table_initval;
-
-// multicast MAC address hash table
-HashTable multicast_table;
-uint32_t multicast_table_initval;
-
-// multicast entries
-LinkedList2 multicast_entries_free;
-struct multicast_table_entry multicast_entries_data[MAX_PEERS*PEER_MAX_GROUPS];
+// frame decider
+FrameDecider frame_decider;
 
 
 // peers that can be user as relays
 // peers that can be user as relays
 LinkedList2 relays;
 LinkedList2 relays;
@@ -275,15 +264,6 @@ static void peer_unregister_need_relay (struct peer_data *peer);
 // handle a link setup failure
 // handle a link setup failure
 static int peer_reset (struct peer_data *peer);
 static int peer_reset (struct peer_data *peer);
 
 
-// associates a MAC address with a peer
-static void peer_add_mac_address (struct peer_data *peer, uint8_t *mac);
-
-// associate an IPv4 multicast address with a peer
-static void peer_join_group (struct peer_data *peer, uint32_t group);
-
-// disassociate an IPv4 multicast address from a peer
-static void peer_leave_group (struct peer_data *peer, uint32_t group);
-
 // handle incoming peer messages
 // handle incoming peer messages
 static void peer_msg (struct peer_data *peer, uint8_t *data, int data_len);
 static void peer_msg (struct peer_data *peer, uint8_t *data, int data_len);
 
 
@@ -332,26 +312,13 @@ static int peer_send_confirmseed (struct peer_data *peer, uint16_t seed_id);
 // submits a relayed frame for sending to the peer
 // submits a relayed frame for sending to the peer
 static void peer_submit_relayed_frame (struct peer_data *peer, struct peer_data *source_peer, uint8_t *frame, int frame_len);
 static void peer_submit_relayed_frame (struct peer_data *peer, struct peer_data *source_peer, uint8_t *frame, int frame_len);
 
 
-// handler for group timers
-static void peer_group_timer_handler (struct peer_group_entry *entry);
-
 // handler for peer DataProto up state changes
 // handler for peer DataProto up state changes
 static void peer_dataproto_handler (struct peer_data *peer, int up);
 static void peer_dataproto_handler (struct peer_data *peer, int up);
 
 
 // looks for a peer with the given ID
 // looks for a peer with the given ID
 static struct peer_data * find_peer_by_id (peerid_t id);
 static struct peer_data * find_peer_by_id (peerid_t id);
 
 
-// multicast table operations
-static void multicast_table_add_entry (struct peer_group_entry *entry);
-static void multicast_table_remove_entry (struct peer_group_entry *entry);
-
 // hash table callback functions
 // hash table callback functions
-static int peer_groups_table_key_comparator (uint32_t *group1, uint32_t *group2);
-static int peer_groups_table_hash_function (uint32_t *group, int modulo);
-static int mac_table_key_comparator (uint8_t *mac1, uint8_t *mac2);
-static int mac_table_hash_function (uint8_t *mac, int modulo);
-static int multicast_table_key_comparator (uint32_t *sig1, uint32_t *sig2);
-static int multicast_table_hash_function (uint32_t *sig, int modulo);
 static int peers_by_id_key_comparator (peerid_t *id1, peerid_t *id2);
 static int peers_by_id_key_comparator (peerid_t *id1, peerid_t *id2);
 static int peers_by_id_hash_function (peerid_t *id, int modulo);
 static int peers_by_id_hash_function (peerid_t *id, int modulo);
 
 
@@ -364,25 +331,6 @@ static void device_input_handler_send (void *unused, uint8_t *data, int data_len
 // submits a local frame for sending to the peer. The frame is taken from the device frame buffer.
 // submits a local frame for sending to the peer. The frame is taken from the device frame buffer.
 static void submit_frame_to_peer (struct peer_data *peer, uint8_t *data, int data_len);
 static void submit_frame_to_peer (struct peer_data *peer, uint8_t *data, int data_len);
 
 
-// submits the current frame to all peers
-static void flood_frame (uint8_t *data, int data_len);
-
-// inspects a frame read from the device and determines how
-// it should be handled. Used for IGMP snooping.
-static int hook_outgoing (uint8_t *pos, int len);
-
-#define HOOK_OUT_DEFAULT 0
-#define HOOK_OUT_FLOOD 1
-
-// inpects an incoming frame. Used for IGMP snooping.
-static void peer_hook_incoming (struct peer_data *peer, uint8_t *pos, int len);
-
-// lowers every group entry timer to IGMP_LAST_MEMBER_QUERY_TIME if it's larger
-static void lower_group_timers_to_lmqt (uint32_t group);
-
-// check an IPv4 packet
-static int check_ipv4_packet (uint8_t *data, int data_len, struct ipv4_header **out_header, uint8_t **out_payload, int *out_payload_len);
-
 // assign relays to clients waiting for them
 // assign relays to clients waiting for them
 static void assign_relays (void);
 static void assign_relays (void);
 
 
@@ -580,39 +528,8 @@ int main (int argc, char *argv[])
         goto fail7;
         goto fail7;
     }
     }
     
     
-    // init MAC address table
-    BRandom_randomize((uint8_t *)&mac_table_initval, sizeof(mac_table_initval));
-    if (!HashTable_Init(
-        &mac_table,
-        OFFSET_DIFF(struct mac_table_entry, mac, table_node),
-        (HashTable_comparator)mac_table_key_comparator,
-        (HashTable_hash_function)mac_table_hash_function,
-        MAX_PEERS * PEER_MAX_MACS
-    )) {
-        BLog(BLOG_ERROR, "HashTable_Init failed");
-        goto fail8;
-    }
-    
-    // init multicast MAC address table
-    BRandom_randomize((uint8_t *)&multicast_table_initval, sizeof(multicast_table_initval));
-    if (!HashTable_Init(
-        &multicast_table,
-        OFFSET_DIFF(struct multicast_table_entry, sig, table_node),
-        (HashTable_comparator)multicast_table_key_comparator,
-        (HashTable_hash_function)multicast_table_hash_function,
-        MAX_PEERS * PEER_MAX_GROUPS
-    )) {
-        BLog(BLOG_ERROR, "HashTable_Init failed");
-        goto fail9;
-    }
-    
-    // init multicast entries
-    LinkedList2_Init(&multicast_entries_free);
-    int i;
-    for (i = 0; i < MAX_PEERS * PEER_MAX_GROUPS; i++) {
-        struct multicast_table_entry *multicast_entry = &multicast_entries_data[i];
-        LinkedList2_Append(&multicast_entries_free, &multicast_entry->free_list_node);
-    }
+    // init frame decider
+    FrameDecider_Init(&frame_decider, PEER_MAX_MACS, PEER_MAX_GROUPS, IGMP_GROUP_MEMBERSHIP_INTERVAL, IGMP_LAST_MEMBER_QUERY_TIME, &ss);
     
     
     // init relays list
     // init relays list
     LinkedList2_Init(&relays);
     LinkedList2_Init(&relays);
@@ -636,10 +553,7 @@ int main (int argc, char *argv[])
     
     
     // cleanup on error
     // cleanup on error
 fail10:
 fail10:
-    HashTable_Free(&multicast_table);
-fail9:
-    HashTable_Free(&mac_table);
-fail8:
+    FrameDecider_Free(&frame_decider);
     HashTable_Free(&peers_by_id);
     HashTable_Free(&peers_by_id);
 fail7:
 fail7:
     PacketPassFairQueue_Free(&device.output_queue);
     PacketPassFairQueue_Free(&device.output_queue);
@@ -732,9 +646,10 @@ void terminate (void)
     // free server
     // free server
     ServerConnection_Free(&server);
     ServerConnection_Free(&server);
     
     
+    // free frame decider
+    FrameDecider_Free(&frame_decider);
+    
     // free hash tables
     // free hash tables
-    HashTable_Free(&multicast_table);
-    HashTable_Free(&mac_table);
     HashTable_Free(&peers_by_id);
     HashTable_Free(&peers_by_id);
     
     
     // free device output
     // free device output
@@ -1428,35 +1343,8 @@ int peer_add (peerid_t id, int flags, const uint8_t *cert, int cert_len)
     // init retry timer
     // init retry timer
     BTimer_Init(&peer->reset_timer, PEER_RETRY_TIME, (BTimer_handler)peer_reset_timer_handler, peer);
     BTimer_Init(&peer->reset_timer, PEER_RETRY_TIME, (BTimer_handler)peer_reset_timer_handler, peer);
     
     
-    // init MAC lists
-    LinkedList2_Init(&peer->macs_used);
-    LinkedList2_Init(&peer->macs_free);
-    // init MAC entries and add them to the free list
-    for (int i = 0; i < PEER_MAX_MACS; i++) {
-        struct mac_table_entry *entry = &peer->macs_data[i];
-        entry->peer = peer;
-        LinkedList2_Append(&peer->macs_free, &entry->list_node);
-    }
-    
-    // init groups lists
-    LinkedList2_Init(&peer->groups_used);
-    LinkedList2_Init(&peer->groups_free);
-    // init group entries and add to unused list
-    for (int i = 0; i < PEER_MAX_GROUPS; i++) {
-        struct peer_group_entry *entry = &peer->groups_data[i];
-        entry->peer = peer;
-        LinkedList2_Append(&peer->groups_free, &entry->list_node);
-        BTimer_Init(&entry->timer, 0, (BTimer_handler)peer_group_timer_handler, entry);
-    }
-    // init groups hash table
-    if (!HashTable_Init(
-        &peer->groups_hashtable,
-        OFFSET_DIFF(struct peer_group_entry, group, table_node),
-        (HashTable_comparator)peer_groups_table_key_comparator,
-        (HashTable_hash_function)peer_groups_table_hash_function,
-        PEER_MAX_GROUPS
-    )) {
-        peer_log(peer, BLOG_ERROR, "HashTable_Init failed");
+    // init frame decider peer
+    if (!FrameDeciderPeer_Init(&peer->decider_peer, &frame_decider)) {
         goto fail5;
         goto fail5;
     }
     }
     
     
@@ -1553,23 +1441,6 @@ void peer_dealloc (struct peer_data *peer)
         peer_free_link(peer);
         peer_free_link(peer);
     }
     }
     
     
-    // free group entries
-    LinkedList2Iterator_InitForward(&it, &peer->groups_used);
-    while (node = LinkedList2Iterator_Next(&it)) {
-        struct peer_group_entry *group_entry = UPPER_OBJECT(node, struct peer_group_entry, list_node);
-        ASSERT(group_entry->peer == peer)
-        multicast_table_remove_entry(group_entry);
-        BReactor_RemoveTimer(&ss, &group_entry->timer);
-    }
-    
-    // free MAC addresses
-    LinkedList2Iterator_InitForward(&it, &peer->macs_used);
-    while (node = LinkedList2Iterator_Next(&it)) {
-        struct mac_table_entry *mac_entry = UPPER_OBJECT(node, struct mac_table_entry, list_node);
-        ASSERT(mac_entry->peer == peer)
-        ASSERT_EXECUTE(HashTable_Remove(&mac_table, mac_entry->mac))
-    }
-    
     // decrement number of peers
     // decrement number of peers
     num_peers--;
     num_peers--;
     
     
@@ -1582,8 +1453,8 @@ void peer_dealloc (struct peer_data *peer)
     // free jobs
     // free jobs
     BPending_Free(&peer->job_send_seed_after_binding);
     BPending_Free(&peer->job_send_seed_after_binding);
     
     
-    // free groups table
-    HashTable_Free(&peer->groups_hashtable);
+    // free frame decider
+    FrameDeciderPeer_Free(&peer->decider_peer);
     
     
     // free retry timer
     // free retry timer
     BReactor_RemoveTimer(&ss, &peer->reset_timer);
     BReactor_RemoveTimer(&ss, &peer->reset_timer);
@@ -1967,145 +1838,6 @@ int peer_reset (struct peer_data *peer)
     return 0;
     return 0;
 }
 }
 
 
-void peer_add_mac_address (struct peer_data *peer, uint8_t *mac)
-{
-    // check if the MAC address is already present in the global table
-    HashTableNode *old_table_node;
-    if (HashTable_Lookup(&mac_table, mac, &old_table_node)) {
-        struct mac_table_entry *old_entry = UPPER_OBJECT(old_table_node, struct mac_table_entry, table_node);
-        ASSERT(!memcmp(old_entry->mac, mac, 6))
-        
-        // if the MAC is already associated with this peer, only move it to the end of the list
-        if (old_entry->peer == peer) {
-            LinkedList2_Remove(&peer->macs_used, &old_entry->list_node);
-            LinkedList2_Append(&peer->macs_used, &old_entry->list_node);
-            return;
-        }
-        
-        // remove entry from global hash table
-        ASSERT_EXECUTE(HashTable_Remove(&mac_table, old_entry->mac))
-        
-        // move entry to peer's unused list
-        LinkedList2_Remove(&old_entry->peer->macs_used, &old_entry->list_node);
-        LinkedList2_Append(&old_entry->peer->macs_free, &old_entry->list_node);
-    }
-    
-    peer_log(peer, BLOG_INFO, "adding MAC %02"PRIx8":%02"PRIx8":%02"PRIx8":%02"PRIx8":%02"PRIx8":%02"PRIx8"", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
-    
-    // aquire MAC address entry, if there are no free ones reuse the oldest used one
-    LinkedList2Node *node;
-    struct mac_table_entry *entry;
-    if (node = LinkedList2_GetFirst(&peer->macs_free)) {
-        entry = UPPER_OBJECT(node, struct mac_table_entry, list_node);
-        ASSERT(entry->peer == peer)
-        
-        // remove from unused list
-        LinkedList2_Remove(&peer->macs_free, &entry->list_node);
-    } else {
-        node = LinkedList2_GetFirst(&peer->macs_used);
-        ASSERT(node)
-        entry = UPPER_OBJECT(node, struct mac_table_entry, list_node);
-        ASSERT(entry->peer == peer)
-        
-        // remove from used list
-        LinkedList2_Remove(&peer->macs_used, &entry->list_node);
-        
-        // remove from global hash table
-        ASSERT_EXECUTE(HashTable_Remove(&mac_table, entry->mac))
-    }
-    
-    // copy MAC to entry
-    memcpy(entry->mac, mac, 6);
-    
-    // add entry to used list
-    LinkedList2_Append(&peer->macs_used, &entry->list_node);
-    
-    // add entry to global hash table
-    ASSERT_EXECUTE(HashTable_Insert(&mac_table, &entry->table_node))
-}
-
-void peer_join_group (struct peer_data *peer, uint32_t group)
-{
-    struct peer_group_entry *group_entry;
-    
-    HashTableNode *old_table_node;
-    if (HashTable_Lookup(&peer->groups_hashtable, &group, &old_table_node)) {
-        group_entry = UPPER_OBJECT(old_table_node, struct peer_group_entry, table_node);
-        
-        // move to end of used list
-        LinkedList2_Remove(&peer->groups_used, &group_entry->list_node);
-        LinkedList2_Append(&peer->groups_used, &group_entry->list_node);
-    } else {
-        peer_log(peer, BLOG_INFO, "joined group %"PRIu8".%"PRIu8".%"PRIu8".%"PRIu8"",
-            ((uint8_t *)&group)[0], ((uint8_t *)&group)[1], ((uint8_t *)&group)[2], ((uint8_t *)&group)[3]
-        );
-        
-        // aquire group entry, if there are no free ones reuse the earliest used one
-        LinkedList2Node *node;
-        if (node = LinkedList2_GetFirst(&peer->groups_free)) {
-            group_entry = UPPER_OBJECT(node, struct peer_group_entry, list_node);
-            
-            // remove from free list
-            LinkedList2_Remove(&peer->groups_free, &group_entry->list_node);
-        } else {
-            node = LinkedList2_GetFirst(&peer->groups_used);
-            ASSERT(node)
-            group_entry = UPPER_OBJECT(node, struct peer_group_entry, list_node);
-            
-            // remove from used list
-            LinkedList2_Remove(&peer->groups_used, &group_entry->list_node);
-            
-            // remove from groups hash table
-            ASSERT_EXECUTE(HashTable_Remove(&peer->groups_hashtable, &group_entry->group))
-            
-            // remove from global multicast table
-            multicast_table_remove_entry(group_entry);
-        }
-        
-        // add entry to used list
-        LinkedList2_Append(&peer->groups_used, &group_entry->list_node);
-        
-        // set group address in entry
-        group_entry->group = group;
-        
-        // add entry to groups hash table
-        ASSERT_EXECUTE(HashTable_Insert(&peer->groups_hashtable, &group_entry->table_node))
-        
-        // add entry to global multicast table
-        multicast_table_add_entry(group_entry);
-    }
-    
-    // start timer
-    group_entry->timer_endtime = btime_gettime() + IGMP_DEFAULT_GROUP_MEMBERSHIP_INTERVAL;
-    BReactor_SetTimerAbsolute(&ss, &group_entry->timer, group_entry->timer_endtime);
-}
-
-void peer_leave_group (struct peer_data *peer, uint32_t group)
-{
-    HashTableNode *table_node;
-    if (!HashTable_Lookup(&peer->groups_hashtable, &group, &table_node)) {
-        return;
-    }
-    struct peer_group_entry *group_entry = UPPER_OBJECT(table_node, struct peer_group_entry, table_node);
-    
-    peer_log(peer, BLOG_INFO, "left group %"PRIu8".%"PRIu8".%"PRIu8".%"PRIu8"",
-        ((uint8_t *)&group)[0], ((uint8_t *)&group)[1], ((uint8_t *)&group)[2], ((uint8_t *)&group)[3]
-    );
-    
-    // move to free list
-    LinkedList2_Remove(&peer->groups_used, &group_entry->list_node);
-    LinkedList2_Append(&peer->groups_free, &group_entry->list_node);
-    
-    // stop timer
-    BReactor_RemoveTimer(&ss, &group_entry->timer);
-    
-    // remove from groups hash table
-    ASSERT_EXECUTE(HashTable_Remove(&peer->groups_hashtable, &group_entry->group))
-    
-    // remove from global multicast table
-    multicast_table_remove_entry(group_entry);
-}
-
 void peer_msg (struct peer_data *peer, uint8_t *data, int data_len)
 void peer_msg (struct peer_data *peer, uint8_t *data, int data_len)
 {
 {
     ASSERT(server_ready)
     ASSERT(server_ready)
@@ -2491,18 +2223,8 @@ void peer_recv_handler_send (struct peer_data *peer, uint8_t *data, int data_len
     if (id == my_id) {
     if (id == my_id) {
         // frame is for us
         // frame is for us
         
         
-        // check ethernet header
-        if (data_len < sizeof(struct ethernet_header)) {
-            peer_log(peer, BLOG_INFO, "received frame without ethernet header");
-            goto out;
-        }
-        struct ethernet_header *header = (struct ethernet_header *)data;
-        
-        // associate source address with peer
-        peer_add_mac_address(peer, header->source);
-        
-        // invoke incoming hook
-        peer_hook_incoming(peer, data, data_len);
+        // let the frame decider analyze the frame
+        FrameDeciderPeer_Analyze(&peer->decider_peer, data, data_len);
         
         
         local = 1;
         local = 1;
     } else {
     } else {
@@ -2903,13 +2625,6 @@ void peer_submit_relayed_frame (struct peer_data *peer, struct peer_data *source
     DataProtoDest_SubmitRelayFrame(&peer->send_dp, &source_peer->relay_source, frame, frame_len, options.send_buffer_relay_size);
     DataProtoDest_SubmitRelayFrame(&peer->send_dp, &source_peer->relay_source, frame, frame_len, options.send_buffer_relay_size);
 }
 }
 
 
-void peer_group_timer_handler (struct peer_group_entry *entry)
-{
-    struct peer_data *peer = entry->peer;
-    
-    peer_leave_group(peer, entry->group);
-}
-
 void peer_dataproto_handler (struct peer_data *peer, int up)
 void peer_dataproto_handler (struct peer_data *peer, int up)
 {
 {
     ASSERT(peer->have_link)
     ASSERT(peer->have_link)
@@ -2944,87 +2659,6 @@ struct peer_data * find_peer_by_id (peerid_t id)
     return peer;
     return peer;
 }
 }
 
 
-void multicast_table_add_entry (struct peer_group_entry *group_entry)
-{
-    // key is 23 network byte order least-significant bits of group address
-    uint32_t sig = hton32(ntoh32(group_entry->group)&0x7FFFFF);
-    
-    // lookup entry in multicast table
-    struct multicast_table_entry *multicast_entry;
-    HashTableNode *table_node;
-    if (HashTable_Lookup(&multicast_table, &sig, &table_node)) {
-        multicast_entry = UPPER_OBJECT(table_node, struct multicast_table_entry, table_node);
-    } else {
-        // grab entry from free multicast entries list
-        LinkedList2Node *free_list_node = LinkedList2_GetFirst(&multicast_entries_free);
-        ASSERT(free_list_node) // there are as many multicast entries as maximum number of groups
-        multicast_entry = UPPER_OBJECT(free_list_node, struct multicast_table_entry, free_list_node);
-        LinkedList2_Remove(&multicast_entries_free, &multicast_entry->free_list_node);
-        
-        // set key
-        multicast_entry->sig = sig;
-        
-        // insert into hash table
-        ASSERT_EXECUTE(HashTable_Insert(&multicast_table, &multicast_entry->table_node))
-        
-        // init list of group entries
-        LinkedList2_Init(&multicast_entry->group_entries);
-    }
-    
-    // add to list of group entries
-    LinkedList2_Append(&multicast_entry->group_entries, &group_entry->multicast_list_node);
-    
-    // write multicast entry pointer to group entry for fast removal of groups
-    group_entry->multicast_entry = multicast_entry;
-}
-
-void multicast_table_remove_entry (struct peer_group_entry *group_entry)
-{
-    struct multicast_table_entry *multicast_entry = group_entry->multicast_entry;
-    
-    // remove group entry from linked list in multicast entry
-    LinkedList2_Remove(&multicast_entry->group_entries, &group_entry->multicast_list_node);
-    
-    // if the multicast entry has no more group entries, remove it from the hash table
-    if (LinkedList2_IsEmpty(&multicast_entry->group_entries)) {
-        // remove from multicast table
-        ASSERT_EXECUTE(HashTable_Remove(&multicast_table, &multicast_entry->sig))
-        
-        // add to free list
-        LinkedList2_Append(&multicast_entries_free, &multicast_entry->free_list_node);
-    }
-}
-
-int peer_groups_table_key_comparator (uint32_t *group1, uint32_t *group2)
-{
-    return (*group1 == *group2);
-}
-
-int peer_groups_table_hash_function (uint32_t *group, int modulo)
-{
-    return (jenkins_lookup2_hash((uint8_t *)group, sizeof(*group), 0) % modulo);
-}
-
-int mac_table_key_comparator (uint8_t *mac1, uint8_t *mac2)
-{
-    return (memcmp(mac1, mac2, 6) == 0);
-}
-
-int mac_table_hash_function (uint8_t *mac, int modulo)
-{
-    return (jenkins_lookup2_hash(mac, 6, mac_table_initval) % modulo);
-}
-
-int multicast_table_key_comparator (uint32_t *sig1, uint32_t *sig2)
-{
-    return (*sig1 == *sig2);
-}
-
-int multicast_table_hash_function (uint32_t *sig, int modulo)
-{
-    return (jenkins_lookup2_hash((uint8_t *)sig, sizeof(*sig), multicast_table_initval) % modulo);
-}
-
 int peers_by_id_key_comparator (peerid_t *id1, peerid_t *id2)
 int peers_by_id_key_comparator (peerid_t *id1, peerid_t *id2)
 {
 {
     return (*id1 == *id2);
     return (*id1 == *id2);
@@ -3051,58 +2685,14 @@ void device_input_handler_send (void *unused, uint8_t *data, int data_len)
     // accept packet
     // accept packet
     PacketPassInterface_Done(&device.input_interface);
     PacketPassInterface_Done(&device.input_interface);
     
     
-    const uint8_t broadcast_mac[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
-    const uint8_t multicast_header[] = {0x01, 0x00, 0x5e};
-    
-    if (data_len < sizeof(struct ethernet_header)) {
-        BLog(BLOG_INFO, "device: frame too small (%d)", data_len);
-        return;
-    }
+    // give frame to decider
+    FrameDecider_AnalyzeAndDecide(&frame_decider, data, data_len);
     
     
-    struct ethernet_header *header = (struct ethernet_header *)data;
-    
-    // invoke outgoing hook
-    int hook_result = hook_outgoing(data, data_len);
-    
-    switch (hook_result) {
-        case HOOK_OUT_DEFAULT:
-            // is it multicast?
-            if (!memcmp(header->dest, multicast_header, 3)) {
-                // obtain multicast group bits from MAC address
-                uint32_t sig;
-                memcpy(&sig, &header->dest[2], 4);
-                sig = hton32(ntoh32(sig)&0x7FFFFF);
-                // lookup multicast entry
-                HashTableNode *multicast_table_node;
-                if (HashTable_Lookup(&multicast_table, &sig, &multicast_table_node)) {
-                    struct multicast_table_entry *multicast_entry = UPPER_OBJECT(multicast_table_node, struct multicast_table_entry, table_node);
-                    // send to all peers with groups matching the known bits of the group address
-                    LinkedList2Iterator it;
-                    LinkedList2Iterator_InitForward(&it, &multicast_entry->group_entries);
-                    LinkedList2Node *group_entries_list_node;
-                    while (group_entries_list_node = LinkedList2Iterator_Next(&it)) {
-                        struct peer_group_entry *group_entry = UPPER_OBJECT(group_entries_list_node, struct peer_group_entry, multicast_list_node);
-                        submit_frame_to_peer(group_entry->peer, data, data_len);
-                    }
-                }
-            } else {
-                // should we flood it?
-                HashTableNode *mac_table_node;
-                if (!memcmp(header->dest, broadcast_mac, 6) || !HashTable_Lookup(&mac_table, header->dest, &mac_table_node)) {
-                    flood_frame(data, data_len);
-                }
-                // unicast it
-                else {
-                    struct mac_table_entry *mac_entry = UPPER_OBJECT(mac_table_node, struct mac_table_entry, table_node);
-                    submit_frame_to_peer(mac_entry->peer, data, data_len);
-                }
-            }
-            break;
-        case HOOK_OUT_FLOOD:
-            flood_frame(data, data_len);
-            break;
-        default:
-            ASSERT(0);
+    // forward frame to peers
+    FrameDeciderPeer *decider_peer;
+    while (decider_peer = FrameDecider_NextDestination(&frame_decider)) {
+        struct peer_data *peer = UPPER_OBJECT(decider_peer, struct peer_data, decider_peer);
+        submit_frame_to_peer(peer, data, data_len);
     }
     }
 }
 }
 
 
@@ -3114,257 +2704,6 @@ void submit_frame_to_peer (struct peer_data *peer, uint8_t *data, int data_len)
     DataProtoLocalSource_SubmitFrame(&peer->local_dpflow, data, data_len);
     DataProtoLocalSource_SubmitFrame(&peer->local_dpflow, data, data_len);
 }
 }
 
 
-void flood_frame (uint8_t *data, int data_len)
-{
-    ASSERT(data_len >= 0)
-    ASSERT(data_len <= device.mtu)
-    
-    LinkedList2Iterator it;
-    LinkedList2Iterator_InitForward(&it, &peers);
-    LinkedList2Node *peer_list_node;
-    while (peer_list_node = LinkedList2Iterator_Next(&it)) {
-        struct peer_data *peer = UPPER_OBJECT(peer_list_node, struct peer_data, list_node);
-        submit_frame_to_peer(peer, data, data_len);
-    }
-}
-
-int hook_outgoing (uint8_t *pos, int len)
-{
-    ASSERT(len >= sizeof(struct ethernet_header))
-    
-    struct ethernet_header *eth_header = (struct ethernet_header *)pos;
-    pos += sizeof(struct ethernet_header);
-    len -= sizeof(struct ethernet_header);
-    
-    switch (ntoh16(eth_header->type)) {
-        case ETHERTYPE_IPV4: {
-            struct ipv4_header *ipv4_header;
-            if (!check_ipv4_packet(pos, len, &ipv4_header, &pos, &len)) {
-                BLog(BLOG_INFO, "hook outgoing: wrong IP packet");
-                goto out;
-            }
-            if (ipv4_header->protocol != IPV4_PROTOCOL_IGMP) {
-                goto out;
-            }
-            if (len < sizeof(struct igmp_base)) {
-                BLog(BLOG_INFO, "hook outgoing: IGMP: short packet");
-                goto out;
-            }
-            struct igmp_base *igmp_base = (struct igmp_base *)pos;
-            pos += sizeof(struct igmp_base);
-            len -= sizeof(struct igmp_base);
-            switch (igmp_base->type) {
-                case IGMP_TYPE_MEMBERSHIP_QUERY: {
-                    if (len == sizeof(struct igmp_v2_extra) && igmp_base->max_resp_code != 0) {
-                        // V2 query
-                        struct igmp_v2_extra *query = (struct igmp_v2_extra *)pos;
-                        pos += sizeof(struct igmp_v2_extra);
-                        len -= sizeof(struct igmp_v2_extra);
-                        if (ntoh32(query->group) != 0) {
-                            // got a Group Specific Query, lower group timers to LMQT
-                            lower_group_timers_to_lmqt(query->group);
-                        }
-                    }
-                    else if (len >= sizeof(struct igmp_v3_query_extra)) {
-                        // V3 query
-                        struct igmp_v3_query_extra *query = (struct igmp_v3_query_extra *)pos;
-                        pos += sizeof(struct igmp_v3_query_extra);
-                        len -= sizeof(struct igmp_v3_query_extra);
-                        uint16_t num_sources = ntoh16(query->number_of_sources);
-                        int i;
-                        for (i = 0; i < num_sources; i++) {
-                            if (len < sizeof(struct igmp_source)) {
-                                BLog(BLOG_NOTICE, "hook outgoing: IGMP: short source");
-                                goto out_igmp;
-                            }
-                            pos += sizeof(struct igmp_source);
-                            len -= sizeof(struct igmp_source);
-                        }
-                        if (i < num_sources) {
-                            BLog(BLOG_NOTICE, "hook outgoing: IGMP: not all sources present");
-                            goto out_igmp;
-                        }
-                        if (ntoh32(query->group) != 0 && num_sources == 0) {
-                            // got a Group Specific Query, lower group timers to LMQT
-                            lower_group_timers_to_lmqt(query->group);
-                        }
-                    }
-                } break;
-            }
-        out_igmp:
-            // flood IGMP frames to allow all peers to learn group membership
-            return HOOK_OUT_FLOOD;
-        } break;
-    }
-    
-out:
-    return HOOK_OUT_DEFAULT;
-}
-
-void peer_hook_incoming (struct peer_data *peer, uint8_t *pos, int len)
-{
-    ASSERT(len >= sizeof(struct ethernet_header))
-    
-    struct ethernet_header *eth_header = (struct ethernet_header *)pos;
-    pos += sizeof(struct ethernet_header);
-    len -= sizeof(struct ethernet_header);
-    
-    switch (ntoh16(eth_header->type)) {
-        case ETHERTYPE_IPV4: {
-            struct ipv4_header *ipv4_header;
-            if (!check_ipv4_packet(pos, len, &ipv4_header, &pos, &len)) {
-                BLog(BLOG_INFO, "hook incoming: wrong IP packet");
-                goto out;
-            }
-            if (ipv4_header->protocol != IPV4_PROTOCOL_IGMP) {
-                goto out;
-            }
-            if (len < sizeof(struct igmp_base)) {
-                BLog(BLOG_INFO, "hook incoming: IGMP: short");
-                goto out;
-            }
-            struct igmp_base *igmp_base = (struct igmp_base *)pos;
-            pos += sizeof(struct igmp_base);
-            len -= sizeof(struct igmp_base);
-            switch (igmp_base->type) {
-                case IGMP_TYPE_V2_MEMBERSHIP_REPORT: {
-                    if (len < sizeof(struct igmp_v2_extra)) {
-                        BLog(BLOG_INFO, "hook incoming: IGMP: short v2 report");
-                        goto out;
-                    }
-                    struct igmp_v2_extra *report = (struct igmp_v2_extra *)pos;
-                    pos += sizeof(struct igmp_v2_extra);
-                    len -= sizeof(struct igmp_v2_extra);
-                    peer_join_group(peer, report->group);
-                } break;
-                case IGMP_TYPE_V3_MEMBERSHIP_REPORT: {
-                    if (len < sizeof(struct igmp_v3_report_extra)) {
-                        BLog(BLOG_INFO, "hook incoming: IGMP: short v3 report");
-                        goto out;
-                    }
-                    struct igmp_v3_report_extra *report = (struct igmp_v3_report_extra *)pos;
-                    pos += sizeof(struct igmp_v3_report_extra);
-                    len -= sizeof(struct igmp_v3_report_extra);
-                    uint16_t num_records = ntoh16(report->number_of_group_records);
-                    int i;
-                    for (i = 0; i < num_records; i++) {
-                        if (len < sizeof(struct igmp_v3_report_record)) {
-                            BLog(BLOG_INFO, "hook incoming: IGMP: short record header");
-                            goto out;
-                        }
-                        struct igmp_v3_report_record *record = (struct igmp_v3_report_record *)pos;
-                        pos += sizeof(struct igmp_v3_report_record);
-                        len -= sizeof(struct igmp_v3_report_record);
-                        uint16_t num_sources = ntoh16(record->number_of_sources);
-                        int j;
-                        for (j = 0; j < num_sources; j++) {
-                            if (len < sizeof(struct igmp_source)) {
-                                BLog(BLOG_INFO, "hook incoming: IGMP: short source");
-                                goto out;
-                            }
-                            struct igmp_source *source = (struct igmp_source *)pos;
-                            pos += sizeof(struct igmp_source);
-                            len -= sizeof(struct igmp_source);
-                        }
-                        if (j < num_sources) {
-                            goto out;
-                        }
-                        uint16_t aux_len = ntoh16(record->aux_data_len);
-                        if (len < aux_len) {
-                            BLog(BLOG_INFO, "hook incoming: IGMP: short record aux data");
-                            goto out;
-                        }
-                        pos += aux_len;
-                        len -= aux_len;
-                        switch (record->type) {
-                            case IGMP_RECORD_TYPE_MODE_IS_INCLUDE:
-                            case IGMP_RECORD_TYPE_CHANGE_TO_INCLUDE_MODE:
-                                if (num_sources != 0) {
-                                    peer_join_group(peer, record->group);
-                                }
-                                break;
-                            case IGMP_RECORD_TYPE_MODE_IS_EXCLUDE:
-                            case IGMP_RECORD_TYPE_CHANGE_TO_EXCLUDE_MODE:
-                                peer_join_group(peer, record->group);
-                                break;
-                        }
-                    }
-                    if (i < num_records) {
-                        BLog(BLOG_INFO, "hook incoming: IGMP: not all records present");
-                    }
-                } break;
-            }
-        } break;
-    }
-    
-out:;
-}
-
-void lower_group_timers_to_lmqt (uint32_t group)
-{
-    // lookup the group in every peer's group entries hash table
-    LinkedList2Iterator it;
-    LinkedList2Iterator_InitForward(&it, &peers);
-    LinkedList2Node *peer_list_node;
-    while (peer_list_node = LinkedList2Iterator_Next(&it)) {
-        struct peer_data *peer = UPPER_OBJECT(peer_list_node, struct peer_data, list_node);
-        HashTableNode *groups_table_node;
-        if (HashTable_Lookup(&peer->groups_hashtable, &group, &groups_table_node)) {
-            struct peer_group_entry *group_entry = UPPER_OBJECT(groups_table_node, struct peer_group_entry, table_node);
-            ASSERT(group_entry->peer == peer)
-            btime_t now = btime_gettime();
-            if (group_entry->timer_endtime > now + IGMP_LAST_MEMBER_QUERY_TIME) {
-                group_entry->timer_endtime = now + IGMP_LAST_MEMBER_QUERY_TIME;
-                BReactor_SetTimerAbsolute(&ss, &group_entry->timer, group_entry->timer_endtime);
-            }
-        }
-    }
-}
-
-int check_ipv4_packet (uint8_t *data, int data_len, struct ipv4_header **out_header, uint8_t **out_payload, int *out_payload_len)
-{
-    // check base header
-    if (data_len < sizeof(struct ipv4_header)) {
-        BLog(BLOG_DEBUG, "check ipv4: packet too short (base header)");
-        return 0;
-    }
-    struct ipv4_header *header = (struct ipv4_header *)data;
-    
-    // check version
-    if (IPV4_GET_VERSION(*header) != 4) {
-        BLog(BLOG_DEBUG, "check ipv4: version not 4");
-        return 0;
-    }
-    
-    // check options
-    int header_len = IPV4_GET_IHL(*header) * 4;
-    if (header_len < sizeof(struct ipv4_header)) {
-        BLog(BLOG_DEBUG, "check ipv4: ihl too small");
-        return 0;
-    }
-    if (header_len > data_len) {
-        BLog(BLOG_DEBUG, "check ipv4: packet too short for ihl");
-        return 0;
-    }
-    
-    // check total length
-    uint16_t total_length = ntoh16(header->total_length);
-    if (total_length < header_len) {
-        BLog(BLOG_DEBUG, "check ipv4: total length too small");
-        return 0;
-    }
-    if (total_length > data_len) {
-        BLog(BLOG_DEBUG, "check ipv4: total length too large");
-        return 0;
-    }
-    
-    *out_header = header;
-    *out_payload = data + header_len;
-    *out_payload_len = total_length - header_len;
-    
-    return 1;
-}
-
 void assign_relays (void)
 void assign_relays (void)
 {
 {
     LinkedList2Node *list_node;
     LinkedList2Node *list_node;

+ 4 - 47
client/client.h

@@ -32,6 +32,7 @@
 #include <client/DatagramPeerIO.h>
 #include <client/DatagramPeerIO.h>
 #include <client/StreamPeerIO.h>
 #include <client/StreamPeerIO.h>
 #include <client/DataProto.h>
 #include <client/DataProto.h>
+#include <client/FrameDecider.h>
 
 
 // NOTE: all time values are in milliseconds
 // NOTE: all time values are in milliseconds
 
 
@@ -68,7 +69,7 @@
 
 
 // for how long a peer can send no Membership Reports for a group
 // for how long a peer can send no Membership Reports for a group
 // before the peer and group are disassociated
 // before the peer and group are disassociated
-#define IGMP_DEFAULT_GROUP_MEMBERSHIP_INTERVAL 260000
+#define IGMP_GROUP_MEMBERSHIP_INTERVAL 260000
 // how long to wait for joins after a Group Specific query has been
 // how long to wait for joins after a Group Specific query has been
 // forwarded to a peer before assuming there are no listeners at the peer
 // forwarded to a peer before assuming there are no listeners at the peer
 #define IGMP_LAST_MEMBER_QUERY_TIME 2000
 #define IGMP_LAST_MEMBER_QUERY_TIME 2000
@@ -96,40 +97,6 @@ struct device_data {
 
 
 struct peer_data;
 struct peer_data;
 
 
-// entry in global MAC hash table
-struct mac_table_entry {
-    struct peer_data *peer;
-    LinkedList2Node list_node; // node in macs_used or macs_free
-    // defined when used:
-    uint8_t mac[6]; // MAC address
-    HashTableNode table_node; // node in global MAC address table
-};
-
-// entry in global multicast hash table
-struct multicast_table_entry {
-    // defined when free:
-    LinkedList2Node free_list_node; // node in free entries list
-    // defined when used:
-    uint32_t sig; // last 23 bits of group address
-    HashTableNode table_node; // node in global multicast hash table
-    LinkedList2 group_entries; // list of peers' group entries that match this multicast entry
-};
-
-// multicast group entry in peers
-struct peer_group_entry {
-    struct peer_data *peer;
-    LinkedList2Node list_node; // node in peer's free or used groups list
-    BTimer timer; // timer for removing the group, running when group entry is used
-    // defined when used:
-    // basic group data
-    uint32_t group; // group address
-    HashTableNode table_node; // node in peer's groups hash table
-    btime_t timer_endtime;
-    // multicast table entry data
-    LinkedList2Node multicast_list_node; // node in list of multicast MACs that may mean this group
-    struct multicast_table_entry *multicast_entry; // pointer to entry in multicast hash table
-};
-
 struct peer_data {
 struct peer_data {
     // peer identifier
     // peer identifier
     peerid_t id;
     peerid_t id;
@@ -189,18 +156,8 @@ struct peer_data {
     // retry timer
     // retry timer
     BTimer reset_timer;
     BTimer reset_timer;
     
     
-    // MAC address entries
-    struct mac_table_entry macs_data[PEER_MAX_MACS];
-    // used entries, in global mac table
-    LinkedList2 macs_used;
-    // free entries
-    LinkedList2 macs_free;
-    
-    // IPv4 multicast groups the peer is a destination for
-    struct peer_group_entry groups_data[PEER_MAX_GROUPS];
-    LinkedList2 groups_used;
-    LinkedList2 groups_free;
-    HashTable groups_hashtable;
+    // frame decider peer
+    FrameDeciderPeer decider_peer;
     
     
     // relay server specific
     // relay server specific
     int is_relay;
     int is_relay;

+ 4 - 0
generated/blog_channel_FrameDecider.h

@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_FrameDecider

+ 2 - 1
generated/blog_channels_defines.h

@@ -10,4 +10,5 @@
 #define BLOG_CHANNEL_flooder 9
 #define BLOG_CHANNEL_flooder 9
 #define BLOG_CHANNEL_Listener 10
 #define BLOG_CHANNEL_Listener 10
 #define BLOG_CHANNEL_DataProto 11
 #define BLOG_CHANNEL_DataProto 11
-#define BLOG_NUM_CHANNELS 12
+#define BLOG_CHANNEL_FrameDecider 12
+#define BLOG_NUM_CHANNELS 13

+ 1 - 0
generated/blog_channels_list.h

@@ -10,3 +10,4 @@
 {.name = "flooder", .loglevel = 4},
 {.name = "flooder", .loglevel = 4},
 {.name = "Listener", .loglevel = 4},
 {.name = "Listener", .loglevel = 4},
 {.name = "DataProto", .loglevel = 4},
 {.name = "DataProto", .loglevel = 4},
+{.name = "FrameDecider", .loglevel = 4},