blob: fdb4e6a3644e4743f22abbe6fc9f049c6493ece1 [file] [log] [blame]
Shekhar Sharma886b00b2019-06-20 16:19:32 +05301#!/usr/bin/env python
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +02002#
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
Shekhar Sharma886b00b2019-06-20 16:19:32 +053013from __future__ import print_function
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +020014import sys
15import os
16import subprocess
17import argparse
18
19IPTABLES = "iptables"
20IP6TABLES = "ip6tables"
Florian Westphal3ac65af2018-11-05 17:03:07 +010021ARPTABLES = "arptables"
Florian Westphalfb747f82018-11-02 12:06:30 +010022EBTABLES = "ebtables"
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +020023
24IPTABLES_SAVE = "iptables-save"
25IP6TABLES_SAVE = "ip6tables-save"
Florian Westphal3ac65af2018-11-05 17:03:07 +010026ARPTABLES_SAVE = "arptables-save"
Florian Westphalfb747f82018-11-02 12:06:30 +010027EBTABLES_SAVE = "ebtables-save"
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +020028#IPTABLES_SAVE = ['xtables-save','-4']
29#IP6TABLES_SAVE = ['xtables-save','-6']
30
31EXTENSIONS_PATH = "extensions"
32LOGFILE="/tmp/iptables-test.log"
33log_file = None
34
35
36class Colors:
37 HEADER = '\033[95m'
38 BLUE = '\033[94m'
39 GREEN = '\033[92m'
40 YELLOW = '\033[93m'
41 RED = '\033[91m'
42 ENDC = '\033[0m'
43
44
45def print_error(reason, filename=None, lineno=None):
46 '''
47 Prints an error with nice colors, indicating file and line number.
48 '''
Shekhar Sharma886b00b2019-06-20 16:19:32 +053049 print(filename + ": " + Colors.RED + "ERROR" +
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +020050 Colors.ENDC + ": line %d (%s)" % (lineno, reason))
51
52
53def delete_rule(iptables, rule, filename, lineno):
54 '''
55 Removes an iptables rule
56 '''
Florian Westphala77a7d82018-05-07 00:05:11 +020057 cmd = iptables + " -D " + rule
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +020058 ret = execute_cmd(cmd, filename, lineno)
59 if ret == 1:
60 reason = "cannot delete: " + iptables + " -I " + rule
61 print_error(reason, filename, lineno)
62 return -1
63
64 return 0
65
66
Pablo Neira Ayuso01231832018-10-19 12:13:37 +020067def run_test(iptables, rule, rule_save, res, filename, lineno, netns):
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +020068 '''
69 Executes an unit test. Returns the output of delete_rule().
70
71 Parameters:
72 :param iptables: string with the iptables command to execute
73 :param rule: string with iptables arguments for the rule to test
74 :param rule_save: string to find the rule in the output of iptables -save
75 :param res: expected result of the rule. Valid values: "OK", "FAIL"
76 :param filename: name of the file tested (used for print_error purposes)
77 :param lineno: line number being tested (used for print_error purposes)
78 '''
79 ret = 0
80
Florian Westphala77a7d82018-05-07 00:05:11 +020081 cmd = iptables + " -A " + rule
Pablo Neira Ayuso01231832018-10-19 12:13:37 +020082 if netns:
83 cmd = "ip netns exec ____iptables-container-test " + EXECUTEABLE + " " + cmd
84
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +020085 ret = execute_cmd(cmd, filename, lineno)
86
87 #
88 # report failed test
89 #
90 if ret:
91 if res == "OK":
92 reason = "cannot load: " + cmd
93 print_error(reason, filename, lineno)
94 return -1
95 else:
96 # do not report this error
97 return 0
98 else:
99 if res == "FAIL":
100 reason = "should fail: " + cmd
101 print_error(reason, filename, lineno)
102 delete_rule(iptables, rule, filename, lineno)
103 return -1
104
105 matching = 0
106 splitted = iptables.split(" ")
107 if len(splitted) == 2:
108 if splitted[1] == '-4':
109 command = IPTABLES_SAVE
110 elif splitted[1] == '-6':
111 command = IP6TABLES_SAVE
112 elif len(splitted) == 1:
113 if splitted[0] == IPTABLES:
114 command = IPTABLES_SAVE
115 elif splitted[0] == IP6TABLES:
116 command = IP6TABLES_SAVE
Florian Westphal3ac65af2018-11-05 17:03:07 +0100117 elif splitted[0] == ARPTABLES:
118 command = ARPTABLES_SAVE
Florian Westphalfb747f82018-11-02 12:06:30 +0100119 elif splitted[0] == EBTABLES:
120 command = EBTABLES_SAVE
Pablo Neira Ayuso01231832018-10-19 12:13:37 +0200121
122 path = os.path.abspath(os.path.curdir) + "/iptables/" + EXECUTEABLE
123 command = path + " " + command
124
125 if netns:
126 command = "ip netns exec ____iptables-container-test " + command
127
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200128 args = splitted[1:]
Pablo Neira Ayuso01231832018-10-19 12:13:37 +0200129 proc = subprocess.Popen(command, shell=True,
Florian Westphald7ac61b2018-04-27 16:50:13 +0200130 stdin=subprocess.PIPE,
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200131 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
132 out, err = proc.communicate()
133
134 #
135 # check for segfaults
136 #
137 if proc.returncode == -11:
138 reason = "iptables-save segfaults: " + cmd
139 print_error(reason, filename, lineno)
140 delete_rule(iptables, rule, filename, lineno)
141 return -1
142
143 # find the rule
Shekhar Sharma886b00b2019-06-20 16:19:32 +0530144 matching = out.find(rule_save.encode('utf-8'))
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200145 if matching < 0:
146 reason = "cannot find: " + iptables + " -I " + rule
147 print_error(reason, filename, lineno)
148 delete_rule(iptables, rule, filename, lineno)
149 return -1
150
Pablo Neira Ayuso01231832018-10-19 12:13:37 +0200151 # Test "ip netns del NETNS" path with rules in place
152 if netns:
153 return 0
154
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200155 return delete_rule(iptables, rule, filename, lineno)
156
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200157def execute_cmd(cmd, filename, lineno):
158 '''
159 Executes a command, checking for segfaults and returning the command exit
160 code.
161
162 :param cmd: string with the command to be executed
163 :param filename: name of the file tested (used for print_error purposes)
164 :param lineno: line number being tested (used for print_error purposes)
165 '''
166 global log_file
Florian Westphal3ac65af2018-11-05 17:03:07 +0100167 if cmd.startswith('iptables ') or cmd.startswith('ip6tables ') or cmd.startswith('ebtables ') or cmd.startswith('arptables '):
Florian Westphala77a7d82018-05-07 00:05:11 +0200168 cmd = os.path.abspath(os.path.curdir) + "/iptables/" + EXECUTEABLE + " " + cmd
169
Shekhar Sharma886b00b2019-06-20 16:19:32 +0530170 print("command: {}".format(cmd), file=log_file)
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200171 ret = subprocess.call(cmd, shell=True, universal_newlines=True,
172 stderr=subprocess.STDOUT, stdout=log_file)
173 log_file.flush()
174
175 # generic check for segfaults
176 if ret == -11:
177 reason = "command segfaults: " + cmd
178 print_error(reason, filename, lineno)
179 return ret
180
181
Pablo Neira Ayuso01231832018-10-19 12:13:37 +0200182def run_test_file(filename, netns):
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200183 '''
184 Runs a test file
185
186 :param filename: name of the file with the test rules
187 '''
188 #
189 # if this is not a test file, skip.
190 #
191 if not filename.endswith(".t"):
192 return 0, 0
193
194 if "libipt_" in filename:
195 iptables = IPTABLES
196 elif "libip6t_" in filename:
197 iptables = IP6TABLES
198 elif "libxt_" in filename:
199 iptables = IPTABLES
Florian Westphal3ac65af2018-11-05 17:03:07 +0100200 elif "libarpt_" in filename:
201 # only supported with nf_tables backend
202 if EXECUTEABLE != "xtables-nft-multi":
203 return 0, 0
204 iptables = ARPTABLES
Florian Westphalfb747f82018-11-02 12:06:30 +0100205 elif "libebt_" in filename:
206 # only supported with nf_tables backend
207 if EXECUTEABLE != "xtables-nft-multi":
208 return 0, 0
209 iptables = EBTABLES
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200210 else:
211 # default to iptables if not known prefix
212 iptables = IPTABLES
213
214 f = open(filename)
215
216 tests = 0
217 passed = 0
218 table = ""
219 total_test_passed = True
220
Taehee Yoo9ff99152018-11-01 23:32:50 +0900221 if netns:
222 execute_cmd("ip netns add ____iptables-container-test", filename, 0)
223
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200224 for lineno, line in enumerate(f):
225 if line[0] == "#":
226 continue
227
228 if line[0] == ":":
229 chain_array = line.rstrip()[1:].split(",")
230 continue
231
232 # external non-iptables invocation, executed as is.
233 if line[0] == "@":
234 external_cmd = line.rstrip()[1:]
Taehee Yoo9ff99152018-11-01 23:32:50 +0900235 if netns:
Pablo Neira Ayusob81c8da2018-11-03 14:40:26 +0100236 external_cmd = "ip netns exec ____iptables-container-test " + external_cmd
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200237 execute_cmd(external_cmd, filename, lineno)
238 continue
239
Pablo Neira Ayusoa9f93772018-11-03 14:40:26 +0100240 # external iptables invocation, executed as is.
241 if line[0] == "%":
242 external_cmd = line.rstrip()[1:]
243 if netns:
244 external_cmd = "ip netns exec ____iptables-container-test " + EXECUTEABLE + " " + external_cmd
245 execute_cmd(external_cmd, filename, lineno)
246 continue
247
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200248 if line[0] == "*":
249 table = line.rstrip()[1:]
250 continue
251
252 if len(chain_array) == 0:
Shekhar Sharma886b00b2019-06-20 16:19:32 +0530253 print("broken test, missing chain, leaving")
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200254 sys.exit()
255
256 test_passed = True
257 tests += 1
258
259 for chain in chain_array:
260 item = line.split(";")
261 if table == "":
262 rule = chain + " " + item[0]
263 else:
264 rule = chain + " -t " + table + " " + item[0]
265
266 if item[1] == "=":
267 rule_save = chain + " " + item[0]
268 else:
269 rule_save = chain + " " + item[1]
270
271 res = item[2].rstrip()
Taehee Yoo9ff99152018-11-01 23:32:50 +0900272 ret = run_test(iptables, rule, rule_save,
273 res, filename, lineno + 1, netns)
Pablo Neira Ayuso01231832018-10-19 12:13:37 +0200274
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200275 if ret < 0:
276 test_passed = False
277 total_test_passed = False
278 break
279
280 if test_passed:
281 passed += 1
282
Taehee Yoo9ff99152018-11-01 23:32:50 +0900283 if netns:
284 execute_cmd("ip netns del ____iptables-container-test", filename, 0)
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200285 if total_test_passed:
Shekhar Sharma886b00b2019-06-20 16:19:32 +0530286 print(filename + ": " + Colors.GREEN + "OK" + Colors.ENDC)
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200287
288 f.close()
289 return tests, passed
290
291
292def show_missing():
293 '''
294 Show the list of missing test files
295 '''
296 file_list = os.listdir(EXTENSIONS_PATH)
297 testfiles = [i for i in file_list if i.endswith('.t')]
298 libfiles = [i for i in file_list
299 if i.startswith('lib') and i.endswith('.c')]
300
301 def test_name(x):
302 return x[0:-2] + '.t'
303 missing = [test_name(i) for i in libfiles
304 if not test_name(i) in testfiles]
305
Shekhar Sharma886b00b2019-06-20 16:19:32 +0530306 print('\n'.join(missing))
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200307
308
309#
310# main
311#
312def main():
313 parser = argparse.ArgumentParser(description='Run iptables tests')
314 parser.add_argument('filename', nargs='?',
315 metavar='path/to/file.t',
316 help='Run only this test')
Phil Sutter1b5d7622019-09-14 02:34:36 +0200317 parser.add_argument('-H', '--host', action='store_true',
318 help='Run tests against installed binaries')
Florian Westphalbe709182018-06-18 09:18:28 +0200319 parser.add_argument('-l', '--legacy', action='store_true',
320 help='Test iptables-legacy')
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200321 parser.add_argument('-m', '--missing', action='store_true',
322 help='Check for missing tests')
Florian Westphald7ac61b2018-04-27 16:50:13 +0200323 parser.add_argument('-n', '--nftables', action='store_true',
324 help='Test iptables-over-nftables')
Pablo Neira Ayuso01231832018-10-19 12:13:37 +0200325 parser.add_argument('-N', '--netns', action='store_true',
326 help='Test netnamespace path')
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200327 args = parser.parse_args()
328
329 #
330 # show list of missing test files
331 #
332 if args.missing:
333 show_missing()
334 return
335
Florian Westphald7ac61b2018-04-27 16:50:13 +0200336 global EXECUTEABLE
Florian Westphalbe709182018-06-18 09:18:28 +0200337 EXECUTEABLE = "xtables-legacy-multi"
Florian Westphald7ac61b2018-04-27 16:50:13 +0200338 if args.nftables:
Florian Westphalbe709182018-06-18 09:18:28 +0200339 EXECUTEABLE = "xtables-nft-multi"
Florian Westphald7ac61b2018-04-27 16:50:13 +0200340
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200341 if os.getuid() != 0:
Shekhar Sharma886b00b2019-06-20 16:19:32 +0530342 print("You need to be root to run this, sorry")
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200343 return
344
Phil Sutter1b5d7622019-09-14 02:34:36 +0200345 if not args.host:
346 os.putenv("XTABLES_LIBDIR", os.path.abspath(EXTENSIONS_PATH))
347 os.putenv("PATH", "%s/iptables:%s" % (os.path.abspath(os.path.curdir),
348 os.getenv("PATH")))
Florian Westphald7ac61b2018-04-27 16:50:13 +0200349
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200350 test_files = 0
351 tests = 0
352 passed = 0
353
354 # setup global var log file
355 global log_file
356 try:
357 log_file = open(LOGFILE, 'w')
358 except IOError:
Shekhar Sharma886b00b2019-06-20 16:19:32 +0530359 print("Couldn't open log file %s" % LOGFILE)
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200360 return
361
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200362 if args.filename:
363 file_list = [args.filename]
Phil Sutter124587a2019-09-27 12:07:46 +0200364 else:
365 file_list = [os.path.join(EXTENSIONS_PATH, i)
366 for i in os.listdir(EXTENSIONS_PATH)
367 if i.endswith('.t')]
368 file_list.sort()
369
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200370 for filename in file_list:
Pablo Neira Ayuso01231832018-10-19 12:13:37 +0200371 file_tests, file_passed = run_test_file(filename, args.netns)
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200372 if file_tests:
373 tests += file_tests
374 passed += file_passed
375 test_files += 1
376
Shekhar Sharma886b00b2019-06-20 16:19:32 +0530377 print("%d test files, %d unit tests, %d passed" % (test_files, tests, passed))
Pablo Neira Ayusoc8b7aaa2012-08-21 19:43:09 +0200378
379
380if __name__ == '__main__':
381 main()