model.lua 18.2 KB
Newer Older
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
1
2
--[[
Base data model for Alpine Wall
3
Copyright (C) 2012-2017 Kaarle Ritvanen
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
4
See LICENSE file for license details
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
5
6
7
]]--


8
local M = {}
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
9
10


11
12
13
14
local loadclass = require('awall').loadclass
M.class = require('awall.class')
local resolve = require('awall.host')
local builtin = require('awall.iptables').builtin
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
15

16
17
local optfrag = require('awall.optfrag')
local combinations = optfrag.combinations
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
18

19
local raise = require('awall.uerror').raise
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
20

21
22
23
24
local util = require('awall.util')
local contains = util.contains
local extend = util.extend
local filter = util.filter
25
local join = util.join
26
local listpairs = util.listpairs
27
local map = util.map
28
local maplist = util.maplist
29
local setdefault = util.setdefault
30

Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
31

32
33
34
35
36
37
local startswith = require('stringy').startswith


M.ConfigObject = M.class()

function M.ConfigObject:init(context, location)
38
39
   if context then
      self.context = context
40
      self.root = context.objects
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
41
   end
42
   self.location = location
43
44

   self.extraobjs = {}
45
   self.uniqueids = {}
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
46
47
end

48
49
50
51
52
53
54
55
function M.ConfigObject:create(cls, params, label, index)
   local key
   if label then
      key = label..(index or '')
      local obj = self.extraobjs[key]
      if obj then return obj end
   end

56
57
   if type(cls) == 'string' then
      local name = cls
58
      cls = loadclass(cls)
59
60
61
62
      if not cls then
	 self:error('Support for '..name..' objects not installed')
      end
   end
63

64
   if type(params) ~= 'table' then params = {params} end
65
   params.label = join(self.label, '-', label)
66

67
68
69
   local obj = cls.morph(params, self.context, self.location)
   if key then self.extraobjs[key] = obj end
   return obj
70
71
end

72
function M.ConfigObject:uniqueid(key)
73
   if not key then key = '' end
74
75
   if self.uniqueids[key] then return self.uniqueids[key] end

76
   local lastid = setdefault(self.context, 'lastid', {})
77
   local res = join(key, '-', self.label)
78
   lastid[res] = setdefault(lastid, res, -1) + 1
79
80
81
82
83
84
   res = res..'-'..lastid[res]

   self.uniqueids[key] = res
   return res
end

85
function M.ConfigObject:error(msg) raise(self.location..': '..msg) end
86

87
function M.ConfigObject:warning(msg)
88
   util.printmsg(self.location..': '..msg)
89
90
end

91
function M.ConfigObject:trules() return {} end
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
92

93
function M.ConfigObject:info()
94
95
   local res = {}
   for i, trule in ipairs(self:trules()) do
96
      table.insert(res, {'  '..optfrag.location(trule), optfrag.command(trule)})
97
98
99
100
   end
   return res
end

Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
101

102
M.Zone = M.class(M.ConfigObject)
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
103

104
function M.Zone:optfrags(dir)
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
105
106
107
108
109
110
111
   local iopt, aopt, iprop, aprop
   if dir == 'in' then
      iopt, aopt, iprop, aprop = 'i', 's', 'in', 'src'
   elseif dir == 'out' then
      iopt, aopt, iprop, aprop = 'o', 'd', 'out', 'dest'
   else assert(false) end

Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
112
113
114
   local aopts = nil
   if self.addr then
      aopts = {}
115
116
      for i, hostdef in listpairs(self.addr) do
	 for i, addr in ipairs(resolve(hostdef, self)) do
117
118
119
120
	    table.insert(
	       aopts,
	       {family=addr[1], [aprop]=addr[2], match='-'..aopt..' '..addr[2]}
	    )
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
121
	 end
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
122
123
124
      end
   end

125
126
127
128
   local popt
   if self.ipsec ~= nil then
      popt = {
	 {
129
	    match='-m policy --dir '..dir..' --pol '..
130
131
132
133
134
	       (self.ipsec and 'ipsec' or 'none')
	 }
      }
   end

