-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathlinux_if.lua
321 lines (270 loc) · 8.47 KB
/
linux_if.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
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
#!/usr/bin/env lua
-- -*-lua-*-
--
-- $Id: linux_if.lua $
--
-- Author: Markus Stenberg <[email protected]>
--
-- Copyright (c) 2012 cisco Systems, Inc.
--
-- Created: Mon Oct 8 13:11:02 2012 mstenber
-- Last modified: Thu Jun 27 10:58:59 2013 mstenber
-- Edit time: 124 min
--
-- wrapper library around 'ip' and 'ifconfig'
--
-- core ideas:
-- - cache things as long as we feel they're relevant
-- - use shell command given, and parse outputs
-- (could read /proc, or use netlink if we were fancy)
local mst = require 'mst'
module(..., package.seeall)
--- if_object
if_object = mst.create_class{class='if_object', mandatory={'name'}}
function if_object:get_hwaddr()
local ifname = self.name
if self.hwaddr
then
return self.hwaddr
end
local s = self.parent.shell(string.format('ifconfig %s | grep HWaddr', ifname))
if not s
then
return nil, 'unable to get'
end
s = mst.string_strip(s)
local i1, i2, r = string.find(s, 'HWaddr ([0-9a-fA-F:]+)%s*$')
if not r
then
return nil, 'unable to parse ' .. s
end
self.hwaddr = r
return r
end
function if_object:add_ipv6(addr)
self.ipv6_valid = false
return self.parent.shell(string.format('ip -6 addr add %s dev %s', addr, self.name))
end
function if_object:del_ipv6(addr)
self.ipv6_valid = false
return self.parent.shell(string.format('ip -6 addr del %s dev %s', addr, self.name))
end
function if_object:set_ipv4(addr, netmask)
self.ipv4_valid = false
return self.parent.shell(string.format('ifconfig %s %s netmask %s', self.name, addr, netmask))
end
--- if_table
if_table = mst.create_class{class='if_table', mandatory={'shell'}}
function if_table:init()
self.map = mst.map:new{}
self.vs = mst.validity_sync:new{t=self.map, single=false}
end
-- this is really get-or-set operation..
function if_table:get_if(k)
self:a(k, 'get_if nil if!')
-- in certain cases, may have eth0.1@eth0
-- => we care only about eth0.1
k = mst.string_split(k, '@')[1]
local r = self.map[k]
if not r
then
r = if_object:new{name=k, parent=self}
self.map[k] = r
end
return r
end
local AF_IPV4='ipv4'
local AF_IPV6='ipv6'
function if_table:read_ip_ipv6()
-- invalidate all interfaces first
self.vs:clear_all_valid(AF_IPV6)
local s, err = self.shell("ip -6 addr | egrep '(^[0-9]| scope global)' | grep -v temporary")
mst.a(s, 'unable to execute ip -6 addr', err)
local ifo = nil
local lines = mst.string_split(s, '\n')
-- filter empty lines
lines = lines:filter(function (line) return #mst.string_strip(line)>0 end)
for i, line in ipairs(lines)
do
mst.string_find_one(line,
-- case 1: <num>: <ifname>:
'^%d+: (%S+): ',
function (ifname)
ifo = self:get_if(ifname)
ifo.ipv6 = mst.array:new{}
ifo.ipv6_valid = true
self.vs:set_valid(ifo, AF_IPV6)
end,
-- case 2: <spaces> inet6 <addr>/64
'^%s+inet6 (%S+/64)%s',
function (addr)
ifo.ipv6:insert(addr)
end,
-- case 3: other inet6 stuff we ignore
'^%s+inet6',
nil
)
end
-- remove non-valid interface objects
self.vs:remove_all_invalid()
return self.map
end
function if_table:read_ip_ipv4()
-- invalidate all interfaces at the outset
self.vs:clear_all_valid(AF_IPV4)
local s, err = self.shell("ip -4 addr")
mst.a(s, 'unable to execute ip -4 addr', err)
local ifo = nil
local lines = mst.string_split(s, '\n')
-- filter empty lines
lines = lines:filter(function (line) return #mst.string_strip(line)>0 end)
for i, line in ipairs(lines)
do
mst.string_find_one(line,
-- case 1: <num>: <ifname>:
'^%d+: (%S+): ',
function (ifname)
ifo = self:get_if(ifname)
ifo.ipv4 = nil
ifo.ipv4_valid = true
self.vs:set_valid(ifo, AF_IPV4)
end,
-- case 2: <spaces> inet <addr>/<mask>
'^%s+inet (%S+/%d+)%s',
function (addr)
if not ipv4s.address_is_loopback(addr)
then
ifo.ipv4 = addr
end
end,
-- case 3: lifetime stuff (ignored)
'^%s+valid_lft.*',
function ()
end
)
end
-- remove non-valid interrface objects
self.vs:remove_all_invalid()
return self.map
end
-- route parsing
function parse_routes(text)
local r = mst.array:new()
for i, line in ipairs(mst.string_split(text, '\n'))
do
line = mst.string_strip(line)
if #line > 0
then
-- destination is the first thing
local t = {}
t.dst = mst.string_split(line, ' ')[1]
for k, v in pairs{via=1, dev=1, metric=2, expires=1, dead=0}
do
if v == 1
then
local r = {string.find(line, '%s' .. k .. ' (%S+)')}
if r and #r > 2
then
t[k] = r[3]
end
elseif v == 2
then
local r = {string.find(line, '%s' .. k .. ' (%S+)')}
if r and #r > 2
then
t[k] = tonumber(r[3])
end
else
mst.a(v == 0)
local r = {string.find(line, '%s' .. k)}
if r and #r == 2
then
t[k] = true
end
end
end
r:insert(t)
end
end
return r
end
function get_ip6_routes(sh, table)
table = table and (' table ' .. table) or ''
return parse_routes(sh('ip -6 route' .. table))
end
--- rule
rule = mst.create_class{class='rule', mandatory={'pref', 'sel', 'table'}}
function rule:del(sh)
self:apply(sh, 'del')
end
function rule:add(sh)
self:apply(sh, 'add')
end
function rule:apply(sh, op)
sh(string.format('ip -6 rule %s %s table %s pref %d',
op, self.sel, self.table, self.pref))
end
--- rule_table
--- wrapper around the AF-specific ip rules
--- no real keys, but priority+conditions should be unique (I think)
rule_table = mst.array:new_subclass{class='rule_table', mandatory={'shell'},
start_table=1000}
function rule_table:init()
self.vs = mst.validity_sync:new{t=self, single=true}
end
function rule_table:find(criteria)
for _, o in ipairs(self)
do
--self:d(' considering', o)
if mst.table_contains(o, criteria) then return o end
end
self:d('not found in', #self)
end
function rule_table:parse()
-- start by invalidating current objects
self.vs:clear_all_valid()
local s, err = self.shell("ip -6 rule")
mst.a(s, 'unable to execute ip -6 rule', err)
local lines = mst.string_split(s, '\n')
-- filter empty lines
lines = lines:filter(function (line) return #mst.string_strip(line)>0 end)
self:d('parsing lines', #lines)
for i, line in ipairs(lines)
do
line = mst.string_strip(line)
local function handle_line(pref, sel, table)
pref = tonumber(pref)
local o = {pref=pref, sel=sel, table=table}
local r = self:find(o)
if not r
then
r = self:add_rule(o)
else
self:d('already had?', r)
end
self.vs:set_valid(r)
end
--self:d('line', line)
mst.string_find_one(line,
'^(%d+):%s+(from %S+)%s+lookup (%S+)$',
handle_line,
'^(%d+):%s+(from all to %S+)%s+lookup (%S+)$',
handle_line)
end
-- get rid of non-valid entries
self.vs:remove_all_invalid()
end
function rule_table:add_rule(criteria)
local r = rule:new(criteria)
self:d('adding rule', r)
self:insert(r)
return r
end
function rule_table:get_free_table()
local t = self.start_table
while self:find{table=tostring(t)}
do
t = t + 1
end
return tostring(t)
end