summaryrefslogtreecommitdiffhomepage
path: root/core/src/iptparser.lua
blob: ab77a74041803e425cdd5e7b64dde44749fa8082 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
--[[
FFLuCI - Iptables parser and query library

Copyright 2008 Jo-Philipp Wich <freifunk@wwsnet.net>

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

$Id$

]]--

module("ffluci.iptparser", package.seeall)
require("ffluci.sys")
require("ffluci.util")


IptParser = ffluci.util.class()

--[[
IptParser.__init__( ... )

The class constructor, initializes the internal lookup table.
]]--

function IptParser.__init__( self, ... )
	self._rules = { }
	self._chain = nil
	self:_parse_rules()
end


--[[
IptParser.find( args )

Find all firewall rules that match the given criteria. Expects a table with search criteria as only argument.
If args is nil or an empty table then all rules will be returned.

The following keys in the args table are recognized:

 - table	Match rules that are located within the given table
 - chain	Match rules that are located within the given chain
 - target	Match rules with the given target
 - protocol	Match rules that match the given protocol, rules with protocol "all" are always matched
 - source	Match rules with the given source, rules with source "0.0.0.0/0" are always matched
 - destination	Match rules with the given destination, rules with destination "0.0.0.0/0" are always matched
 - inputif	Match rules with the given input interface, rules with input interface "*" (=all) are always matched
 - outputif	Match rules with the given output interface, rules with output interface "*" (=all) are always matched
 - flags	Match rules that match the given flags, current supported values are "-f" (--fragment) and "!f" (! --fragment)
 - options	Match rules containing all given options

The return value is a list of tables representing the matched rules.
Each rule table contains the following fields:

 - index	The index number of the rule
 - table	The table where the rule is located, can be one of "filter", "nat" or "mangle"
 - chain	The chain where the rule is located, e.g. "INPUT" or "postrouting_wan"
 - target	The rule target, e.g. "REJECT" or "DROP"
 - protocol	The matching protocols, e.g. "all" or "tcp"
 - flags	Special rule options ("--", "-f" or "!f")
 - inputif	Input interface of the rule, e.g. "eth0.0" or "*" for all interfaces
 - outputif	Output interface of the rule, e.g. "eth0.0" or "*" for all interfaces
 - source	The source ip range, e.g. "0.0.0.0/0"
 - destination	The destination ip range, e.g. "0.0.0.0/0"
 - options	A list of specific options of the rule, e.g. { "reject-with", "tcp-reset" }
 - packets	The number of packets matched by the rule
 - bytes	The number of total bytes matched by the rule

Example:

ip = IptParser();
result = ip.find( {
	target="REJECT",
	protocol="tcp",
	options={ "reject-with", "tcp-reset" }
} )

This will match all rules with target "-j REJECT", protocol "-p tcp" (or "-p all") and the option "--reject-with tcp-reset".

]]--

function IptParser.find( self, args )

	local args = args or { }
	local rv   = { }

	for i, rule in ipairs(self._rules) do
		local match = true

		-- match table
		if not ( not args.table or args.table == rule.table ) then
			match = false
		end

		-- match chain
		if not ( match == true and ( not args.chain or args.chain == rule.chain ) ) then
			match = false
		end

		-- match target
		if not ( match == true and ( not args.target or args.target == rule.target ) ) then
			match = false
		end

		-- match protocol
		if not ( match == true and ( not args.protocol or rule.protocol == "all" or args.protocol == rule.protocol ) ) then
			match = false
		end
		
		-- match source (XXX: implement ipcalc stuff so that 192.168.1.0/24 matches 0.0.0.0/0 etc.)
		if not ( match == true and ( not args.source or rule.source == "0.0.0.0/0" or rule.source == args.source ) ) then
			match = false
		end

		-- match destination (XXX: implement ipcalc stuff so that 192.168.1.0/24 matches 0.0.0.0/0 etc.)
		if not ( match == true and ( not args.destination or rule.destination == "0.0.0.0/0" or rule.destination == args.destination ) ) then
			match = false
		end

		-- match input interface
		if not ( match == true and ( not args.inputif or rule.inputif == "*" or args.inputif == rule.inputif ) ) then
			match = false
		end

		-- match output interface
		if not ( match == true and ( not args.outputif or rule.outputif == "*" or args.outputif == rule.outputif ) ) then
			match = false
		end

		-- match flags (the "opt" column)
		if not ( match == true and ( not args.flags or rule.flags == args.flags ) ) then
			match = false
		end

		-- match specific options
		if not ( match == true and ( not args.options or self:_match_options( rule.options, args.options ) ) ) then
			match = false
		end


		-- insert match
		if match == true then
			table.insert( rv, rule )
		end
	end

	return rv
end


--[[
IptParser.resync()

Rebuild the internal lookup table, for example when rules have changed through external commands.
]]--

function IptParser.resync( self )
	self._rules = { }
	self._chain = nil
	self:_parse_rules()
end


--[[
IptParser._parse_rules()

[internal] Parse iptables output from all tables.
]]--

function IptParser._parse_rules( self )

	for i, tbl in ipairs({ "filter", "nat", "mangle" }) do

		for i, rule in ipairs(ffluci.sys.execl("iptables -t " .. tbl .. " --line-numbers -nxvL")) do

			if rule:find( "Chain " ) == 1 then
		
				self._chain = rule:gsub("Chain ([^%s]*) .*", "%1")

			else
				if rule:find("%d") == 1 then

					local rule_parts   = ffluci.util.split( rule, "%s+", nil, true )
					local rule_details = { }

					rule_details["table"]       = tbl
					rule_details["chain"]       = _chain
					rule_details["index"]       = tonumber(rule_parts[1])
					rule_details["packets"]     = tonumber(rule_parts[2])
					rule_details["bytes"]       = tonumber(rule_parts[3])
					rule_details["target"]      = rule_parts[4]
					rule_details["protocol"]    = rule_parts[5]
					rule_details["flags"]       = rule_parts[6]
					rule_details["inputif"]     = rule_parts[7]
					rule_details["outputif"]    = rule_parts[8]
					rule_details["source"]      = rule_parts[9]
					rule_details["destination"] = rule_parts[10]
					rule_details["options"]     = { }

					for i = 11, #rule_parts - 1 do 
						rule_details["options"][i-10] = rule_parts[i]
					end

					table.insert( self._rules, rule_details )
				end
			end
		end
	end
end


--[[
IptParser._match_options( optlist1, optlist2 )

[internal] Return true if optlist1 contains all elements of optlist2. Return false in all other cases.
]]--

function IptParser._match_options( self, o1, o2 )

	-- construct a hashtable of first options list to speed up lookups
	local oh = { }
	for i, opt in ipairs( o1 ) do oh[opt] = true end

	-- iterate over second options list
	-- each string in o2 must be also present in o1
	-- if o2 contains a string which is not found in o1 then return false
	for i, opt in ipairs( o2 ) do
		if not oh[opt] then
			return false
		end
	end

	return true
end