135
136
137
   return combinations(
      maplist(
	 self.iface,
138
	 function(x) return {[iprop]=x, match='-'..iopt..' '..x} end
139
      ),
140
141
      aopts,
      popt
142
   )
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
143
144
145
end


146
M.fwzone = M.Zone()
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
147
148


149
local IPSet = M.class(M.ConfigObject)
150
151

function IPSet:init(...)
152
   IPSet.super(self):init(...)
153
154
155

   if not self.type then self:error('Type not defined') end

156
   if startswith(self.type, 'bitmap:') then
157
158
159
160
      if not self.range then self:error('Range not defined') end
      self.options = {self.type, 'range', self.range}
      self.family = 'inet'

161
   elseif startswith(self.type, 'hash:') then
162
163
164
165
166
167
168
169
170
      if not self.family then self:error('Family not defined') end
      self.options = {self.type, 'family', self.family}

   elseif self.type == 'list:set' then self.options = {self.type}

   else self:error('Invalid type: '..self.type) end
end


171
M.Rule = M.class(M.ConfigObject)
172

Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
173

174
175
function M.Rule:init(...)
   M.Rule.super(self):init(...)
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
176
177

   for i, prop in ipairs({'in', 'out'}) do
178
179
180
181
182
183
184
185
186
      self[prop] = self[prop] and maplist(
	 self[prop],
	 function(z)
	    if type(z) ~= 'string' then return z end
	    return z == '_fw' and M.fwzone or
	       self.root.zone[z] or
	       self:error('Invalid zone: '..z)
	 end
      )
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
187
   end
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
188

189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
   -- alpine v3.4 compatibility
   if self.ipsec then
      if not contains({'in', 'out'}, self.ipsec) then
	 self:error('Invalid ipsec policy direction')
      end
      self:warning('ipsec deprecated in rules, define in zones instead')
      local zones = self[self.ipsec]
      if zones then
	 self[self.ipsec] = maplist(
	    zones,
	    function(z)
	       return self:create(
		  M.Zone, {iface=z.iface, addr=z.addr, ipsec=true}
	       )
	    end
	 )
      else self[self.ipsec] = {self:create(M.Zone, {ipsec=true})} end
      self.ipsec = nil
   end

Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
209
   if self.service then
210
211
212
213
      if not self.label and type(self.service) == 'string' then
	 self.label = self.service
      end

214
215
216
217
218
219
      self.service = util.list(self.service)

      for i, serv in ipairs(self.service) do
	 if type(serv) == 'string' then
	    self.service[i] = self.root.service[serv] or
	       self:error('Invalid service: '..serv)
220
	 end
221
222
223
224
225
226
227
	 for i, sdef in listpairs(self.service[i]) do
	    if not sdef.proto then self:error('Protocol not defined') end
	    sdef.proto = (
	       {[1]='icmp', [6]='tcp', [17]='udp', [58]='ipv6-icmp'}
            )[sdef.proto] or sdef.proto
	 end
      end
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
228
229
230
231
   end
end


232
function M.Rule:direction(dir)
233
234
235
236
237
238
   if dir == 'in' then return self.reverse and 'out' or 'in' end
   if dir == 'out' then return self.reverse and 'in' or 'out' end
   self:error('Invalid direction: '..dir)
end


239
function M.Rule:zoneoptfrags()
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
240

241
   local function zonepair(zin, zout)
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
242

243
      local function zofs(zone, dir)
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
244
	 if not zone then return zone end
245
	 return zone:optfrags(dir)
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
246
247
248
249
      end

      local chain, ofrags

250
      if zin == M.fwzone or zout == M.fwzone then
251
	 if zin == zout then return {} end
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
252
	 local dir, z = 'in', zin
253
	 if zin == M.fwzone then dir, z = 'out', zout end
254
	 chain = dir:upper()..'PUT'
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
255
256
	 ofrags = zofs(z, dir)

