Skip to content

Commit

Permalink
Add VLAN related apps
Browse files Browse the repository at this point in the history
  • Loading branch information
plajjan committed Apr 9, 2016
1 parent 03f5958 commit e37fd84
Show file tree
Hide file tree
Showing 2 changed files with 226 additions and 0 deletions.
52 changes: 52 additions & 0 deletions src/apps/vlan/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# VLAN

There are three VLAN related apps, Tagger, Untagger and VlanMux. Tagger and
Untagger are simple apps that add or remove a tag whereas VlanMux can mux and
demux packets to different interfaces based on tag.

## Tagger

Tagger adds a VLAN tag, with the configured value, to packets received on the
input interface and sends them to the output interface.

### Configuration

- Key **tag**

*Required*. VLAN tag to add or remove from the packet


## Untagger

Untagger checks packets received on the input interface for a VLAN tag, removes
it if it matches with the configured VLAN tag and sends them to the output
interface. Packets with other VLAN tags than the configured tag will be dropped.

### Configuration

- Key **tag**

*Required*. VLAN tag to add or remove from the packet


## VlanMux

Despite the name, VlanMux can act both as a multiplexer, i.e. receive packes
from multiple different inputs, add a VLAN tag and send them out onto one, as
well as receiving packets from a "trunk" interface and demultiplex it over many
interfaces based on the VLAN tag of the received packet.

Packets received on the interface named "trunk" with ethertype 0x8100 are
inspected for the VLAN tag and sent out interface "vlanX" where X is the VLAN
tag parsed from the packet. If no such output interface exists the frame is
dropped. Received packets with an ethertype other than 0x8100 are sent out the
output interface "native".

Packets received on the "native" interface are sent out verbatim on the "trunk"
port.

Packets received on an interface named vlanX, where X is a VLAN tag, will have
the VLAN tag X added and then be sent out the "trunk" interface.

There is no configuration for VlanMux, simply link it to your other apps and it
will base its actions on the name of the links.
174 changes: 174 additions & 0 deletions src/apps/vlan/vlan.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
module(..., package.seeall)

local packet = require("core.packet")
local bit = require("bit")
local ffi = require("ffi")

local C = ffi.C
local receive, transmit = link.receive, link.transmit
local cast = ffi.cast

Tagger = {}
Untagger = {}

-- 802.1q
local dotq_tpid = 0x8100
local o_ethernet_ethertype = 12
local uint32_ptr_t = ffi.typeof('uint32_t*')


-- build a VLAN tag consisting of 2 bytes of TPID set to 0x8100 followed by the
-- TCI field which in turns consists of PCP, DEI and VID (VLAN id). Both PCP
-- and DEI is always 0
local function build_tag(vid)
return ffi.C.htonl(bit.bor(bit.lshift(dotq_tpid, 16), vid))
end

-- pop a VLAN tag (4 byte of TPID and TCI) from a packet
function pop_tag(pkt)
local payload = pkt.data + o_ethernet_ethertype
local length = pkt.length
pkt.length = length - 4
C.memmove(payload, payload + 4, length - o_ethernet_ethertype - 4)
end

-- push a VLAN tag onto a packet
function push_tag(pkt, tag)
local payload = pkt.data + o_ethernet_ethertype
local length = pkt.length
pkt.length = length + 4
C.memmove(payload + 4, payload, length - o_ethernet_ethertype)
cast(uint32_ptr_t, payload)[0] = tag
end

-- extract TCI (2 bytes) from packet, no check is performed to verify that the
-- packet is carrying a VLAN tag, if it's an untagged frame these bytes will be
-- Ethernet payload
function extract_tci(pkt)
return ffi.C.ntohs(ffi.cast("uint16_t*", packet.data(pkt) + o_ethernet_ethertype + 2)[0])
end

-- extract VLAN id from TCI
function tci_to_vid(tci)
return bit.band(tci, 0xFFF)
end


function Tagger:new(conf)
local o = setmetatable({}, {__index=Tagger})
o.tag = build_tag(assert(conf.tag))
return o
end

function Tagger:push ()
local input, output = self.input.input, self.output.output
local tag = self.tag
for _=1,link.nreadable(input) do
local pkt = receive(input)
push_tag(pkt, tag)
transmit(output, pkt)
end
end

function Untagger:new(conf)
local o = setmetatable({}, {__index=Untagger})
o.tag = build_tag(assert(conf.tag))
return o
end

function Untagger:push ()
local input, output = self.input.input, self.output.output
local tag = self.tag
for _=1,link.nreadable(input) do
local pkt = receive(input)
local payload = pkt.data + o_ethernet_ethertype
if cast(uint32_ptr_t, payload)[0] ~= tag then
-- Incorrect VLAN tag; drop.
packet.free(pkt)
else
pop_tag(pkt)
transmit(output, pkt)
end
end
end


VlanMux = {}
function VlanMux:new(conf)
local self = setmetatable({}, {__index=VlanMux})
self.ethertype_8100 = ffi.cast("uint16_t", 0x0081) -- 0x8100 in network byte order
return self
end

function VlanMux:push()
local noutputs = #self.output
if noutputs > 0 then
for name, l in pairs(self.input) do
local maxoutput = link.max
-- find out max number of packets we can put out an interface
-- this is kind of bad because we limit ourselves by the interface with
-- the fullest queue, yet packets might go out a different interface. We
-- don't know until we've looked in the packet and parsed the VLAN id. I
-- suppose we kind of get some HOLB with this :(
for _, o in ipairs(self.output) do
maxoutput = math.min(maxoutput, link.nwritable(o))
end

if type(name) == "string" then
for _ = 1, math.min(link.nreadable(l), maxoutput) do
local p = receive(l)
local ethertype = ffi.cast("uint16_t*", packet.data(p) + o_ethernet_ethertype)[0]

if name == "trunk" then -- trunk
-- check for ethertype 0x8100 (802.1q VLAN tag)
if ethertype == self.ethertype_8100 then
-- dig out TCI field
local tci = extract_tci(p)
local vid = tci_to_vid(tci)
local oif = self.output["vlan"..vid]
pop_tag(p)
self:transmit(oif, p)

else -- untagged, send to native output
self:transmit(self.output.native, p)
end
elseif name == "native" then
self:transmit(self.output.trunk, p)
else -- some vlanX interface
local vid = tonumber(string.sub(name, 5))
push_tag(p, build_tag(vid))
self:transmit(self.output.trunk, p)
end
end
end
end
end
end

-- transmit packet out interface if given interface exists, otherwise drop
function VlanMux:transmit(o, pkt)
if o == nil then
packet.free(pkt)
else
transmit(o, pkt)
end
end


function selftest()
local app = require("core.app")
local basic_apps = require("apps.basic.basic_apps")

local c = config.new()
config.app(c, "source", basic_apps.Source)
config.app(c, "vlan_mux", VlanMux)
config.app(c, "sink", basic_apps.Sink)

config.link(c, "source.output -> vlan_mux.vlan1")
config.link(c, "vlan_mux.trunk -> sink.input")
app.configure(c)
app.main({duration = 1})

print("source sent: " .. link.stats(app.app_table.source.output.output).txpackets)
print("sink received: " .. link.stats(app.app_table.sink.input.input).rxpackets)
end

0 comments on commit e37fd84

Please sign in to comment.