blob: 7858f1879ef9f361002d99206184a62cae785c89 [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"
Florian Westphalfb747f82018-11-02 12:06:30 +010020EBTABLES = "ebtables"
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +020021
22IPTABLES_SAVE = "iptables-save"
23IP6TABLES_SAVE = "ip6tables-save"
Florian Westphalfb747f82018-11-02 12:06:30 +010024EBTABLES_SAVE = "ebtables-save"
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +020025#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 Westphala77a7d82018-05-07 00:05:11 +020054 cmd = 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
Pablo Neira Ayuso01231832018-10-19 12:13:37 +020064def run_test(iptables, rule, rule_save, res, filename, lineno, netns):
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +020065 '''
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 Westphala77a7d82018-05-07 00:05:11 +020078 cmd = iptables + " -A " + rule
Pablo Neira Ayuso01231832018-10-19 12:13:37 +020079 if netns:
80 cmd = "ip netns exec ____iptables-container-test " + EXECUTEABLE + " " + cmd
81
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +020082 ret = execute_cmd(cmd, filename, lineno)
83
84 #
85 # report failed test
86 #
87 if ret:
88 if res == "OK":
89 reason = "cannot load: " + cmd
90 print_error(reason, filename, lineno)
91 return -1
92 else:
93 # do not report this error
94 return 0
95 else:
96 if res == "FAIL":
97 reason = "should fail: " + cmd
98 print_error(reason, filename, lineno)
99 delete_rule(iptables, rule, filename, lineno)
100 return -1
101
102 matching = 0
103 splitted = iptables.split(" ")
104 if len(splitted) == 2:
105 if splitted[1] == '-4':
106 command = IPTABLES_SAVE
107 elif splitted[1] == '-6':
108 command = IP6TABLES_SAVE
109 elif len(splitted) == 1:
110 if splitted[0] == IPTABLES:
111 command = IPTABLES_SAVE
112 elif splitted[0] == IP6TABLES:
113 command = IP6TABLES_SAVE
Florian Westphalfb747f82018-11-02 12:06:30 +0100114 elif splitted[0] == EBTABLES:
115 command = EBTABLES_SAVE
Pablo Neira Ayuso01231832018-10-19 12:13:37 +0200116
117 path = os.path.abspath(os.path.curdir) + "/iptables/" + EXECUTEABLE
118 command = path + " " + command
119
120 if netns:
121 command = "ip netns exec ____iptables-container-test " + command
122
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200123 args = splitted[1:]
Pablo Neira Ayuso01231832018-10-19 12:13:37 +0200124 proc = subprocess.Popen(command, shell=True,
Florian Westphald7ac61b2018-04-27 16:50:13 +0200125 stdin=subprocess.PIPE,
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200126 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
127 out, err = proc.communicate()
128
129 #
130 # check for segfaults
131 #
132 if proc.returncode == -11:
133 reason = "iptables-save segfaults: " + cmd
134 print_error(reason, filename, lineno)
135 delete_rule(iptables, rule, filename, lineno)
136 return -1
137
138 # find the rule
139 matching = out.find(rule_save)
140 if matching < 0:
141 reason = "cannot find: " + iptables + " -I " + rule
142 print_error(reason, filename, lineno)
143 delete_rule(iptables, rule, filename, lineno)
144 return -1
145
Pablo Neira Ayuso01231832018-10-19 12:13:37 +0200146 # Test "ip netns del NETNS" path with rules in place
147 if netns:
148 return 0
149
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200150 return delete_rule(iptables, rule, filename, lineno)
151
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200152def execute_cmd(cmd, filename, lineno):
153 '''
154 Executes a command, checking for segfaults and returning the command exit
155 code.
156
157 :param cmd: string with the command to be executed
158 :param filename: name of the file tested (used for print_error purposes)
159 :param lineno: line number being tested (used for print_error purposes)
160 '''
161 global log_file
Florian Westphalfb747f82018-11-02 12:06:30 +0100162 if cmd.startswith('iptables ') or cmd.startswith('ip6tables ') or cmd.startswith('ebtables '):
Florian Westphala77a7d82018-05-07 00:05:11 +0200163 cmd = os.path.abspath(os.path.curdir) + "/iptables/" + EXECUTEABLE + " " + cmd
164
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200165 print >> log_file, "command: %s" % cmd
166 ret = subprocess.call(cmd, shell=True, universal_newlines=True,
167 stderr=subprocess.STDOUT, stdout=log_file)
168 log_file.flush()
169
170 # generic check for segfaults
171 if ret == -11:
172 reason = "command segfaults: " + cmd
173 print_error(reason, filename, lineno)
174 return ret
175
176
Pablo Neira Ayuso01231832018-10-19 12:13:37 +0200177def run_test_file(filename, netns):
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200178 '''
179 Runs a test file
180
181 :param filename: name of the file with the test rules
182 '''
183 #
184 # if this is not a test file, skip.
185 #
186 if not filename.endswith(".t"):
187 return 0, 0
188
189 if "libipt_" in filename:
190 iptables = IPTABLES
191 elif "libip6t_" in filename:
192 iptables = IP6TABLES
193 elif "libxt_" in filename:
194 iptables = IPTABLES
Florian Westphalfb747f82018-11-02 12:06:30 +0100195 elif "libebt_" in filename:
196 # only supported with nf_tables backend
197 if EXECUTEABLE != "xtables-nft-multi":
198 return 0, 0
199 iptables = EBTABLES
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200200 else:
201 # default to iptables if not known prefix
202 iptables = IPTABLES
203
204 f = open(filename)
205
206 tests = 0
207 passed = 0
208 table = ""
209 total_test_passed = True
210
Taehee Yoo9ff99152018-11-01 23:32:50 +0900211 if netns:
212 execute_cmd("ip netns add ____iptables-container-test", filename, 0)
213
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200214 for lineno, line in enumerate(f):
215 if line[0] == "#":
216 continue
217
218 if line[0] == ":":
219 chain_array = line.rstrip()[1:].split(",")
220 continue
221
222 # external non-iptables invocation, executed as is.
223 if line[0] == "@":
224 external_cmd = line.rstrip()[1:]
Taehee Yoo9ff99152018-11-01 23:32:50 +0900225 if netns:
Pablo Neira Ayusob81c8da2018-11-03 14:40:26 +0100226 external_cmd = "ip netns exec ____iptables-container-test " + external_cmd
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200227 execute_cmd(external_cmd, filename, lineno)
228 continue
229
230 if line[0] == "*":
231 table = line.rstrip()[1:]
232 continue
233
234 if len(chain_array) == 0:
235 print "broken test, missing chain, leaving"
236 sys.exit()
237
238 test_passed = True
239 tests += 1
240
241 for chain in chain_array:
242 item = line.split(";")
243 if table == "":
244 rule = chain + " " + item[0]
245 else:
246 rule = chain + " -t " + table + " " + item[0]
247
248 if item[1] == "=":
249 rule_save = chain + " " + item[0]
250 else:
251 rule_save = chain + " " + item[1]
252
253 res = item[2].rstrip()
Taehee Yoo9ff99152018-11-01 23:32:50 +0900254 ret = run_test(iptables, rule, rule_save,
255 res, filename, lineno + 1, netns)
Pablo Neira Ayuso01231832018-10-19 12:13:37 +0200256
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200257 if ret < 0:
258 test_passed = False
259 total_test_passed = False
260 break
261
262 if test_passed:
263 passed += 1
264
Taehee Yoo9ff99152018-11-01 23:32:50 +0900265 if netns:
266 execute_cmd("ip netns del ____iptables-container-test", filename, 0)
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200267 if total_test_passed:
268 print filename + ": " + Colors.GREEN + "OK" + Colors.ENDC
269
270 f.close()
271 return tests, passed
272
273
274def show_missing():
275 '''
276 Show the list of missing test files
277 '''
278 file_list = os.listdir(EXTENSIONS_PATH)
279 testfiles = [i for i in file_list if i.endswith('.t')]
280 libfiles = [i for i in file_list
281 if i.startswith('lib') and i.endswith('.c')]
282
283 def test_name(x):
284 return x[0:-2] + '.t'
285 missing = [test_name(i) for i in libfiles
286 if not test_name(i) in testfiles]
287
288 print '\n'.join(missing)
289
290
291#
292# main
293#
294def main():
295 parser = argparse.ArgumentParser(description='Run iptables tests')
296 parser.add_argument('filename', nargs='?',
297 metavar='path/to/file.t',
298 help='Run only this test')
Florian Westphalbe709182018-06-18 09:18:28 +0200299 parser.add_argument('-l', '--legacy', action='store_true',
300 help='Test iptables-legacy')
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200301 parser.add_argument('-m', '--missing', action='store_true',
302 help='Check for missing tests')
Florian Westphald7ac61b2018-04-27 16:50:13 +0200303 parser.add_argument('-n', '--nftables', action='store_true',
304 help='Test iptables-over-nftables')
Pablo Neira Ayuso01231832018-10-19 12:13:37 +0200305 parser.add_argument('-N', '--netns', action='store_true',
306 help='Test netnamespace path')
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200307 args = parser.parse_args()
308
309 #
310 # show list of missing test files
311 #
312 if args.missing:
313 show_missing()
314 return
315
Florian Westphald7ac61b2018-04-27 16:50:13 +0200316 global EXECUTEABLE
Florian Westphalbe709182018-06-18 09:18:28 +0200317 EXECUTEABLE = "xtables-legacy-multi"
Florian Westphald7ac61b2018-04-27 16:50:13 +0200318 if args.nftables:
Florian Westphalbe709182018-06-18 09:18:28 +0200319 EXECUTEABLE = "xtables-nft-multi"
Florian Westphald7ac61b2018-04-27 16:50:13 +0200320
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200321 if os.getuid() != 0:
322 print "You need to be root to run this, sorry"
323 return
324
Florian Westphald7ac61b2018-04-27 16:50:13 +0200325 os.putenv("XTABLES_LIBDIR", os.path.abspath(EXTENSIONS_PATH))
326 os.putenv("PATH", "%s/iptables:%s" % (os.path.abspath(os.path.curdir), os.getenv("PATH")))
327
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200328 test_files = 0
329 tests = 0
330 passed = 0
331
332 # setup global var log file
333 global log_file
334 try:
335 log_file = open(LOGFILE, 'w')
336 except IOError:
337 print "Couldn't open log file %s" % LOGFILE
338 return
339
340 file_list = [os.path.join(EXTENSIONS_PATH, i)
341 for i in os.listdir(EXTENSIONS_PATH)]
342 if args.filename:
343 file_list = [args.filename]
344 for filename in file_list:
Pablo Neira Ayuso01231832018-10-19 12:13:37 +0200345 file_tests, file_passed = run_test_file(filename, args.netns)
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200346 if file_tests:
347 tests += file_tests
348 passed += file_passed
349 test_files += 1
350
351 print ("%d test files, %d unit tests, %d passed" %
352 (test_files, tests, passed))
353
354
355if __name__ == '__main__':
356 main()