filter.lua 6.39 KB
Newer Older
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
1
2
--[[
Filter module for Alpine Wall
3
Copyright (C) 2012-2013 Kaarle Ritvanen
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
4
5
6
7
8
9
Licensed under the terms of GPL2
]]--


module(..., package.seeall)

10
11
12
local resolve = require('awall.host').resolve
local model = require('awall.model')
local combinations = require('awall.optfrag').combinations
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
13

14
15
local util = require('awall.util')
local extend = util.extend
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
16
17


18
local Log = model.class()
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
19
20
21
22
23

function Log:matchopts()
   return self.limit and '-m limit --limit '..self.limit..'/second'
end

Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
24
25
26
27
28
function Log:target()
   local mode = self.mode or 'log'
   local prefix = self.prefix and ' --'..mode..'-prefix '..self.prefix or ''
   return string.upper(mode)..prefix
end
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
29

Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
30
31
32

local Filter = model.class(model.Rule)

Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
function Filter:init(...)
   model.Rule.init(self, unpack(arg))

   -- alpine v2.4 compatibility
   if util.contains({'logdrop', 'logreject'}, self.action) then
      self:warning('Deprecated action: '..self.action)
      self.action = string.sub(self.action, 4, -1)
   end

   local function log(spec, default)
      if spec == nil then spec = default end
      if spec == false then return end
      if spec == true then spec = '_default' end
      return self.root.log[spec] or self:error('Invalid log: '..spec)
   end

   self.log = log(self.log, self.action ~= 'accept')
   local limit = self:limit()
   if limit then self[limit].log = log(self[limit].log, true) end
end

Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
54
55
56
57
function Filter:destoptfrags()
   local ofrags = model.Rule.destoptfrags(self)
   if not self.dnat then return ofrags end

58
   ofrags = combinations(ofrags, {{family='inet6'}})
