add some indirection around make targets of module.mk, so you can combine it more...
[project/luci.git] / libs / core / src / sys / iptparser.lua
1 --[[
2 LuCI - Iptables parser and query library
3
4 Copyright 2008 Jo-Philipp Wich <freifunk@wwsnet.net>
5
6 Licensed under the Apache License, Version 2.0 (the "License");
7 you may not use this file except in compliance with the License.
8 You may obtain a copy of the License at
9
10         http://www.apache.org/licenses/LICENSE-2.0
11
12 Unless required by applicable law or agreed to in writing, software
13 distributed under the License is distributed on an "AS IS" BASIS,
14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 See the License for the specific language governing permissions and
16 limitations under the License.
17
18 $Id$
19
20 ]]--
21
22 module("luci.sys.iptparser", package.seeall)
23 require("luci.sys")
24 require("luci.util")
25
26
27 IptParser = luci.util.class()
28
29 --[[
30 IptParser.__init__( ... )
31
32 The class constructor, initializes the internal lookup table.
33 ]]--
34
35 function IptParser.__init__( self, ... )
36         self._rules = { }
37         self._chain = nil
38         self:_parse_rules()
39 end
40
41
42 --[[
43 IptParser.find( args )
44
45 Find all firewall rules that match the given criteria. Expects a table with search criteria as only argument.
46 If args is nil or an empty table then all rules will be returned.
47
48 The following keys in the args table are recognized:
49
50  - table        Match rules that are located within the given table
51  - chain        Match rules that are located within the given chain
52  - target       Match rules with the given target
53  - protocol     Match rules that match the given protocol, rules with protocol "all" are always matched
54  - source       Match rules with the given source, rules with source "0.0.0.0/0" are always matched
55  - destination  Match rules with the given destination, rules with destination "0.0.0.0/0" are always matched
56  - inputif      Match rules with the given input interface, rules with input interface "*" (=all) are always matched
57  - outputif     Match rules with the given output interface, rules with output interface "*" (=all) are always matched
58  - flags        Match rules that match the given flags, current supported values are "-f" (--fragment) and "!f" (! --fragment)
59  - options      Match rules containing all given options
60
61 The return value is a list of tables representing the matched rules.
62 Each rule table contains the following fields:
63
64  - index        The index number of the rule
65  - table        The table where the rule is located, can be one of "filter", "nat" or "mangle"
66  - chain        The chain where the rule is located, e.g. "INPUT" or "postrouting_wan"
67  - target       The rule target, e.g. "REJECT" or "DROP"
68  - protocol     The matching protocols, e.g. "all" or "tcp"
69  - flags        Special rule options ("--", "-f" or "!f")
70  - inputif      Input interface of the rule, e.g. "eth0.0" or "*" for all interfaces
71  - outputif     Output interface of the rule, e.g. "eth0.0" or "*" for all interfaces
72  - source       The source ip range, e.g. "0.0.0.0/0"
73  - destination  The destination ip range, e.g. "0.0.0.0/0"
74  - options      A list of specific options of the rule, e.g. { "reject-with", "tcp-reset" }
75  - packets      The number of packets matched by the rule
76  - bytes        The number of total bytes matched by the rule
77
78 Example:
79
80 ip = luci.sys.iptparser.IptParser()
81 result = ip.find( {
82         target="REJECT",
83         protocol="tcp",
84         options={ "reject-with", "tcp-reset" }
85 } )
86
87 This will match all rules with target "-j REJECT", protocol "-p tcp" (or "-p all") and the option "--reject-with tcp-reset".
88
89 ]]--
90
91 function IptParser.find( self, args )
92
93         local args = args or { }
94         local rv   = { }
95
96         for i, rule in ipairs(self._rules) do
97                 local match = true
98
99                 -- match table
100                 if not ( not args.table or args.table == rule.table ) then
101                         match = false
102                 end
103
104                 -- match chain
105                 if not ( match == true and ( not args.chain or args.chain == rule.chain ) ) then
106                         match = false
107                 end
108
109                 -- match target
110                 if not ( match == true and ( not args.target or args.target == rule.target ) ) then
111                         match = false
112                 end
113
114                 -- match protocol
115                 if not ( match == true and ( not args.protocol or rule.protocol == "all" or args.protocol == rule.protocol ) ) then
116                         match = false
117                 end
118                 
119                 -- match source (XXX: implement ipcalc stuff so that 192.168.1.0/24 matches 0.0.0.0/0 etc.)
120                 if not ( match == true and ( not args.source or rule.source == "0.0.0.0/0" or rule.source == args.source ) ) then
121                         match = false
122                 end
123
124                 -- match destination (XXX: implement ipcalc stuff so that 192.168.1.0/24 matches 0.0.0.0/0 etc.)
125                 if not ( match == true and ( not args.destination or rule.destination == "0.0.0.0/0" or rule.destination == args.destination ) ) then
126                         match = false
127                 end
128
129                 -- match input interface
130                 if not ( match == true and ( not args.inputif or rule.inputif == "*" or args.inputif == rule.inputif ) ) then
131                         match = false
132                 end
133
134                 -- match output interface
135                 if not ( match == true and ( not args.outputif or rule.outputif == "*" or args.outputif == rule.outputif ) ) then
136                         match = false
137                 end
138
139                 -- match flags (the "opt" column)
140                 if not ( match == true and ( not args.flags or rule.flags == args.flags ) ) then
141                         match = false
142                 end
143
144                 -- match specific options
145                 if not ( match == true and ( not args.options or self:_match_options( rule.options, args.options ) ) ) then
146                         match = false
147                 end
148
149
150                 -- insert match
151                 if match == true then
152                         table.insert( rv, rule )
153                 end
154         end
155
156         return rv
157 end
158
159
160 --[[
161 IptParser.resync()
162
163 Rebuild the internal lookup table, for example when rules have changed through external commands.
164 ]]--
165
166 function IptParser.resync( self )
167         self._rules = { }
168         self._chain = nil
169         self:_parse_rules()
170 end
171
172
173 --[[
174 IptParser._parse_rules()
175
176 [internal] Parse iptables output from all tables.
177 ]]--
178
179 function IptParser._parse_rules( self )
180
181         for i, tbl in ipairs({ "filter", "nat", "mangle" }) do
182
183                 for i, rule in ipairs(luci.sys.execl("iptables -t " .. tbl .. " --line-numbers -nxvL")) do
184
185                         if rule:find( "Chain " ) == 1 then
186                 
187                                 self._chain = rule:gsub("Chain ([^%s]*) .*", "%1")
188
189                         else
190                                 if rule:find("%d") == 1 then
191
192                                         local rule_parts   = luci.util.split( rule, "%s+", nil, true )
193                                         local rule_details = { }
194
195                                         rule_details["table"]       = tbl
196                                         rule_details["chain"]       = self._chain
197                                         rule_details["index"]       = tonumber(rule_parts[1])
198                                         rule_details["packets"]     = tonumber(rule_parts[2])
199                                         rule_details["bytes"]       = tonumber(rule_parts[3])
200                                         rule_details["target"]      = rule_parts[4]
201                                         rule_details["protocol"]    = rule_parts[5]
202                                         rule_details["flags"]       = rule_parts[6]
203                                         rule_details["inputif"]     = rule_parts[7]
204                                         rule_details["outputif"]    = rule_parts[8]
205                                         rule_details["source"]      = rule_parts[9]
206                                         rule_details["destination"] = rule_parts[10]
207                                         rule_details["options"]     = { }
208
209                                         for i = 11, #rule_parts - 1 do 
210                                                 rule_details["options"][i-10] = rule_parts[i]
211                                         end
212
213                                         table.insert( self._rules, rule_details )
214                                 end
215                         end
216                 end
217         end
218
219         self._chain = nil
220 end
221
222
223 --[[
224 IptParser._match_options( optlist1, optlist2 )
225
226 [internal] Return true if optlist1 contains all elements of optlist2. Return false in all other cases.
227 ]]--
228
229 function IptParser._match_options( self, o1, o2 )
230
231         -- construct a hashtable of first options list to speed up lookups
232         local oh = { }
233         for i, opt in ipairs( o1 ) do oh[opt] = true end
234
235         -- iterate over second options list
236         -- each string in o2 must be also present in o1
237         -- if o2 contains a string which is not found in o1 then return false
238         for i, opt in ipairs( o2 ) do
239                 if not oh[opt] then
240                         return false
241                 end
242         end
243
244         return true
245 end