-
Notifications
You must be signed in to change notification settings - Fork 298
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #333 from alexandergall/learning-bridge
Learning bridge based on Bloom filter
- Loading branch information
Showing
10 changed files
with
1,032 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
-- Base class for an Ethernet bridge with split-horizon semantics. | ||
-- | ||
-- A bridge conists of any number of ports, each of which is a member | ||
-- of at most one split-horizon group. If it is not a member of a | ||
-- split-horizon group, the port is also called a "free" port. | ||
-- Packets arriving on a free port may be forwarded to all other | ||
-- ports. Packets arriving on a port that belongs to a split-horizon | ||
-- group are never forwarded to any port belonging to the same | ||
-- split-horizon group. | ||
-- | ||
-- The configuration is passed as a table of the following form | ||
-- | ||
-- config = { ports = { <free-port1>, <free-port2>, ... }, | ||
-- split_horizon_groups = { | ||
-- <sh_group1> = { <shg1-port1>, <shg1-port2>, ...}, | ||
-- ...}, | ||
-- config = { <bridge-specific-config> } } | ||
-- | ||
-- The "config" table contains configuration options specific to a | ||
-- derived class. It is ignored by the base class. | ||
-- | ||
-- The base constructor checks the configuration and creates the | ||
-- following arrays as private instance variables for efficient access | ||
-- in the push() method (which must be provided by any derived class). | ||
-- | ||
-- self._src_ports | ||
-- | ||
-- This array contains the names of all ports connected to the | ||
-- bridge. | ||
-- | ||
-- self._dst_ports | ||
-- | ||
-- This table is keyed by the name of an input port and associates | ||
-- it with an array of output ports according to the split-horizon | ||
-- topology. | ||
-- | ||
-- The push() method of a derived class should iterate over all source | ||
-- ports and forward the incoming packets to the associated output | ||
-- ports, replicating the packets as necessary. In the simplest case, | ||
-- the packets must be replicated to all destination ports (flooded) | ||
-- to make sure they reach any potential recipient. A more | ||
-- sophisticated bridge can store the MAC source addresses on incoming | ||
-- ports to limit the scope of flooding. | ||
|
||
module(..., package.seeall) | ||
|
||
local bridge = subClass(nil) | ||
bridge._name = "base bridge" | ||
|
||
function bridge:new (config) | ||
assert(self ~= bridge, "Can't instantiate abstract class "..self:name()) | ||
local o = bridge:superClass().new(self) | ||
assert(config and config.ports, self:name()..": invalid configuration") | ||
|
||
-- Create a list of forwarding ports for all ports connected to the | ||
-- bridge, taking split horizon groups into account | ||
local ports = {} | ||
local function add_port(port, group) | ||
assert(not ports[port], | ||
self:name()..": duplicate definition of port "..port) | ||
ports[port] = group | ||
end | ||
for _, port in ipairs(config.ports) do | ||
add_port(port, '') | ||
end | ||
if config.split_horizon_groups then | ||
for group, ports in pairs(config.split_horizon_groups) do | ||
for _, port in ipairs(ports) do | ||
add_port(port, group) | ||
end | ||
end | ||
end | ||
local src_ports, dst_ports = {}, {} | ||
for sport, sgroup in pairs(ports) do | ||
table.insert(src_ports, sport) | ||
dst_ports[sport] = {} | ||
for dport, dgroup in pairs(ports) do | ||
if not (sport == dport or (sgroup ~= '' and sgroup == dgroup)) then | ||
table.insert(dst_ports[sport], dport) | ||
end | ||
end | ||
end | ||
o._src_ports = src_ports | ||
o._dst_ports = dst_ports | ||
return o | ||
end | ||
|
||
return bridge |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
-- This class derives from lib.bridge.base and implements the simplest | ||
-- possible bridge, which floods a packet arriving on a port to all | ||
-- destination ports within its scope according to the split-horizon | ||
-- topology. | ||
|
||
module(..., package.seeall) | ||
|
||
local bridge_base = require("apps.bridge.base") | ||
local packet = require("core.packet") | ||
local link = require("core.link") | ||
local empty, receive, transmit = link.empty, link.receive, link.transmit | ||
local cow_clone = packet.cow_clone | ||
|
||
local bridge = subClass(bridge_base) | ||
bridge._name = "flooding bridge" | ||
|
||
function bridge:new (config) | ||
return bridge:superClass().new(self, config) | ||
end | ||
|
||
function bridge:push() | ||
local src_ports = self._src_ports | ||
local dst_ports = self._dst_ports | ||
local output = self.output | ||
local i = 1 | ||
while src_ports[i] do | ||
local src_port = src_ports[i] | ||
local l_in = self.input[src_port] | ||
while not empty(l_in) do | ||
local ports = dst_ports[src_port] | ||
local p = receive(l_in) | ||
transmit(output[ports[1]], p) | ||
local j = 2 | ||
while ports[j] do | ||
transmit(output[ports[j]], cow_clone(p)) | ||
j = j + 1 | ||
end | ||
end | ||
i = i + 1 | ||
end | ||
end | ||
|
||
return bridge |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
-- This class derives from lib.bridge.base and implements a "learning | ||
-- bridge" using a Bloom filter (provided by lib.bloom_filter) to | ||
-- store the set of MAC source addresses of packets arriving on each | ||
-- port. | ||
-- | ||
-- Two Bloom storage cells called mac_table and mac_shadow are | ||
-- allocated for each port connected to the bridge. For each packet | ||
-- arriving on a port, the MAC source address is stored in both cells. | ||
-- The mac_table cell is used during packet forwarding while | ||
-- mac_shadow is used to time out the learned addresses. | ||
-- | ||
-- When a packet is received on a port, its MAC destination address is | ||
-- looked up in the mac_table cells of all associated output | ||
-- ports. The packet is sent on all ports for which the lookup results | ||
-- in a match, replicating the packet if necessary. A destination can | ||
-- be associated with multiple output ports, either because the | ||
-- address has actually been learned on multiple ports or due to false | ||
-- positives in the lookup operation, which are inevitable for Bloom | ||
-- filters. | ||
-- | ||
-- Multicast MAC addresses are always flooded to all output ports | ||
-- associated with the input port. | ||
-- | ||
-- The timing out of learned addresses is implemented by periodically | ||
-- copying mac_shadow to mac_table and clearing mac_shadow for every | ||
-- port. I.e., mac_table contains only the addresses learned during | ||
-- the past timeout interval. | ||
-- | ||
-- Configuration variables (via the "config" table in the generic | ||
-- configuration of the base class) | ||
-- | ||
-- mac_table_size (default 1000) | ||
-- | ||
-- Expected maximum number of MAC addresses to store in each | ||
-- per-port Bloom filter. | ||
-- | ||
-- fp_rate (default 0.001) | ||
-- | ||
-- Maximum rate of false-positives for lookups in the Bloom | ||
-- filters, provided the number of distinct objects stored in the | ||
-- filter does not exceed mac_table_size. | ||
-- | ||
-- timeout (default 60 seconds) | ||
-- | ||
-- Timeout for learned MAC addresses in seconds. | ||
-- | ||
-- verbose (default false) | ||
-- | ||
-- If true, a diagnostic message containing the storage cell usage | ||
-- of each mac_table is printed to stdout | ||
-- | ||
|
||
module(..., package.seeall) | ||
|
||
local ffi = require("ffi") | ||
local bridge_base = require("apps.bridge.base") | ||
local packet = require("core.packet") | ||
local link = require("core.link") | ||
local bloom = require("lib.bloom_filter") | ||
local ethernet = require("lib.protocol.ethernet") | ||
|
||
local empty, receive, transmit = link.empty, link.receive, link.transmit | ||
local cow_clone = packet.cow_clone | ||
|
||
local bridge = subClass(bridge_base) | ||
bridge._name = "learning bridge" | ||
|
||
local default_config = { mac_table_size = 1000, fp_rate = 0.001, | ||
timeout = 60, verbose = false } | ||
|
||
function bridge:new (config) | ||
local o = bridge:superClass().new(self, config) | ||
local config = config.config or {} | ||
for k, v in pairs(default_config) do | ||
if not config[k] then | ||
config[k] = v | ||
end | ||
end | ||
local bf = bloom:new(config.mac_table_size, config.fp_rate) | ||
o._bf = bf | ||
o._nsrc_ports = #o._src_ports | ||
o._port_index = 1 | ||
-- Per-port Bloom filters | ||
o._filters = {} | ||
for _, port in ipairs(o._src_ports) do | ||
o._filters[port] = { mac_table = bf:cell_new(), | ||
mac_shadow = bf:cell_new(), | ||
mac_address = bf:item_new() | ||
} | ||
|
||
end | ||
o._eth_dst = bf:item_new() | ||
|
||
timer.activate(timer.new("mac_learn_timeout", | ||
function (t) | ||
if config.verbose then | ||
print("MAC learning timeout") | ||
print("Table usage per port:") | ||
end | ||
for port, filter in pairs(o._filters) do | ||
bf:cell_copy(filter.mac_shadow, filter.mac_table) | ||
bf:cell_clear(filter.mac_shadow) | ||
if config.verbose then | ||
print(string.format("\t%s: %02.2f%%", port, | ||
100*bf:cell_usage(filter.mac_table))) | ||
end | ||
end | ||
end, | ||
config.timeout *1e9, 'repeating') | ||
) | ||
|
||
-- Caches for various cdata pointer objects to avoid boxing in the | ||
-- push() loop | ||
o._cache = { | ||
p = ffi.new("struct packet *[1]"), | ||
iov = ffi.new("struct packet_iovec *[1]"), | ||
mem = ffi.new("uint8_t *[1]") | ||
} | ||
return o | ||
end | ||
|
||
-- We only process a single input port for each call of the push() | ||
-- method to reduce the number of nested loops. A better | ||
-- understanding of the JIT compiler is needed to decide whether this | ||
-- is actually a good thing or not. Empirical data suggests it is :) | ||
function bridge:push() | ||
local src_port = self._src_ports[self._port_index] | ||
local l_in = self.input[src_port] | ||
while not empty(l_in) do | ||
local cache = self._cache | ||
local dst_ports = self._dst_ports | ||
local p = cache.p | ||
local iov = cache.iov | ||
local mem = cache.mem | ||
local filters = self._filters | ||
local eth_dst = self._eth_dst | ||
local bf = self._bf | ||
p[0] = receive(l_in) | ||
|
||
-- Create a storage item from the destination MAC address | ||
-- for matching with the source addresses learned on the | ||
-- outbound ports, unless it is a multicast address. | ||
iov[0] = p[0].iovecs[0] | ||
mem[0] = iov[0].buffer.pointer + iov[0].offset | ||
local is_mcast = ethernet:is_mcast(mem[0]) | ||
if not is_mcast then | ||
bf:store_value(mem, 6, eth_dst) | ||
end | ||
|
||
-- Store the source MAC address in the active and shadow | ||
-- Bloom filters. | ||
local filter = filters[src_port] | ||
local mac_address = filter.mac_address | ||
mem[0] = mem[0] + 6 | ||
bf:store_value(mem, 6, mac_address, filter.mac_table) | ||
bf:store_item(mac_address, filter.mac_shadow) | ||
|
||
local ports = dst_ports[src_port] | ||
local copy = false | ||
local j = 1 | ||
while ports[j] do | ||
local dst_port = ports[j] | ||
if is_mcast or bf:check_item(eth_dst, filters[dst_port].mac_table) then | ||
if not copy then | ||
transmit(self.output[dst_port], p[0]) | ||
copy = true | ||
else | ||
transmit(self.output[dst_port], cow_clone(p[0])) | ||
end | ||
end | ||
j = j + 1 | ||
end | ||
if not copy then | ||
-- The source MAC address is unknown, flood the packet to | ||
-- all ports | ||
local output = self.output | ||
transmit(output[ports[1]], p[0]) | ||
local j = 2 | ||
while ports[j] do | ||
transmit(output[ports[j]], cow_clone(p[0])) | ||
j = j + 1 | ||
end | ||
end | ||
end -- of while not empty(l_in) | ||
if self._port_index == self._nsrc_ports then | ||
self._port_index = 1 | ||
else | ||
self._port_index = self._port_index + 1 | ||
end | ||
end | ||
|
||
return bridge |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.