59
   local natof = self:create(model.Zone, {addr=self.dnat}):optfrags('out')
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
60
61
62
63
64
65
66
67
   assert(#natof == 1)
   table.insert(ofrags, natof[1])
   return ofrags
end

function Filter:trules()
   local res = {}

68
69
70
71
72
73
   local function extrarules(cls, extra)
      local params = {}
      for i, attr in ipairs({'in', 'out', 'src', 'dest',
			     'ipset', 'ipsec', 'service'}) do
	 params[attr] = self[attr]
      end
74
      util.update(params, extra)
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
75
      return extend(res, self:create(cls, params):trules())
76
77
   end

Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
78
   if self.dnat then
79
80
81
      if self.action ~= 'accept' then
	 self:error('dnat option not allowed with '..self.action..' action')
      end
82
83
84
      if self['no-track'] then
	 self:error('dnat option not allowed with no-track')
      end
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
85
      if not self.dest then
86
	 self:error('Destination address must be specified with DNAT')
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
87
88
      end
      if string.find(self.dnat, '/') then
89
	 self:error('DNAT target cannot be a network address')
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
90
91
92
      end
      for i, attr in ipairs({'ipsec', 'ipset'}) do
	 if self[attr] then
93
	    self:error('dnat and '..attr..' options cannot be used simultaneously')
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
94
95
96
97
	 end
      end

      local dnataddr
98
      for i, addr in ipairs(resolve(self.dnat, self)) do
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
99
100
	 if addr[1] == 'inet' then
	    if dnataddr then
101
	       self:error(self.dnat..' resolves to multiple IPv4 addresses')
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
102
103
104
105
106
	    end
	    dnataddr = addr[2]
	 end
      end
      if not dnataddr then
107
	 self:error(self.dnat..' does not resolve to any IPv4 address')
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
108
109
      end

110
      extrarules('dnat', {['to-addr']=dnataddr, out=nil})
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
111
112
   end

113
114
115
   if self.action == 'tarpit' or self['no-track'] then
      extrarules('no-track')
   end
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
116

Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
117
   extend(res, model.Rule.trules(self))
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
118

119
120
121
122
123
   if self['no-track'] and self.action == 'accept' then
      extrarules('no-track', {reverse=true})
      extrarules('filter', {reverse=true, action='accept', log=false})
   end

Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
124
125
126
   return res
end

Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
127
128
129
130
131
function Filter:limit()
   local res
   for i, limit in ipairs({'conn-limit', 'flow-limit'}) do
      if self[limit] then
	 if res then
132
	    self:error('Cannot specify multiple limits for a single filter rule')
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
133
134
135
136
137
138
139
140
141
142
143
144
	 end
	 res = limit
      end
   end
   return res
end

function Filter:position()
   return self:limit() == 'flow-limit' and 'prepend' or 'append'
end

function Filter:target()
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
145
146
147
   if self:limit() then return self:newchain('limit') end
   if self.log then return self:newchain('log'..self.action) end
   return model.Rule.target(self)
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
148
149
150
151
end

function Filter:extraoptfrags()
   local res = {}
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
152
153
154
155
156
157
158

   local function logchain(action, log, target)
      extend(res, combinations({{chain=self:newchain('log'..action)}},
			       {{opts=log:matchopts(), target=log:target()},
				{target=target}}))
   end

Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
159
160
161
   local limit = self:limit()
   if limit then
      if self.action ~= 'accept' then
162
	 self:error('Cannot specify limit for '..self.action..' filter')
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
163
      end
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
164
165
166
167
168
169
170
171
172
173
174
175
176

      local chain = self:newchain('limit')
      local limitlog = self[limit].log

      extend(res,
	     combinations({{chain=chain,
			    opts='-m recent --name '..chain}},
			  {{opts='--update --hitcount '..self[limit].count..' --seconds '..self[limit].interval,
				target=limitlog and self:newchain('logdrop') or 'DROP'},
			     {opts='--set',
			      target=self.log and self:newchain('log'..self.action) or 'ACCEPT'}}))

      if limitlog then logchain('drop', limitlog, 'DROP') end
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
177
   end
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
178
179
180

   if self.log then logchain(self.action, self.log, model.Rule.target(self)) end
   
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
181
182
183
184
185
186
187
188
189
190
   return res
end



local Policy = model.class(Filter)

function Policy:servoptfrags() return nil end


191
192
193
local fchains = {{chain='FORWARD'}, {chain='INPUT'}, {chain='OUTPUT'}}

local dar = combinations(fchains,
194
			 {{opts='-m conntrack --ctstate RELATED,ESTABLISHED'}})
195
196
197
198
199
for i, chain in ipairs({'INPUT', 'OUTPUT'}) do
   table.insert(dar,
		{chain=chain,
		 opts='-'..string.lower(string.sub(chain, 1, 1))..' lo'})
end
200
201
202
203
204
dar = combinations(
   dar,
   {{table='filter', target='ACCEPT'}},
   {{family='inet'}, {family='inet6'}}
)
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
205

206
207
local icmp = {{family='inet', table='filter', opts='-p icmp'}}
local icmp6 = {{family='inet6', table='filter', opts='-p icmpv6'}}
208
209
210
211
212
213
214
local ir = combinations(
   icmp6,
   {{chain='INPUT'}, {chain='OUTPUT'}},
   {{target='ACCEPT'}}
)
extend(ir, combinations(icmp6, {{chain='FORWARD', target='icmp-routing'}}))
extend(ir, combinations(icmp, fchains, {{target='icmp-routing'}}))
215
216

local function icmprules(ofrag, oname, types)
217
218
219
220
221
222
223
224
225
   extend(
      ir,
      combinations(ofrag,
		   {{chain='icmp-routing', target='ACCEPT'}},
		   util.map(types,
			    function(t)
			       return {opts='--'..oname..' '..t}
			    end))
   )
226
227
228
end
icmprules(icmp, 'icmp-type', {3, 11, 12})
icmprules(icmp6, 'icmpv6-type', {1, 2, 3, 4})
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
229

230
231
232
233
234
235
236
237
export = {
   filter={class=Filter, before={'dnat', 'no-track'}},
   log={class=Log},
   policy={class=Policy, after='%filter-after'},
   ['%filter-before']={rules=dar, before='filter'},
   ['%filter-after']={rules=ir, after='filter'}
}

Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
238
239
240
achains = combinations({{chain='tarpit'}},
		       {{opts='-p tcp', target='TARPIT'},
			{target='DROP'}})
241