/*
* Copyright (c) 2011 Radim Vansa
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @addtogroup libnic
* @{
*/
/**
* @file
* @brief Incoming packets (frames) filtering functions
*/
#include <assert.h>
#include <stdlib.h>
#include <errno.h>
#include <mem.h>
#include <nic/nic.h>
#include "nic_rx_control.h"
/**
* Initializes the receive control structure
*
* @param rxc
*
* @return EOK On success
* @return ENOMEM On not enough memory
* @return EINVAL Internal error, should not happen
*/
errno_t nic_rxc_init(nic_rxc_t *rxc)
{
memset(rxc, 0, sizeof(nic_rxc_t));
errno_t rc;
rc = nic_addr_db_init(&rxc->blocked_sources, ETH_ADDR);
if (rc != EOK) {
return rc;
}
rc = nic_addr_db_init(&rxc->unicast_addrs, ETH_ADDR);
if (rc != EOK) {
return rc;
}
rc = nic_addr_db_init(&rxc->multicast_addrs, ETH_ADDR);
if (rc != EOK) {
return rc;
}
rxc->block_sources = false;
rxc->unicast_mode = NIC_UNICAST_DEFAULT;
rxc->multicast_mode = NIC_MULTICAST_BLOCKED;
rxc->broadcast_mode = NIC_BROADCAST_ACCEPTED;
/* Default NIC behavior */
rxc->unicast_exact = true;
rxc->multicast_exact = false;
rxc->vlan_exact = true;
return EOK;
}
/** Reinitialize the structure.
*
* @param filters
*/
errno_t nic_rxc_clear(nic_rxc_t *rxc)
{
nic_addr_db_destroy(&rxc->unicast_addrs);
nic_addr_db_destroy(&rxc->multicast_addrs);
nic_addr_db_destroy(&rxc->blocked_sources);
return nic_rxc_init(rxc);
}
/** Set the NIC's address that should be used as the default address during
* the checks.
*
* @param rxc
* @param prev_addr Previously used default address. Can be NULL
* if this is the first call after filters' initialization.
* @param curr_addr The new default address.
*
* @return EOK On success
*
*/
errno_t nic_rxc_set_addr(nic_rxc_t *rxc, const nic_address_t *prev_addr,
const nic_address_t *curr_addr)
{
if (prev_addr != NULL) {
errno_t rc = nic_addr_db_remove(&rxc->unicast_addrs,
(const uint8_t *) &prev_addr->address);
if (rc != EOK)
return rc;
}
return nic_addr_db_insert(&rxc->unicast_addrs,
(const uint8_t *) &curr_addr->address);
}
/* Helper structure */
typedef struct {
size_t max_count;
nic_address_t *address_list;
size_t address_count;
} nic_rxc_add_addr_t;
/** Helper function */
static void nic_rxc_add_addr(const uint8_t *addr, void *arg)
{
nic_rxc_add_addr_t *hs = (nic_rxc_add_addr_t *) arg;
if (hs->address_count < hs->max_count && hs->address_list != NULL) {
memcpy(&hs->address_list[hs->address_count].address, addr, ETH_ADDR);
}
hs->address_count++;
}
/**
* Queries the current mode of unicast frames receiving.
*
* @param rxc
* @param mode The new unicast mode
* @param max_count Max number of addresses that can be written into the
* address_list.
* @param address_list List of MAC addresses or NULL.
* @param address_count Number of accepted addresses (can be > max_count)
*/
void nic_rxc_unicast_get_mode(const nic_rxc_t *rxc, nic_unicast_mode_t *mode,
size_t max_count, nic_address_t *address_list, size_t *address_count)
{
*mode = rxc->unicast_mode;
if (rxc->unicast_mode == NIC_UNICAST_LIST) {
nic_rxc_add_addr_t hs = {
.max_count = max_count,
.address_list = address_list,
.address_count = 0
};
nic_addr_db_foreach(&rxc->unicast_addrs, nic_rxc_add_addr, &hs);
if (address_count) {
*address_count = hs.address_count;
}
}
}
/**
* Sets the current mode of unicast frames receiving.
*
* @param rxc
* @param mode The current unicast mode
* @param address_list List of MAC addresses or NULL.
* @param address_count Number of addresses in the list
*
* @return EOK On success
* @return EINVAL If any of the MAC addresses is not a unicast address.
* @return ENOMEM If there was not enough memory
*/
errno_t nic_rxc_unicast_set_mode(nic_rxc_t *rxc, nic_unicast_mode_t mode,
const nic_address_t *address_list, size_t address_count)
{
if (mode == NIC_UNICAST_LIST && address_list == NULL) {
return EINVAL;
} else if (mode != NIC_UNICAST_LIST && address_list != NULL) {
return EINVAL;
}
if (rxc->unicast_mode == NIC_UNICAST_LIST) {
nic_addr_db_clear(&rxc->unicast_addrs);
}
rxc->unicast_mode = mode;
size_t i;
for (i = 0; i < address_count; ++i) {
errno_t rc = nic_addr_db_insert(&rxc->unicast_addrs,
(const uint8_t *) &address_list[i].address);
if (rc == ENOMEM) {
return ENOMEM;
}
}
return EOK;
}
/**
* Queries the current mode of multicast frames receiving.
*
* @param rxc
* @param mode The current multicast mode
* @param max_count Max number of addresses that can be written into the
* address_list.
* @param address_list List of MAC addresses or NULL.
* @param address_count Number of accepted addresses (can be > max_count)
*/
void nic_rxc_multicast_get_mode(const nic_rxc_t *rxc,
nic_multicast_mode_t *mode, size_t max_count, nic_address_t *address_list,
size_t *address_count)
{
*mode = rxc->multicast_mode;
if (rxc->multicast_mode == NIC_MULTICAST_LIST) {
nic_rxc_add_addr_t hs = {
.max_count = max_count,
.address_list = address_list,
.address_count = 0
};
nic_addr_db_foreach(&rxc->multicast_addrs, nic_rxc_add_addr, &hs);
if (address_count) {
*address_count = hs.address_count;
}
}
}
/**
* Sets the current mode of multicast frames receiving.
*
* @param rxc
* @param mode The new multicast mode
* @param address_list List of MAC addresses or NULL.
* @param address_count Number of addresses in the list
*
* @return EOK On success
* @return EINVAL If any of the MAC addresses is not a multicast address.
* @return ENOMEM If there was not enough memory
*/
errno_t nic_rxc_multicast_set_mode(nic_rxc_t *rxc, nic_multicast_mode_t mode,
const nic_address_t *address_list, size_t address_count)
{
if (mode == NIC_MULTICAST_LIST && address_list == NULL)
return EINVAL;
else if (mode != NIC_MULTICAST_LIST && address_list != NULL)
return EINVAL;
if (rxc->multicast_mode == NIC_MULTICAST_LIST)
nic_addr_db_clear(&rxc->multicast_addrs);
rxc->multicast_mode = mode;
size_t i;
for (i = 0; i < address_count; ++i) {
errno_t rc = nic_addr_db_insert(&rxc->multicast_addrs,
(const uint8_t *)&address_list[i].address);
if (rc == ENOMEM) {
return ENOMEM;
}
}
return EOK;
}
/**
* Queries the current mode of broadcast frames receiving.
*
* @param rxc
* @param mode The new broadcast mode
*/
void nic_rxc_broadcast_get_mode(const nic_rxc_t *rxc, nic_broadcast_mode_t *mode)
{
*mode = rxc->broadcast_mode;
}
/**
* Sets the current mode of broadcast frames receiving.
*
* @param rxc
* @param mode The new broadcast mode
*
* @return EOK On success
*/
errno_t nic_rxc_broadcast_set_mode(nic_rxc_t *rxc, nic_broadcast_mode_t mode)
{
rxc->broadcast_mode = mode;
return EOK;
}
/**
* Queries the current blocked source addresses.
*
* @param rxc
* @param max_count Max number of addresses that can be written into the
* address_list.
* @param address_list List of MAC addresses or NULL.
* @param address_count Number of blocked addresses (can be > max_count)
*/
void nic_rxc_blocked_sources_get(const nic_rxc_t *rxc,
size_t max_count, nic_address_t *address_list, size_t *address_count)
{
nic_rxc_add_addr_t hs = {
.max_count = max_count,
.address_list = address_list,
.address_count = 0
};
nic_addr_db_foreach(&rxc->blocked_sources, nic_rxc_add_addr, &hs);
if (address_count) {
*address_count = hs.address_count;
}
}
/**
* Clears the currently blocked addresses and sets the addresses contained in
* the list as the set of blocked source addresses (no frame with this source
* address will be received). Duplicated addresses are ignored.
*
* @param rxc
* @param address_list List of the blocked addresses. Can be NULL.
* @param address_count Number of addresses in the list
*
* @return EOK On success
* @return ENOMEM If there was not enough memory
*/
errno_t nic_rxc_blocked_sources_set(nic_rxc_t *rxc,
const nic_address_t *address_list, size_t address_count)
{
assert((address_count == 0 && address_list == NULL) ||
(address_count != 0 && address_list != NULL));
nic_addr_db_clear(&rxc->blocked_sources);
rxc->block_sources = (address_count != 0);
size_t i;
for (i = 0; i < address_count; ++i) {
errno_t rc = nic_addr_db_insert(&rxc->blocked_sources,
(const uint8_t *) &address_list[i].address);
if (rc == ENOMEM) {
return ENOMEM;
}
}
return EOK;
}
/**
* Query mask used for filtering according to the VLAN tags.
*
* @param rxc
* @param mask Must be 512 bytes long
*
* @return EOK
* @return ENOENT
*/
errno_t nic_rxc_vlan_get_mask(const nic_rxc_t *rxc, nic_vlan_mask_t *mask)
{
if (rxc->vlan_mask == NULL) {
return ENOENT;
}
memcpy(mask, rxc->vlan_mask, sizeof (nic_vlan_mask_t));
return EOK;
}
/**
* Set mask for filtering according to the VLAN tags.
*
* @param rxc
* @param mask Must be 512 bytes long
*
* @return EOK
* @return ENOMEM
*/
errno_t nic_rxc_vlan_set_mask(nic_rxc_t *rxc, const nic_vlan_mask_t *mask)
{
if (mask == NULL) {
if (rxc->vlan_mask) {
free(rxc->vlan_mask);
}
rxc->vlan_mask = NULL;
return EOK;
}
if (!rxc->vlan_mask) {
rxc->vlan_mask = malloc(sizeof (nic_vlan_mask_t));
if (rxc->vlan_mask == NULL) {
return ENOMEM;
}
}
memcpy(rxc->vlan_mask, mask, sizeof (nic_vlan_mask_t));
return EOK;
}
/**
* Check if the frame passes through the receive control.
*
* @param rxc
* @param frame The probed frame
*
* @return True if the frame passes, false if it does not
*/
bool nic_rxc_check(const nic_rxc_t *rxc, const void *data, size_t size,
nic_frame_type_t *frame_type)
{
assert(frame_type != NULL);
uint8_t *dest_addr = (uint8_t *) data;
uint8_t *src_addr = dest_addr + ETH_ADDR;
if (size < 2 * ETH_ADDR)
return false;
if (dest_addr[0] & 1) {
/* Multicast or broadcast */
if (*(uint32_t *) dest_addr == 0xFFFFFFFF &&
*(uint16_t *) (dest_addr + 4) == 0xFFFF) {
/* It is broadcast */
*frame_type = NIC_FRAME_BROADCAST;
if (rxc->broadcast_mode == NIC_BROADCAST_BLOCKED)
return false;
} else {
*frame_type = NIC_FRAME_MULTICAST;
/* In promiscuous mode the multicast_exact should be set to true */
if (!rxc->multicast_exact) {
if (rxc->multicast_mode == NIC_MULTICAST_BLOCKED)
return false;
else {
if (!nic_addr_db_contains(&rxc->multicast_addrs,
dest_addr))
return false;
}
}
}
} else {
/* Unicast */
*frame_type = NIC_FRAME_UNICAST;
/* In promiscuous mode the unicast_exact should be set to true */
if (!rxc->unicast_exact) {
if (rxc->unicast_mode == NIC_UNICAST_BLOCKED)
return false;
else {
if (!nic_addr_db_contains(&rxc->unicast_addrs,
dest_addr))
return false;
}
}
}
/* Blocked source addresses */
if (rxc->block_sources) {
if (nic_addr_db_contains(&rxc->blocked_sources, src_addr))
return false;
}
/* VLAN filtering */
if (!rxc->vlan_exact && rxc->vlan_mask != NULL) {
vlan_header_t *vlan_header = (vlan_header_t *)
((uint8_t *) data + 2 * ETH_ADDR);
if (vlan_header->tpid_upper == VLAN_TPID_UPPER &&
vlan_header->tpid_lower == VLAN_TPID_LOWER) {
int index = ((int) (vlan_header->vid_upper & 0xF) << 5) |
(vlan_header->vid_lower >> 3);
if (!(rxc->vlan_mask->bitmap[index] &
(1 << (vlan_header->vid_lower & 0x7))))
return false;
}
}
return true;
}
/**
* Set information about current HW filtering.
* 1 ... Only those frames we want to receive are passed through HW
* 0 ... The HW filtering is imperfect
* -1 ... Don't change the setting
* This function should be called only from the mode change event handler.
*
* @param rxc
* @param unicast_exact Unicast frames
* @param mcast_exact Multicast frames
* @param vlan_exact VLAN tags
*/
void nic_rxc_hw_filtering(nic_rxc_t *rxc,
int unicast_exact, int multicast_exact, int vlan_exact)
{
if (unicast_exact >= 0)
rxc->unicast_exact = unicast_exact;
if (multicast_exact >= 0)
rxc->multicast_exact = multicast_exact;
if (vlan_exact >= 0)
rxc->vlan_exact = vlan_exact;
}
/** Polynomial used in multicast address hashing */
#define CRC_MCAST_POLYNOMIAL 0x04c11db6
/** Compute the standard hash from MAC
*
* Hashing MAC into 64 possible values and using the value as index to
* 64bit number.
*
* The code is copied from qemu-0.13's implementation of ne2000 and rt8139
* drivers, but according to documentation there it originates in FreeBSD.
*
* @param[in] addr The 6-byte MAC address to be hashed
*
* @return 64-bit number with only single bit set to 1
*
*/
static uint64_t multicast_hash(const uint8_t addr[6])
{
uint32_t crc;
int carry, i, j;
uint8_t b;
crc = 0xffffffff;
for (i = 0; i < 6; i++) {
b = addr[i];
for (j = 0; j < 8; j++) {
carry = ((crc & 0x80000000L) ? 1 : 0) ^ (b & 0x01);
crc <<= 1;
b >>= 1;
if (carry)
crc = ((crc ^ CRC_MCAST_POLYNOMIAL) | carry);
}
}
uint64_t one64 = 1;
return one64 << (crc >> 26);
}
/**
* Computes hash for the address list based on standard multicast address
* hashing.
*
* @param address_list
* @param count
*
* @return Multicast hash
*
* @see multicast_hash
*/
uint64_t nic_rxc_mcast_hash(const nic_address_t *address_list, size_t count)
{
size_t i;
uint64_t hash = 0;
for (i = 0; i < count; ++i) {
hash |= multicast_hash(address_list[i].address);
}
return hash;
}
static void nic_rxc_hash_addr(const uint8_t *address, void *arg)
{
*((uint64_t *) arg) |= multicast_hash(address);
}
/**
* Computes hash for multicast addresses currently set up in the RX multicast
* filtering. For promiscuous mode returns all ones, for blocking all zeroes.
* Can be called only from the on_*_change handler.
*
* @param rxc
*
* @return Multicast hash
*
* @see multicast_hash
*/
uint64_t nic_rxc_multicast_get_hash(const nic_rxc_t *rxc)
{
switch (rxc->multicast_mode) {
case NIC_MULTICAST_UNKNOWN:
case NIC_MULTICAST_BLOCKED:
return 0;
case NIC_MULTICAST_LIST:
break;
case NIC_MULTICAST_PROMISC:
return ~(uint64_t) 0;
}
uint64_t hash;
nic_addr_db_foreach(&rxc->multicast_addrs, nic_rxc_hash_addr, &hash);
return hash;
}
/** @}
*/