257
258
259
260
261
262
263
264
265
266
267
      elseif not zin or not zout then

	 if zin then
	    chain = 'PREROUTING'
	    ofrags = zofs(zin, 'in')

	 elseif zout then
	    chain = 'POSTROUTING'
	    ofrags = zofs(zout, 'out')
	 end

Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
268
269
      else
	 chain = 'FORWARD'
270
271
	 ofrags = combinations(zofs(zin, 'in'), zofs(zout, 'out'))

272
	 if ofrags and not zout['route-back'] then
273
274
275
276
277
278
	    ofrags = filter(
	       ofrags,
	       function(of)
		  return not (of['in'] and of.out and of['in'] == of.out)
	       end
	    )
279
	 end
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
280
281
      end

282
283
284
      return combinations(ofrags,
			  chain and {{chain=chain}} or {{chain='PREROUTING'},
							{chain='OUTPUT'}})
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
285
286
287
   end

   local res = {}
288
289
   local izones = self[self:direction('in')] or {}
   local ozones = self[self:direction('out')] or {}
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
290

291
292
   for i = 1,math.max(1, table.maxn(izones)) do
      for j = 1,math.max(1, table.maxn(ozones)) do
293
	 extend(res, zonepair(izones[i], ozones[j]))
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
294
295
296
297
298
299
300
      end
   end

   return res
end


301
function M.Rule:servoptfrags()
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
302
303
304

   if not self.service then return end

305
   local fports = {inet={}, inet6={}}
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
306
307
308
   local res = {}

   for i, serv in ipairs(self.service) do
309
      for i, sdef in listpairs(serv) do
310
	 if contains({'tcp', 'udp'}, sdef.proto) then
311
312
313
314
315
316
317
318
	    for family, ports in pairs(fports) do
	       if not sdef.family or family == sdef.family then

		  local new = not ports[sdef.proto]
		  if new then ports[sdef.proto] = {} end

		  if new or ports[sdef.proto][1] then
		     if sdef.port then
319
			extend(
320
			   ports[sdef.proto],
321
			   maplist(
322
			      sdef.port,
323
			      function(p) return tostring(p):gsub('-', ':') end
324
325
326
327
328
			   )
			)
		     else ports[sdef.proto] = {} end
		  end
	       end
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
329
330
331
332
333
334
335
	    end

	 else

	    local opts = '-p '..sdef.proto
	    local family = nil

336
337
	    -- TODO multiple ICMP types per rule
	    local oname
338
	    if sdef.proto == 'icmp' then
339
340
	       family = 'inet'
	       oname = 'icmp-type'
341
	    elseif contains({'ipv6-icmp', 'icmpv6'}, sdef.proto) then
342
343
	       family = 'inet6'
	       oname = 'icmpv6-type'
344
	    elseif sdef.type or sdef['reply-type'] then
345
	       self:error('Type specification not valid with '..sdef.proto)
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
346
347
	    end

348
349
350
351
352
353
354
355
356
	    if sdef.family then
	       if not family then family = sdef.family
	       elseif family ~= sdef.family then
		  self:error(
		     'Protocol '..sdef.proto..' is incompatible with '..sdef.family
		  )
	       end
	    end

357
358
	    if sdef.type then
	       opts = opts..' --'..oname..' '..(
359
		  self.reverse and sdef['reply-type'] or sdef.type
360
361
	       )
	    end
362
	    table.insert(res, {family=family, match=opts})
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
363
364
365
366
	 end
      end
   end

367
   local popt = ' --'..(self.reverse and 's' or 'd')..'port'
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
   for family, pports in pairs(fports) do
      local ofrags = {}

      for proto, ports in pairs(pports) do
	 local propt = '-p '..proto

	 if ports[1] then
	    local len = #ports
	    repeat
	       local opts

	       if len == 1 then
		  opts = propt..popt..' '..ports[1]
		  len = 0

	       else
		  opts = propt..' -m multiport'..popt..'s '
		  local pc = 0
		  repeat
		     local sep = pc == 0 and '' or ','
		     local port = ports[1]
		     
390
		     pc = pc + (port:find(':') and 2 or 1)
