-
Notifications
You must be signed in to change notification settings - Fork 298
/
tap.lua
274 lines (252 loc) · 9.05 KB
/
tap.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
-- Use of this source code is governed by the Apache 2.0 license; see COPYING.
module(..., package.seeall)
local S = require("syscall")
local link = require("core.link")
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.band, 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 },
overwrite_src_mac = { default = false }
}
-- 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
op = "SIOCSIFMTU"
ifr.ivalue = mtu
end
local ok, err = sock:ioctl(op, ifr)
if not ok then
error(op.." failed for tap device " .. ifr.name
.. ": " ..tostring(err))
end
return ifr.ivalue
end
-- 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 " .. ifr.name
.. ": " .. tostring(err))
end
if status ~= nil then
if status == 1 then
-- up
ifr.flags = bor(ifr.flags, const.IFF.UP)
else
-- down
ifr.flags = band(ifr.flags, bnot(const.IFF.UP))
end
local ok, err = sock:ioctl("SIOCSIFFLAGS", ifr)
if not ok then
error("Error setting flags for tap device " .. ifr.name
.. ": " .. tostring(err))
end
else
if band(ifr.flags, const.IFF.UP) ~= 0 then
return 1 -- up
else
return 2 -- down
end
end
end
-- Get the MAC address of a tap device as a int64_t
local function _macaddr (sock, ifr, mac)
mac = mac or macaddr:new(0)
local ok, err = sock:ioctl("SIOCGIFHWADDR", ifr)
if not ok then
error("Error getting MAC address for tap device "
.. ifr.name ..": " .. tostring(err))
end
local sa = ifr.hwaddr
if sa.sa_family ~= const.ARPHRD.ETHER then
error("Tap interface " .. ifr.name
.. " is not of type ethernet: " .. sa.sa_family)
else
ffi.copy(mac.bytes, sa.sa_data, ffi.sizeof(mac.bytes))
return mac
end
end
function Tap:new (conf)
-- Backwards compatibility
if type(conf) == "string" then
conf = { name = conf }
end
conf = lib.parse(conf, self._config)
local ephemeral = not S.stat('/sys/class/net/'..conf.name)
local fd, err = S.open("/dev/net/tun", "rdwr, nonblock");
assert(fd, "Error opening /dev/net/tun: " .. tostring(err))
local ifr = t.ifreq()
ifr.flags = "tap, no_pi"
ifr.name = conf.name
local ok, err = fd:ioctl("TUNSETIFF", ifr)
if not ok then
fd:close()
error("ioctl(TUNSETIFF) failed on /dev/net/tun: " .. tostring(err))
end
-- 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
fd:close()
error("Error creating ioctl socket for tap device: " .. tostring(err))
end
if ephemeral then
-- Set status to "up"
_status(sock, ifr, 1)
end
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
end
if mtu_set then
_mtu(sock, ifr, mtu_eff)
else
local mtu_configured = _mtu(sock, ifr)
assert(mtu_configured == mtu_eff,
"Mismatch of IP MTU on tap device " .. conf.name
.. ": expected " .. mtu_eff .. ", configured "
.. mtu_configured)
end
local mac = _macaddr(sock, ifr)
return setmetatable({fd = fd,
sock = sock,
ifr = ifr,
mac = mac,
name = conf.name,
status_timer = lib.throttle(0.1),
pkt = packet.allocate(),
eth = ethernet:new{},
overwrite_src_mac = conf.overwrite_src_mac,
shm = { rxbytes = {counter},
rxpackets = {counter},
rxmcast = {counter},
rxbcast = {counter},
txbytes = {counter},
txpackets = {counter},
txmcast = {counter},
txbcast = {counter},
type = {counter, 0x1001}, -- propVirtual
status = {counter, _status(sock, ifr)},
mtu = {counter, conf.mtu},
speed = {counter, 0},
macaddr = {counter, mac.bits} }},
{__index = Tap})
end
function Tap:status()
counter.set(self.shm.status, _status(self.sock, self.ifr))
counter.set(self.shm.macaddr, _macaddr(self.sock, self.ifr, self.mac).bits)
end
function Tap:tick ()
if self.status_timer() then
self:status()
end
end
function Tap:pull ()
local l = self.output.output
if l == nil then return end
for i=1,engine.pull_npackets do
local len, err = S.read(self.fd, self.pkt.data, 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
return
end
if not len then
error("Failed read on " .. self.name .. ": " .. tostring(err))
end
self.pkt.length = len
link.transmit(l, self.pkt)
counter.add(self.shm.rxbytes, len)
counter.add(self.shm.rxpackets)
if ethernet:is_mcast(self.pkt.data) then
counter.add(self.shm.rxmcast)
end
if ethernet:is_bcast(self.pkt.data) then
counter.add(self.shm.rxbcast)
end
self.pkt = packet.allocate()
end
end
function Tap:set_src_mac (p)
local eth = self.eth:new_from_mem(p.data, p.length)
eth:src(self.mac.bytes)
end
function Tap:push ()
local l = self.input.input
while not link.empty(l) do
-- The write might have blocked so don't dequeue the packet from the link
-- until the write has completed.
local p = link.front(l)
if self.overwrite_src_mac then
self:set_src_mac(p)
end
local len, err = S.write(self.fd, p.data, 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 " .. self.name .. tostring(err))
end
if len ~= p.length and err.errno == const.E.AGAIN then
return
end
counter.add(self.shm.txbytes, len)
counter.add(self.shm.txpackets)
if ethernet:is_mcast(p.data) then
counter.add(self.shm.txmcast)
end
if ethernet:is_bcast(p.data) then
counter.add(self.shm.txbcast)
end
-- The write completed so dequeue it from the link and free the packet
link.receive(l)
packet.free(p)
end
end
function Tap:stop()
self.fd:close()
self.sock:close()
end
function selftest()
-- tapsrc and tapdst are bridged together in linux. Packets are sent out of tapsrc and they are expected
-- to arrive back on tapdst.
-- The linux bridge does mac address learning so some care must be taken with the preparation of selftest.cap
-- A mac address should appear only as the source address or destination address
-- This test should only be run from inside apps/tap/selftest.sh
if not os.getenv("SNABB_TAPTEST") then os.exit(engine.test_skipped_code) end
local Synth = require("apps.test.synth").Synth
local Match = require("apps.test.match").Match
local c = config.new()
config.app(c, "tap_in", Tap, "tapsrc")
config.app(c, "tap_out", Tap, "tapdst")
config.app(c, "match", Match, {fuzzy=true,modest=true})
config.app(c, "comparator", Synth, {dst="00:50:56:fd:19:ca",
src="00:0c:29:3e:ca:7d"})
config.app(c, "source", Synth, {dst="00:50:56:fd:19:ca",
src="00:0c:29:3e:ca:7d"})
config.link(c, "comparator.output->match.comparator")
config.link(c, "source.output->tap_in.input")
config.link(c, "tap_out.output->match.rx")
engine.configure(c)
engine.main({duration = 0.05, report = {showapps=true,showlinks=true}})
assert(#engine.app_table.match:errors() == 0)
end