blob: f3ec1b5c5f3d8902ee3c7d6e731886482fa06025 [file] [log] [blame]
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +02001#!/usr/bin/python
2#
3# (C) 2012-2013 by Pablo Neira Ayuso <pablo@netfilter.org>
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation; either version 2 of the License, or
8# (at your option) any later version.
9#
10# This software has been sponsored by Sophos Astaro <http://www.sophos.com>
11#
12
13import sys
14import os
15import subprocess
16import argparse
17
18IPTABLES = "iptables"
19IP6TABLES = "ip6tables"
20#IPTABLES = "xtables -4"
21#IP6TABLES = "xtables -6"
22
23IPTABLES_SAVE = "iptables-save"
24IP6TABLES_SAVE = "ip6tables-save"
25#IPTABLES_SAVE = ['xtables-save','-4']
26#IP6TABLES_SAVE = ['xtables-save','-6']
27
28EXTENSIONS_PATH = "extensions"
29LOGFILE="/tmp/iptables-test.log"
30log_file = None
31
32
33class Colors:
34 HEADER = '\033[95m'
35 BLUE = '\033[94m'
36 GREEN = '\033[92m'
37 YELLOW = '\033[93m'
38 RED = '\033[91m'
39 ENDC = '\033[0m'
40
41
42def print_error(reason, filename=None, lineno=None):
43 '''
44 Prints an error with nice colors, indicating file and line number.
45 '''
46 print (filename + ": " + Colors.RED + "ERROR" +
47 Colors.ENDC + ": line %d (%s)" % (lineno, reason))
48
49
50def delete_rule(iptables, rule, filename, lineno):
51 '''
52 Removes an iptables rule
53 '''
Florian Westphald7ac61b2018-04-27 16:50:13 +020054 cmd = EXECUTEABLE + " " + iptables + " -D " + rule
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +020055 ret = execute_cmd(cmd, filename, lineno)
56 if ret == 1:
57 reason = "cannot delete: " + iptables + " -I " + rule
58 print_error(reason, filename, lineno)
59 return -1
60
61 return 0
62
63
64def run_test(iptables, rule, rule_save, res, filename, lineno):
65 '''
66 Executes an unit test. Returns the output of delete_rule().
67
68 Parameters:
69 :param iptables: string with the iptables command to execute
70 :param rule: string with iptables arguments for the rule to test
71 :param rule_save: string to find the rule in the output of iptables -save
72 :param res: expected result of the rule. Valid values: "OK", "FAIL"
73 :param filename: name of the file tested (used for print_error purposes)
74 :param lineno: line number being tested (used for print_error purposes)
75 '''
76 ret = 0
77
Florian Westphald7ac61b2018-04-27 16:50:13 +020078 cmd = EXECUTEABLE + " " + iptables + " -A " + rule
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +020079 ret = execute_cmd(cmd, filename, lineno)
80
81 #
82 # report failed test
83 #
84 if ret:
85 if res == "OK":
86 reason = "cannot load: " + cmd
87 print_error(reason, filename, lineno)
88 return -1
89 else:
90 # do not report this error
91 return 0
92 else:
93 if res == "FAIL":
94 reason = "should fail: " + cmd
95 print_error(reason, filename, lineno)
96 delete_rule(iptables, rule, filename, lineno)
97 return -1
98
99 matching = 0
100 splitted = iptables.split(" ")
101 if len(splitted) == 2:
102 if splitted[1] == '-4':
103 command = IPTABLES_SAVE
104 elif splitted[1] == '-6':
105 command = IP6TABLES_SAVE
106 elif len(splitted) == 1:
107 if splitted[0] == IPTABLES:
108 command = IPTABLES_SAVE
109 elif splitted[0] == IP6TABLES:
110 command = IP6TABLES_SAVE
111 args = splitted[1:]
Florian Westphald7ac61b2018-04-27 16:50:13 +0200112 proc = subprocess.Popen((os.path.abspath(os.path.curdir) + "/iptables/" + EXECUTEABLE, command),
113 stdin=subprocess.PIPE,
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200114 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
115 out, err = proc.communicate()
116
117 #
118 # check for segfaults
119 #
120 if proc.returncode == -11:
121 reason = "iptables-save segfaults: " + cmd
122 print_error(reason, filename, lineno)
123 delete_rule(iptables, rule, filename, lineno)
124 return -1
125
126 # find the rule
127 matching = out.find(rule_save)
128 if matching < 0:
129 reason = "cannot find: " + iptables + " -I " + rule
130 print_error(reason, filename, lineno)
131 delete_rule(iptables, rule, filename, lineno)
132 return -1
133
134 return delete_rule(iptables, rule, filename, lineno)
135
136
137def execute_cmd(cmd, filename, lineno):
138 '''
139 Executes a command, checking for segfaults and returning the command exit
140 code.
141
142 :param cmd: string with the command to be executed
143 :param filename: name of the file tested (used for print_error purposes)
144 :param lineno: line number being tested (used for print_error purposes)
145 '''
146 global log_file
147 print >> log_file, "command: %s" % cmd
148 ret = subprocess.call(cmd, shell=True, universal_newlines=True,
149 stderr=subprocess.STDOUT, stdout=log_file)
150 log_file.flush()
151
152 # generic check for segfaults
153 if ret == -11:
154 reason = "command segfaults: " + cmd
155 print_error(reason, filename, lineno)
156 return ret
157
158
159def run_test_file(filename):
160 '''
161 Runs a test file
162
163 :param filename: name of the file with the test rules
164 '''
165 #
166 # if this is not a test file, skip.
167 #
168 if not filename.endswith(".t"):
169 return 0, 0
170
171 if "libipt_" in filename:
172 iptables = IPTABLES
173 elif "libip6t_" in filename:
174 iptables = IP6TABLES
175 elif "libxt_" in filename:
176 iptables = IPTABLES
177 else:
178 # default to iptables if not known prefix
179 iptables = IPTABLES
180
181 f = open(filename)
182
183 tests = 0
184 passed = 0
185 table = ""
186 total_test_passed = True
187
188 for lineno, line in enumerate(f):
189 if line[0] == "#":
190 continue
191
192 if line[0] == ":":
193 chain_array = line.rstrip()[1:].split(",")
194 continue
195
196 # external non-iptables invocation, executed as is.
197 if line[0] == "@":
198 external_cmd = line.rstrip()[1:]
199 execute_cmd(external_cmd, filename, lineno)
200 continue
201
202 if line[0] == "*":
203 table = line.rstrip()[1:]
204 continue
205
206 if len(chain_array) == 0:
207 print "broken test, missing chain, leaving"
208 sys.exit()
209
210 test_passed = True
211 tests += 1
212
213 for chain in chain_array:
214 item = line.split(";")
215 if table == "":
216 rule = chain + " " + item[0]
217 else:
218 rule = chain + " -t " + table + " " + item[0]
219
220 if item[1] == "=":
221 rule_save = chain + " " + item[0]
222 else:
223 rule_save = chain + " " + item[1]
224
225 res = item[2].rstrip()
226
227 ret = run_test(iptables, rule, rule_save,
228 res, filename, lineno + 1)
229 if ret < 0:
230 test_passed = False
231 total_test_passed = False
232 break
233
234 if test_passed:
235 passed += 1
236
237 if total_test_passed:
238 print filename + ": " + Colors.GREEN + "OK" + Colors.ENDC
239
240 f.close()
241 return tests, passed
242
243
244def show_missing():
245 '''
246 Show the list of missing test files
247 '''
248 file_list = os.listdir(EXTENSIONS_PATH)
249 testfiles = [i for i in file_list if i.endswith('.t')]
250 libfiles = [i for i in file_list
251 if i.startswith('lib') and i.endswith('.c')]
252
253 def test_name(x):
254 return x[0:-2] + '.t'
255 missing = [test_name(i) for i in libfiles
256 if not test_name(i) in testfiles]
257
258 print '\n'.join(missing)
259
260
261#
262# main
263#
264def main():
265 parser = argparse.ArgumentParser(description='Run iptables tests')
266 parser.add_argument('filename', nargs='?',
267 metavar='path/to/file.t',
268 help='Run only this test')
269 parser.add_argument('-m', '--missing', action='store_true',
270 help='Check for missing tests')
Florian Westphald7ac61b2018-04-27 16:50:13 +0200271 parser.add_argument('-n', '--nftables', action='store_true',
272 help='Test iptables-over-nftables')
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200273 args = parser.parse_args()
274
275 #
276 # show list of missing test files
277 #
278 if args.missing:
279 show_missing()
280 return
281
Florian Westphald7ac61b2018-04-27 16:50:13 +0200282 global EXECUTEABLE
283 EXECUTEABLE = "xtables-multi"
284 if args.nftables:
285 EXECUTEABLE = "xtables-compat-multi"
286
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200287 if os.getuid() != 0:
288 print "You need to be root to run this, sorry"
289 return
290
Florian Westphald7ac61b2018-04-27 16:50:13 +0200291 os.putenv("XTABLES_LIBDIR", os.path.abspath(EXTENSIONS_PATH))
292 os.putenv("PATH", "%s/iptables:%s" % (os.path.abspath(os.path.curdir), os.getenv("PATH")))
293
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200294 test_files = 0
295 tests = 0
296 passed = 0
297
298 # setup global var log file
299 global log_file
300 try:
301 log_file = open(LOGFILE, 'w')
302 except IOError:
303 print "Couldn't open log file %s" % LOGFILE
304 return
305
306 file_list = [os.path.join(EXTENSIONS_PATH, i)
307 for i in os.listdir(EXTENSIONS_PATH)]
308 if args.filename:
309 file_list = [args.filename]
310 for filename in file_list:
311 file_tests, file_passed = run_test_file(filename)
312 if file_tests:
313 tests += file_tests
314 passed += file_passed
315 test_files += 1
316
317 print ("%d test files, %d unit tests, %d passed" %
318 (test_files, tests, passed))
319
320
321if __name__ == '__main__':
322 main()