Browse files

Tap: support MTU, augment stats

Support setting of the MTU on the tap device.  This is a bit tricky,
because the kernel only allows manipulation of the L3 (IP) MTU, while
Snabb considers the tap device to be L2 and includes the L2 header in
the MTU.  Various configuration options are introduces to work around
this issue in a flexible manner.

The app can create an ephemeral tap device or attach to a persistent
device managed outside of the Snabb process.  This was already
possible but this commit makes it clear in the README and also makes
sure that an ephemeral device's status is set to "up".

The interface's operational status is monitored and exposed as a stats
  • Loading branch information...
alexandergall committed Aug 23, 2016
1 parent 8960b0f commit 20d59582495baeb9a9a1b3a584b4ca87ca534702
Showing with 200 additions and 17 deletions.
  1. +5 −0 lib/ljsyscall/syscall/linux/ioctl.lua
  2. +56 −2 src/apps/tap/
  3. +139 −15 src/apps/tap/tap.lua
@@ -189,6 +189,11 @@ local ioctl = strflag {
SIOCGIFMTU = 0x8921,
SIOCSIFMTU = 0x8922,
SIOCDARP = 0x8953,
@@ -14,12 +14,66 @@ device, and packets that arrive on the tap device can be received on the
## Configuration
The `Tap` app accepts a string that identifies an existing tap interface.
This app accepts either a single string or a table as its
configuration argument. A single string is equivalent to the default
configuration with the `name` attribute set to the string.
The Tap device can be configured using standard Linux tools:
— Key **name**
*Required*. The name of the tap device.
If the device does not exist yet, which is inferred from the absence
of the directory `/sys/class/net/`**name**, it will be created by the
app and removed when the process terminates. Such a device is called
_ephemeral_ and its operational state is set to _up_ after creation.
If the device already exists, it is called _persistent_. The app can
attach to a persistent tap device and detaches from it when it
terminates. The operational state is not changed. By default, the
MTU is also not changed by the app, see the **mtu_set** option below.
One manner in which a persistent tap device can be created is by using
the `ip` tool
ip tuntap add Tap345 mode tap
ip link set up dev Tap345
ip link set address 02:01:02:03:04:08 dev Tap0
— Key **mtu**
*Optional*. The L2 MTU of the device. The default is 1514.
By definition, the L2 MTU includes the size of the L2 header, e.g. 14
bytes in case of Ethernet without VLANs. However, the Linux `ioctl`
methods only expose the L3 (IP) MTU, which does not include the L2
header. The following configuration options are used to correct this
— Key **mtu_fixup**
*Optional*. A boolean that indicates whether the **mtu** option should
be corrected for the difference between the L2 and L3 MTU. The
default is _true_.
— Key **mtu_offset**
*Optional*. The value by which the **mtu** is reduced when
**mtu_fixup** is set to _true_. The default is 14.
The resulting MTU is called the _effective_ MTU.
— Key **mtu_set**
*Optional*. Either _nil_ or a boolean that indicates whether the MTU
of the tap device should be set or checked. If **mtu_set** is _true_,
the MTU of the tap device is set to the effective MTU. If **mtu_set**
is false, the effective MTU is compared with the current value of the
MTU of the tap device and an error is raised in case of a mismatch.
If **mtu_set** is _nil_, the MTU is set or checked if the tap device
is ephemeral or persistent, respectively. The rationale is that if
the device is persistent, the entity that created the device is
responsible for the configuration and might not expect or react well
to a change of the MTU.
@@ -7,30 +7,141 @@ local link = require("")
local packet = require("core.packet")
local counter = require("core.counter")
local ethernet = require("lib.protocol.ethernet")
local macaddr = require("lib.macaddress")
local ffi = require("ffi")
local C = ffi.C
local const = require("syscall.linux.constants")
local os = require("os")
local lib = require("core.lib")
local band, bor, bnot =, bit.bor, bit.bnot
local t = S.types.t
Tap = { }
-- The original version of this driver expected the name of the tap
-- device as only configuration option. To be backwards compatible,
-- we don't use the automatic arg checking capability of core.config,
-- hence the name _config instead of config for this table.
Tap._config = {
name = { required = true },
mtu = { default = 1514 },
mtu_fixup = { default = true },
mtu_offset = { default = 14 },
mtu_set = { default = nil },
function Tap:new (name)
assert(name, "missing tap interface name")
-- Get or set the MTU of a tap device. Return the current value.
local function _mtu (sock, ifr, mtu)
local op = "SIOCGIFMTU"
if mtu then
ifr.ivalue = mtu
local ok, err = sock:ioctl(op, ifr)
if not ok then
error(op.." failed for tap device " ..
.. ": " ..tostring(err))
return ifr.ivalue
local sock, err ="/dev/net/tun", "rdwr, nonblock");
assert(sock, "Error opening /dev/net/tun: " .. tostring(err))
-- Get or set the operational status of a tap device. Return the
-- current status.
local function _status (sock, ifr, status)
local ok, err = sock:ioctl("SIOCGIFFLAGS", ifr)
if not ok then
error("Error getting flags for tap device " ..
.. ": " .. tostring(err))
if status ~= nil then
if status == 1 then
-- up
ifr.flags = bor(ifr.flags, const.IFF.UP)
-- down
ifr.flags = band(ifr.flags, bnot(const.IFF.UP))
local ok, err = sock:ioctl("SIOCSIFFLAGS", ifr)
if not ok then
error("Error setting flags for tap device " ..
.. ": " .. tostring(err))
if band(ifr.flags, const.IFF.UP) ~= 0 then
return 1 -- up
return 2 -- down
-- Get the MAC address of a tap device as a int64_t
local function _macaddr (sock, ifr)
local ok, err = sock:ioctl("SIOCGIFHWADDR", ifr)
if not ok then
error("Error getting MAC address for tap device "
.. ..": " .. tostring(err))
local sa = ifr.hwaddr
if sa.sa_family ~= const.ARPHRD.ETHER then
error("Tap interface " ..
.. " is not of type ethernet: " .. sa.sa_family)
return macaddr:new(ffi.cast("uint64_t*", sa.sa_data)[0]).bits
function Tap:new (conf)
-- Backwards compatibility
if type(conf) == "string" then
conf = { name = conf }
conf = lib.parse(conf, self._config)
local ephemeral = not S.stat('/sys/class/net/'
local fd, err ="/dev/net/tun", "rdwr, nonblock");
assert(fd, "Error opening /dev/net/tun: " .. tostring(err))
local ifr = t.ifreq()
ifr.flags = "tap, no_pi" = name
local ok, err = sock:ioctl("TUNSETIFF", ifr) =
local ok, err = fd:ioctl("TUNSETIFF", ifr)
if not ok then
error("ioctl(TUNSETIFF) failed on /dev/net/tun: " .. tostring(err))
return setmetatable({sock = sock,
name = name,
-- A dummy socket to perform SIOC{G,S}IF* ioctl() calls. Any
-- PF/type would do.
local sock, err = S.socket(const.AF.PACKET, const.SOCK.RAW, 0)
if not sock then
error("Error creating ioctl socket for tap device: " .. tostring(err))
if ephemeral then
-- Set status to "up"
_status(sock, ifr, 1)
local mtu_eff = conf.mtu - (conf.mtu_fixup and conf.mtu_offset) or 0
local mtu_set = conf.mtu_set
if mtu_set == nil then
mtu_set = ephemeral
if mtu_set then
_mtu(sock, ifr, mtu_eff)
local mtu_configured = _mtu(sock, ifr)
assert(mtu_configured == mtu_eff,
"Mismatch of IP MTU on tap device " ..
.. ": expected " .. mtu_eff .. ", configured "
.. mtu_configured)
return setmetatable({fd = fd,
sock = sock,
ifr = ifr,
name =,
status_timer = lib.throttle(0.001),
pkt = packet.allocate(),
shm = { rxbytes = {counter},
rxpackets = {counter},
@@ -39,16 +150,28 @@ function Tap:new (name)
txbytes = {counter},
txpackets = {counter},
txmcast = {counter},
txbcast = {counter} }},
{__index = Tap})
txbcast = {counter},
type = {counter, 0x1001}, -- propVirtual
status = {counter, _status(sock, ifr)},
mtu = {counter, conf.mtu},
speed = {counter, 0},
macaddr = {counter, _macaddr(sock, ifr)} }},
{__index = Tap})
function Tap:status()
counter.set(self.shm.status, _status(self.sock, self.ifr))
function Tap:pull ()
local l = self.output.output
if l == nil then return end
if self.status_timer() then
for i=1,engine.pull_npackets do
local len, err =,, C.PACKET_PAYLOAD_SIZE)
-- errno == EAGAIN indicates that the read would of blocked as there is no
local len, err =,, C.PACKET_PAYLOAD_SIZE)
-- errno == EAGAIN indicates that the read would have blocked as there is no
-- packet waiting. It is not a failure.
if not len and err.errno == const.E.AGAIN then
@@ -73,10 +196,10 @@ end
function Tap:push ()
local l = self.input.input
while not link.empty(l) do
-- The socket write might of blocked so don't dequeue the packet from the link
-- The write might have blocked so don't dequeue the packet from the link
-- until the write has completed.
local p = link.front(l)
local len, err = S.write(self.sock,, p.length)
local len, err = S.write(self.fd,, p.length)
-- errno == EAGAIN indicates that the write would of blocked
if not len and err.errno ~= const.E.AGAIN or len and len ~= p.length then
error("Failed write on " .. .. tostring(err))
@@ -99,6 +222,7 @@ function Tap:push ()
function Tap:stop()

0 comments on commit 20d5958

Please sign in to comment.