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