391
392
393
394
395
396
397
398
399
		     if pc > 15 then break end
		     
		     opts = opts..sep..port
		     
		     table.remove(ports, 1)
		     len = len - 1
		  until len == 0
	       end

400
	       table.insert(ofrags, {match=opts})
401
402
	    until len == 0

403
	 else table.insert(ofrags, {match=propt}) end
404
      end
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
405

406
      extend(res, combinations(ofrags, {{family=family}}))
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
407
408
409
410
411
   end

   return res
end

412
413
function M.Rule:destoptfrags()
   return self:create(M.Zone, {addr=self.dest}):optfrags(self:direction('out'))
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
414
415
end

416
function M.Rule:table() return 'filter' end
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
417

418
function M.Rule:position() return 'append' end
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
419

420
function M.Rule:target()
421
422
423
424
425
426
427
428
429
430
   -- alpine v2.7 compatibility
   if self.action == 'accept' then
      self:warning("'accept' action deprecated in favor of 'exclude'")
      self.action = 'exclude'
   end

   if self.action == 'exclude' then return 'ACCEPT' end
   if self.action and self.action ~= 'include' then
      self:error('Invalid action: '..self.action)
   end
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
431
432
433
end


434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
function M.Rule:combine(ofs1, ofs2, key, unique)

   local function connect()
      local chain = self:uniqueid(key)
      local function setvar(name)
	 return function(of)
	    setdefault(of, name, chain)
	    return of
	 end
      end

      return extend(map(ofs1, setvar('target')), map(ofs2, setvar('chain')))
   end

   local chainless = filter(ofs2, function(of) return not of.chain end)
   local created = {}
   local res = {}

   for _, of in ipairs(ofs1) do
      if of.target == nil then

	 local ofs = combinations(chainless, {{family=of.family}})
	 assert(#ofs > 0)

	 if unique then
	    assert(of.family)
	    if created[of.family] then return connect() end
	    created[of.family] = true

	    if #ofs > 1 then return connect() end
	 end

	 local comb = combinations({of}, ofs)
	 if #comb < #ofs then return connect() end
	 extend(res, comb)

      else table.insert(res, of) end
   end

   return res
end


477
function M.Rule:trules()
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
478

479
   local function tag(ofrags, tag, value)
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
480
481
482
483
484
485
486
487
      for i, ofrag in ipairs(ofrags) do
	 assert(not ofrag[tag])
	 ofrag[tag] = value
      end
   end

   local families

488
   local function setfamilies(ofrags)
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
489
490
491
492
493
494
495
496
497
498
499
500
      if ofrags then
	 families = {}
	 for i, ofrag in ipairs(ofrags) do
	    if not ofrag.family then
	       families = nil
	       return
	    end
	    table.insert(families, ofrag.family)
	 end
      else families = nil end
   end

501
   local function ffilter(ofrags)
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
502
      if not ofrags or not ofrags[1] or not families then return ofrags end
503
504
505
506
507
508
      return filter(
	 ofrags,
	 function(of)
	    return not of.family or contains(families, of.family)
	 end
      )
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
509
510
   end

511
   local ofrags = self:zoneoptfrags()
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
512

513
   if self.ipset then
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
514
      local ipsetofrags = {}
515
      for i, ipset in listpairs(self.ipset) do
516
	 if not ipset.name then self:error('Set name not defined') end
517

Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
518
	 local setdef = self.root.ipset and self.root.ipset[ipset.name]
519
	 if not setdef then self:error('Invalid set name') end
520

Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
521
	 if not ipset.args then
522
	    self:error('Set direction arguments not defined')
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
523
	 end
524

Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
525
	 local setopts = '-m set --match-set '..ipset.name..' '
526
527
	 setopts = setopts..table.concat(util.map(util.list(ipset.args),
						  function(a)
528
529
530
531
						     if self:direction(a) == 'in' then
							return 'src'
						     end
						     return 'dst'
532
533
						  end),
					 ',')
534
	 table.insert(ipsetofrags, {family=setdef.family, match=setopts})
535
      end
536
      ofrags = combinations(ofrags, ipsetofrags)
537
538
   end

Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
   if self.string then
      if type(self.string) == 'string' then
	 self.string = {match=self.string}
      end
      if not self.string.match then self:error('String match not defined') end
      setdefault(self.string, 'algo', 'bm')

      local opts = '-m string --string "'..
	 self.string.match:gsub('(["\\])', '\\%1')..'"'

      for _, attr in ipairs{'algo', 'from', 'to'} do
	 if self.string[attr] then
	    opts = opts..' --'..attr..' '..self.string[attr]
	 end
      end

      ofrags = combinations(ofrags, {{match=opts}})
   end

558
   if self.match then ofrags = combinations(ofrags, {{match=self.match}}) end
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
559

560
   ofrags = combinations(ofrags, self:servoptfrags())
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
561

562
563
   tag(ofrags, 'position', self:position())

564
   setfamilies(ofrags)
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
565

566
567
568
569
   local addrofrags = combinations(
      self:create(M.Zone, {addr=self.src}):optfrags(self:direction('in')),
      self:destoptfrags()
   )
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
570
571
572
   if addrofrags then
      addrofrags = ffilter(addrofrags)
      setfamilies(addrofrags)
573
      ofrags = self:combine(ffilter(ofrags), addrofrags, 'address')
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
574
575
   end

576
   ofrags = self:mangleoptfrags(ofrags)
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
577

578
579
   local custom = self:customtarget()
   for _, ofrag in ipairs(ofrags) do
580
      setdefault(ofrag, 'target', custom or self:target())
581
   end
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
582

583
584
585
586
587
588
589
   local tbl = self:table()

   local function convertchains(ofrags)
      local res = {}

      for i, ofrag in ipairs(ofrags) do

590
	 if contains(builtin[tbl], ofrag.chain) then table.insert(res, ofrag)
591
	 else
592
593
594
	    local ofs, recursive
	    if ofrag.chain == 'PREROUTING' then
	       ofs = {{chain='FORWARD'}, {chain='INPUT'}}
595
	    elseif ofrag.chain == 'POSTROUTING' then
596
597
598
	       ofs = {{chain='FORWARD'}, {chain='OUTPUT'}}
	       recursive = true
	    elseif ofrag.chain == 'INPUT' then
599
600
601
	       ofs = {
	          {match='-m addrtype --dst-type LOCAL', chain='PREROUTING'}
	       }
602
603
	    elseif ofrag.chain == 'FORWARD' then
	       ofs = {
604
		  {match='-m addrtype ! --dst-type LOCAL', chain='PREROUTING'}
605
	       }
606
607
	    end

608
	    if ofs then
609
	       ofrag.chain = nil
610
611
	       ofs = combinations(ofs, {ofrag})
	       if recursive then ofs = convertchains(ofs) end
612
	       extend(res, ofs)
613

614
615
616
617
618
619
620
	    else table.insert(res, ofrag) end
	 end
      end

      return res
   end

621
622
   ofrags = convertchains(ffilter(ofrags))
   tag(ofrags, 'table', tbl, false)
623
624

   local function checkzof(ofrag, dir, chains)
625
      if ofrag[dir] and contains(chains, ofrag.chain) then
626
627
628
629
	 self:error('Cannot specify '..dir..'bound interface ('..ofrag[dir]..')')
      end
   end

630
   for i, ofrag in ipairs(ofrags) do
631
632
633
      checkzof(ofrag, 'in', {'OUTPUT', 'POSTROUTING'})
      checkzof(ofrag, 'out', {'INPUT', 'PREROUTING'})
   end
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
634
   
635
636
637
638
   ofrags = filter(
      combinations(ofrags, ffilter({{family='inet'}, {family='inet6'}})),
      function(r) return self:trulefilter(r) end
   )
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
639

640
   local extra = self:extratrules(ofrags)
641
   if custom and extra[1] then self:error('Custom action not allowed here') end
642
   return extend(ofrags, extra)
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
643
644
645
646
647
end

function M.Rule:customtarget()
   if self.action then
      local as = self.action:sub(1, 1)
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
648
649
650
      if as == as:upper() or startswith(self.action, 'custom:') then
	 return self.action
      end
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
651
   end
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
652
653
end

654
function M.Rule:mangleoptfrags(ofrags) return ofrags end
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
655

656
657
658
659
function M.Rule:trulefilter(rule) return true end

function M.Rule:extratrules(rules) return {} end

660
661
662
663
function M.Rule:extrarules(label, cls, options)
   local params = {}

   for _, attr in ipairs(
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
664
      extend(
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
665
         {'in', 'out', 'src', 'dest', 'ipset', 'string', 'match', 'service'},
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
666
667
	 options.attrs
      )
668
669
670
671
672
673
674
675
676
677
   ) do
      params[attr] = (options.src or self)[attr]
   end

   util.update(params, options.update)
   if options.discard then params[options.discard] = nil end

   return self:create(cls, params, label, options.index):trules()
end

Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
678

679
680
681
682
683
684
685
686
687
688
689
690
M.Limit = M.class(M.ConfigObject)

function M.Limit:init(...)
   M.Limit.super(self):init(...)

   if not self.count then
      if not self[1] then
	 self:error('Packet count not defined for limit')
      end
      self.count = self[1]
   end

691
   setdefault(self, 'interval', 1)
692
693
694
695

   if type(setdefault(self, 'mask', {})) == 'number' then
      self.mask = {src=self.mask}
   end
696
   for _, family in ipairs{'inet', 'inet6'} do
697
      setdefault(self.mask, family, util.copy(self.mask))
698
      for _, attr in ipairs{'src', 'dest'} do
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
699
	 setdefault(
700
701
702
703
	    self.mask[family],
	    attr,
	    ({src=({inet=32, inet6=128})[family], dest=0})[attr]
	 )
704
705
      end
   end
706
707
end

Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
708
709
710
711
712
713
714
715
716
717
718
719
function M.Limit:maskmode(family)
   local res
   for _, attr in ipairs{'src', 'dest'} do
      local mask = self.mask[family][attr]
      if mask > 0 then
	 if res then return end
	 res = {attr, mask}
      end
   end
   if res then return table.unpack(res) end
end

Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
720
721
722
function M.Limit:rate() return self.count / self.interval end

function M.Limit:intrate() return math.ceil(self:rate()) end
723

724
function M.Limit:limitofrags(name)
725
   local rate = self:rate()
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
726
727
728
729
730
731
732
733
734
735
   local unit
   for _, quantum in ipairs{
      {1, 'second'}, {60, 'minute'}, {60, 'hour'}, {24, 'day'}
   } do
      rate = rate * quantum[1]
      unit = quantum[2]
      if rate >= 1 then break end
   end
   rate = math.ceil(rate)..'/'..unit

736
737
738
739
740
   local ofrags = {}

   for _, family in ipairs{'inet', 'inet6'} do
      local keys = {}
      local maskopts = ''
741
      for _, attr in ipairs{'src', 'dest'} do
742
743
	 local mask = self.mask[family][attr]
	 if mask > 0 then
744
	    local opt = ({src='src', dest='dst'})[attr]
745
746
747
748
749
750
751
752
753
	    table.insert(keys, opt..'ip')
	    maskopts = maskopts..' --hashlimit-'..opt..'mask '..mask
	 end
      end

      table.insert(
	 ofrags,
	 {
	    family=family,
754
	    match=keys[1] and
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
755
756
757
758
	       '-m hashlimit --hashlimit-upto '..rate..' --hashlimit-burst '..
	       self:intrate()..' --hashlimit-mode '..table.concat(keys, ',')..
	       maskopts..' --hashlimit-name '..(name or self:uniqueid()) or
	       '-m limit --limit '..rate
759
760
761
762
763
	 }
      )
   end

   return ofrags
764
765
766
end


Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
767
768
769
770
771
M.export = {
   custom={class=M.ConfigObject},
   ipset={class=IPSet, before='%modules'},
   zone={class=M.Zone}
}
772
773

return M