6450c30729e8c1b7540915fdd82f5dc3348cc355
[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.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