This adds a simple nftables Python class in py/nftables.py which gives access to libnftables API via ctypes module. nft-test.py is extended to make use of the above class instead of calling nft binary. Since command line formatting had to be touched anyway, this patch also streamlines things a bit by introducing __str__ methods to classes Table and Chain and making extensive use of format strings instead of onerously adding all string parts together. Since the called commands don't see a shell anymore, all shell meta character escaping done in testcases is removed. The visible effects of this change are: * Four new warnings in ip/flowtable.t due to changing objref IDs (will be addressed later in a patch to libnftnl). * Reported command line in warning and error messages changed slightly for obvious reasons. * Reduction of a full test run's runtime by a factor of four. Status diff after running with 'time': < 83 test files, 77 files passed, 1724 unit tests, 0 error, 33 warning < 87.23user 696.13system 15:11.82elapsed 85%CPU (0avgtext+0avgdata 9604maxresident)k < 8inputs+36800outputs (0major+35171235minor)pagefaults 0swaps > 83 test files, 77 files passed, 1724 unit tests, 4 error, 33 warning > 6.80user 30.18system 3:45.86elapsed 16%CPU (0avgtext+0avgdata 14064maxresident)k > 0inputs+35808outputs (0major+2874minor)pagefaults 0swaps Signed-off-by: Phil Sutter <phil@xxxxxx> --- py/.gitignore | 1 + py/nftables.py | 224 ++++++++++++++++++++++++++++++++++++++ tests/py/any/ct.t | 14 +-- tests/py/any/ct.t.payload | 12 +- tests/py/any/log.t | 2 +- tests/py/any/log.t.payload | 2 +- tests/py/any/meta.t | 4 +- tests/py/arp/arp.t | 2 +- tests/py/arp/arp.t.payload | 2 +- tests/py/arp/arp.t.payload.netdev | 2 +- tests/py/inet/tcp.t | 2 +- tests/py/inet/tcp.t.payload | 2 +- tests/py/ip/ip.t | 6 +- tests/py/ip/ip.t.payload | 6 +- tests/py/ip/ip.t.payload.bridge | 6 +- tests/py/ip/ip.t.payload.inet | 6 +- tests/py/ip/ip.t.payload.netdev | 6 +- tests/py/ip/objects.t | 4 +- tests/py/nft-test.py | 156 +++++++++++++------------- 19 files changed, 339 insertions(+), 120 deletions(-) create mode 100644 py/.gitignore create mode 100644 py/nftables.py diff --git a/py/.gitignore b/py/.gitignore new file mode 100644 index 0000000000000..0d20b6487c61e --- /dev/null +++ b/py/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/py/nftables.py b/py/nftables.py new file mode 100644 index 0000000000000..c175975076982 --- /dev/null +++ b/py/nftables.py @@ -0,0 +1,224 @@ +import json +from ctypes import * +import sys + +class Nftables: + """A class representing libnftables interface""" + + debug_flags = { + "scanner": 0x1, + "parser": 0x2, + "eval": 0x4, + "netlink": 0x8, + "mnl": 0x10, + "proto-ctx": 0x20, + "segtree": 0x40, + } + + numeric_levels = { + "none": 0, + "addr": 1, + "port": 2, + "all": 3, + } + + def __init__(self, sofile="libnftables.so"): + """Instantiate a new Nftables class object. + + Accepts a shared object file to open, by default standard search path + is searched for a file named 'libnftables.so'. + + After loading the library using ctypes module, a new nftables context + is requested from the library and buffering of output and error streams + is turned on. + """ + lib = cdll.LoadLibrary(sofile) + + ### API function definitions + + self.nft_ctx_new = lib.nft_ctx_new + self.nft_ctx_new.restype = c_void_p + self.nft_ctx_new.argtypes = [c_int] + + self.nft_ctx_output_get_handle = lib.nft_ctx_output_get_handle + self.nft_ctx_output_get_handle.restype = c_bool + self.nft_ctx_output_get_handle.argtypes = [c_void_p] + + self.nft_ctx_output_set_handle = lib.nft_ctx_output_set_handle + self.nft_ctx_output_set_handle.argtypes = [c_void_p, c_bool] + + self.nft_ctx_output_get_numeric = lib.nft_ctx_output_get_numeric + self.nft_ctx_output_get_numeric.restype = c_int + self.nft_ctx_output_get_numeric.argtypes = [c_void_p] + + self.nft_ctx_output_set_numeric = lib.nft_ctx_output_set_numeric + self.nft_ctx_output_set_numeric.argtypes = [c_void_p, c_int] + + self.nft_ctx_output_get_stateless = lib.nft_ctx_output_get_stateless + self.nft_ctx_output_get_stateless.restype = c_bool + self.nft_ctx_output_get_stateless.argtypes = [c_void_p] + + self.nft_ctx_output_set_stateless = lib.nft_ctx_output_set_stateless + self.nft_ctx_output_set_stateless.argtypes = [c_void_p, c_bool] + + self.nft_ctx_output_get_debug = lib.nft_ctx_output_get_debug + self.nft_ctx_output_get_debug.restype = c_int + self.nft_ctx_output_get_debug.argtypes = [c_void_p] + + self.nft_ctx_output_set_debug = lib.nft_ctx_output_set_debug + self.nft_ctx_output_set_debug.argtypes = [c_void_p, c_int] + + self.nft_ctx_buffer_output = lib.nft_ctx_buffer_output + self.nft_ctx_buffer_output.restype = c_int + self.nft_ctx_buffer_output.argtypes = [c_void_p] + + self.nft_ctx_get_output_buffer = lib.nft_ctx_get_output_buffer + self.nft_ctx_get_output_buffer.restype = c_char_p + self.nft_ctx_get_output_buffer.argtypes = [c_void_p] + + self.nft_ctx_buffer_error = lib.nft_ctx_buffer_error + self.nft_ctx_buffer_error.restype = c_int + self.nft_ctx_buffer_error.argtypes = [c_void_p] + + self.nft_ctx_get_error_buffer = lib.nft_ctx_get_error_buffer + self.nft_ctx_get_error_buffer.restype = c_char_p + self.nft_ctx_get_error_buffer.argtypes = [c_void_p] + + self.nft_run_cmd_from_buffer = lib.nft_run_cmd_from_buffer + self.nft_run_cmd_from_buffer.restype = c_int + self.nft_run_cmd_from_buffer.argtypes = [c_void_p, c_char_p, c_int] + + self.nft_ctx_free = lib.nft_ctx_free + lib.nft_ctx_free.argtypes = [c_void_p] + + # initialize libnftables context + self.__ctx = self.nft_ctx_new(0) + self.nft_ctx_buffer_output(self.__ctx) + self.nft_ctx_buffer_error(self.__ctx) + + def get_handle_output(self): + """Get the current state of handle output. + + Returns a boolean indicating whether handle output is active or not. + """ + return self.nft_ctx_output_get_handle(self.__ctx) + + def set_handle_output(self, val): + """Enable or disable handle output. + + Accepts a boolean turning handle output on or off. + + Returns the previous value. + """ + old = self.get_handle_output() + self.nft_ctx_output_set_handle(self.__ctx, val) + return old + + def get_numeric_output(self): + """Get the current state of numeric output. + + Returns a boolean indicating whether boolean output is active or not. + """ + return self.nft_ctx_output_get_numeric(self.__ctx) + + def set_numeric_output(self, val): + """Enable or disable numeric output. + + Accepts a boolean turning numeric output on or off. + + Returns the previous value. + """ + old = self.get_numeric_output() + + if type(val) is str: + val = self.numeric_levels[val] + self.nft_ctx_output_set_numeric(self.__ctx, val) + + return old + + def get_stateless_output(self): + """Get the current state of stateless output. + + Returns a boolean indicating whether stateless output is active or not. + """ + return self.nft_ctx_output_get_stateless(self.__ctx) + + def set_stateless_output(self, val): + """Enable or Disable stateless output. + + Accepts a boolean turning stateless output either on or off. + + Returns the previous value. + """ + old = self.get_stateless_output() + self.nft_ctx_output_set_stateless(self.__ctx, val) + return old + + def get_debug(self): + """Get currently active debug flags. + + Returns a set of flag names. See set_debug() for details. + """ + val = self.nft_ctx_output_get_debug(self.__ctx) + + names = [] + for n,v in self.debug_flags.items(): + if val & v: + names.append(n) + val &= ~v + if val: + names.append(val) + + return names + + def set_debug(self, values): + """Set debug output flags. + + Accepts either a single flag or a set of flags. Each flag might be + given either as string or integer value as shown in the following + table: + + Name | Value (hex) + ----------------------- + scanner | 0x1 + parser | 0x2 + eval | 0x4 + netlink | 0x8 + mnl | 0x10 + proto-ctx | 0x20 + segtree | 0x40 + + Returns a set of previously active debug flags, as returned by + get_debug() method. + """ + old = self.get_debug() + + if type(values) in [str, int]: + values = [values] + + val = 0 + for v in values: + if type(v) is str: + v = self.debug_flags[v] + val |= v + + self.nft_ctx_output_set_debug(self.__ctx, val) + + return old + + def cmd(self, cmdline): + """Run a simple nftables command via libnftables. + + Accepts a string containing an nftables command just like what one + would enter into an interactive nftables (nft -i) session. + + Returns a tuple (rc, output, error): + rc -- return code as returned by nft_run_cmd_from_buffer() fuction + output -- a string containing output written to stdout + error -- a string containing output written to stderr + """ + rc = self.nft_run_cmd_from_buffer(self.__ctx, cmdline, len(cmdline)) + output = self.nft_ctx_get_output_buffer(self.__ctx) + error = self.nft_ctx_get_error_buffer(self.__ctx) + + return (rc, output, error) diff --git a/tests/py/any/ct.t b/tests/py/any/ct.t index 6334dd760b563..ce6d51a401151 100644 --- a/tests/py/any/ct.t +++ b/tests/py/any/ct.t @@ -75,19 +75,19 @@ ct expiration != {33-55};ok;ct expiration != { 33s-55s} ct helper "ftp";ok ct helper "12345678901234567";fail -ct helper '""';fail +ct helper "";fail ct state . ct mark { new . 0x12345678};ok ct state . ct mark { new . 0x12345678, new . 0x34127856, established . 0x12785634};ok ct direction . ct mark { original . 0x12345678};ok ct state . ct mark vmap { new . 0x12345678 : drop};ok -ct original bytes \> 100000;ok;ct original bytes > 100000 -ct reply packets \< 100;ok;ct reply packets < 100 -ct bytes \> 100000;ok;ct bytes > 100000 +ct original bytes > 100000;ok +ct reply packets < 100;ok +ct bytes > 100000;ok -ct avgpkt \> 200;ok;ct avgpkt > 200 -ct original avgpkt \< 500;ok;ct original avgpkt < 500 +ct avgpkt > 200;ok +ct original avgpkt < 500;ok # bogus direction ct both bytes gt 1;fail @@ -107,7 +107,7 @@ ct mark original;fail ct event set new;ok ct event set new or related or destroy or foobar;fail -ct event set 'new | related | destroy | label';ok;ct event set new,related,destroy,label +ct event set new | related | destroy | label;ok;ct event set new,related,destroy,label ct event set new,related,destroy,label;ok ct event set new,destroy;ok ct event set 1;ok;ct event set new diff --git a/tests/py/any/ct.t.payload b/tests/py/any/ct.t.payload index 7ebf3f8d327e0..9f288e79140df 100644 --- a/tests/py/any/ct.t.payload +++ b/tests/py/any/ct.t.payload @@ -343,31 +343,31 @@ ip test-ip4 output [ lookup reg 1 set __map%d dreg 1 ] [ ct set mark with reg 1 ] -# ct original bytes \> 100000 +# ct original bytes > 100000 ip test-ip4 output [ ct load bytes => reg 1 , dir original ] [ byteorder reg 1 = hton(reg 1, 8, 8) ] [ cmp gt reg 1 0x00000000 0xa0860100 ] -# ct reply packets \< 100 +# ct reply packets < 100 ip test-ip4 output [ ct load packets => reg 1 , dir reply ] [ byteorder reg 1 = hton(reg 1, 8, 8) ] [ cmp lt reg 1 0x00000000 0x64000000 ] -# ct bytes \> 100000 +# ct bytes > 100000 ip test-ip4 output [ ct load bytes => reg 1 ] [ byteorder reg 1 = hton(reg 1, 8, 8) ] [ cmp gt reg 1 0x00000000 0xa0860100 ] -# ct avgpkt \> 200 +# ct avgpkt > 200 ip test-ip4 output [ ct load avgpkt => reg 1 ] [ byteorder reg 1 = hton(reg 1, 8, 8) ] [ cmp gt reg 1 0x00000000 0xc8000000 ] -# ct original avgpkt \< 500 +# ct original avgpkt < 500 ip test-ip4 output [ ct load avgpkt => reg 1 , dir original ] [ byteorder reg 1 = hton(reg 1, 8, 8) ] @@ -396,7 +396,7 @@ ip test-ip4 output [ immediate reg 1 0x00000001 ] [ ct set event with reg 1 ] -# ct event set 'new | related | destroy | label' +# ct event set new | related | destroy | label ip test-ip4 output [ immediate reg 1 0x00000407 ] [ ct set event with reg 1 ] diff --git a/tests/py/any/log.t b/tests/py/any/log.t index 37982022a6232..d1b4ab623c4de 100644 --- a/tests/py/any/log.t +++ b/tests/py/any/log.t @@ -24,7 +24,7 @@ log prefix aaaaa-aaaaaa group 2 snaplen 33;ok;log prefix "aaaaa-aaaaaa" group 2 # The correct rule is log group 2 queue-threshold 2 log group 2 queue-threshold 2;ok log group 2 snaplen 33;ok -log group 2 prefix \"nft-test: \";ok;log prefix "nft-test: " group 2 +log group 2 prefix "nft-test: ";ok;log prefix "nft-test: " group 2 log flags all;ok log level debug flags ip options flags skuid;ok diff --git a/tests/py/any/log.t.payload b/tests/py/any/log.t.payload index 385b8bba2460d..ffb914d20de5f 100644 --- a/tests/py/any/log.t.payload +++ b/tests/py/any/log.t.payload @@ -46,7 +46,7 @@ ip test-ip4 output ip test-ip4 output [ log group 2 snaplen 33 qthreshold 0 ] -# log group 2 prefix \"nft-test: \" +# log group 2 prefix "nft-test: " ip test-ip4 output [ log prefix nft-test: group 2 snaplen 0 qthreshold 0 ] diff --git a/tests/py/any/meta.t b/tests/py/any/meta.t index 9df038e510f42..b3bb0504f0ff7 100644 --- a/tests/py/any/meta.t +++ b/tests/py/any/meta.t @@ -70,7 +70,7 @@ meta iifname {"dummy0", "lo"};ok;iifname {"dummy0", "lo"} meta iifname != {"dummy0", "lo"};ok;iifname != {"dummy0", "lo"} meta iifname "dummy*";ok;iifname "dummy*" meta iifname "dummy\*";ok;iifname "dummy\*" -meta iifname '""';fail +meta iifname "";fail meta iiftype {ether, ppp, ipip, ipip6, loopback, sit, ipgre};ok;iiftype {ether, ppp, ipip, ipip6, loopback, sit, ipgre} meta iiftype != {ether, ppp, ipip, ipip6, loopback, sit, ipgre};ok;iiftype != {ether, ppp, ipip, ipip6, loopback, sit, ipgre} @@ -89,7 +89,7 @@ meta oifname != "dummy0";ok;oifname != "dummy0" meta oifname { "dummy0", "lo"};ok;oifname { "dummy0", "lo"} meta oifname "dummy*";ok;oifname "dummy*" meta oifname "dummy\*";ok;oifname "dummy\*" -meta oifname '""';fail +meta oifname "";fail meta oiftype {ether, ppp, ipip, ipip6, loopback, sit, ipgre};ok;oiftype {ether, ppp, ipip, ipip6, loopback, sit, ipgre} meta oiftype != {ether, ppp, ipip, ipip6, loopback, sit, ipgre};ok;oiftype != {ether, ppp, ipip, ipip6, loopback, sit, ipgre} diff --git a/tests/py/arp/arp.t b/tests/py/arp/arp.t index 36c7f1964841c..d62cc546f24d5 100644 --- a/tests/py/arp/arp.t +++ b/tests/py/arp/arp.t @@ -55,4 +55,4 @@ arp operation != inreply;ok arp operation != nak;ok arp operation != reply;ok -meta iifname \"invalid\" arp ptype 0x0800 arp htype 1 arp hlen 6 arp plen 4 @nh,192,32 0xc0a88f10 @nh,144,48 set 0x112233445566;ok;iifname "invalid" arp htype 1 arp ptype ip arp hlen 6 arp plen 4 @nh,192,32 3232272144 @nh,144,48 set 18838586676582 +meta iifname "invalid" arp ptype 0x0800 arp htype 1 arp hlen 6 arp plen 4 @nh,192,32 0xc0a88f10 @nh,144,48 set 0x112233445566;ok;iifname "invalid" arp htype 1 arp ptype ip arp hlen 6 arp plen 4 @nh,192,32 3232272144 @nh,144,48 set 18838586676582 diff --git a/tests/py/arp/arp.t.payload b/tests/py/arp/arp.t.payload index 34ae241448067..bb95e1c14be04 100644 --- a/tests/py/arp/arp.t.payload +++ b/tests/py/arp/arp.t.payload @@ -268,7 +268,7 @@ arp test-arp input [ payload load 2b @ network header + 6 => reg 1 ] [ cmp neq reg 1 0x00000200 ] -# meta iifname \"invalid\" arp ptype 0x0800 arp htype 1 arp hlen 6 arp plen 4 @nh,192,32 0xc0a88f10 @nh,144,48 set 0x112233445566 +# meta iifname "invalid" arp ptype 0x0800 arp htype 1 arp hlen 6 arp plen 4 @nh,192,32 0xc0a88f10 @nh,144,48 set 0x112233445566 arp test-arp input [ meta load iifname => reg 1 ] [ cmp eq reg 1 0x61766e69 0x0064696c 0x00000000 0x00000000 ] diff --git a/tests/py/arp/arp.t.payload.netdev b/tests/py/arp/arp.t.payload.netdev index 21818ba2f2db8..00c26ccc1932f 100644 --- a/tests/py/arp/arp.t.payload.netdev +++ b/tests/py/arp/arp.t.payload.netdev @@ -358,7 +358,7 @@ netdev test-netdev ingress [ payload load 2b @ network header + 6 => reg 1 ] [ cmp neq reg 1 0x00000200 ] -# meta iifname \"invalid\" arp ptype 0x0800 arp htype 1 arp hlen 6 arp plen 4 @nh,192,32 0xc0a88f10 @nh,144,48 set 0x112233445566 +# meta iifname "invalid" arp ptype 0x0800 arp htype 1 arp hlen 6 arp plen 4 @nh,192,32 0xc0a88f10 @nh,144,48 set 0x112233445566 netdev test-netdev ingress [ meta load iifname => reg 1 ] [ cmp eq reg 1 0x61766e69 0x0064696c 0x00000000 0x00000000 ] diff --git a/tests/py/inet/tcp.t b/tests/py/inet/tcp.t index f25be59969d7f..5276516625cce 100644 --- a/tests/py/inet/tcp.t +++ b/tests/py/inet/tcp.t @@ -76,7 +76,7 @@ tcp flags { fin, syn, rst, psh, ack, urg, ecn, cwr} drop;ok tcp flags != { fin, urg, ecn, cwr} drop;ok tcp flags cwr;ok tcp flags != cwr;ok -tcp 'flags & (syn|fin) == (syn|fin)';ok;tcp flags & (fin | syn) == fin | syn +tcp flags & (syn|fin) == (syn|fin);ok;tcp flags & (fin | syn) == fin | syn tcp window 22222;ok tcp window 22;ok diff --git a/tests/py/inet/tcp.t.payload b/tests/py/inet/tcp.t.payload index bf2ffaaff3bc9..512b42e9df084 100644 --- a/tests/py/inet/tcp.t.payload +++ b/tests/py/inet/tcp.t.payload @@ -421,7 +421,7 @@ inet test-inet input [ payload load 1b @ transport header + 13 => reg 1 ] [ cmp neq reg 1 0x00000080 ] -# tcp 'flags & (syn|fin) == (syn|fin)' +# tcp flags & (syn|fin) == (syn|fin) inet test-inet input [ meta load l4proto => reg 1 ] [ cmp eq reg 1 0x00000006 ] diff --git a/tests/py/ip/ip.t b/tests/py/ip/ip.t index d773042afe021..0421d01bf6e49 100644 --- a/tests/py/ip/ip.t +++ b/tests/py/ip/ip.t @@ -113,10 +113,10 @@ ip daddr 192.168.0.1;ok ip daddr 192.168.0.1 drop;ok ip daddr 192.168.0.2;ok -ip saddr \& 0xff == 1;ok;ip saddr & 0.0.0.255 == 0.0.0.1 -ip saddr \& 0.0.0.255 \< 0.0.0.127;ok;ip saddr & 0.0.0.255 < 0.0.0.127 +ip saddr & 0xff == 1;ok;ip saddr & 0.0.0.255 == 0.0.0.1 +ip saddr & 0.0.0.255 < 0.0.0.127;ok -ip saddr \& 0xffff0000 == 0xffff0000;ok;ip saddr 255.255.0.0/16 +ip saddr & 0xffff0000 == 0xffff0000;ok;ip saddr 255.255.0.0/16 ip version 4 ip hdrlength 5;ok ip hdrlength 0;ok diff --git a/tests/py/ip/ip.t.payload b/tests/py/ip/ip.t.payload index e9de690d8f70e..eba79dec4f2e9 100644 --- a/tests/py/ip/ip.t.payload +++ b/tests/py/ip/ip.t.payload @@ -484,19 +484,19 @@ ip test-ip4 input [ payload load 4b @ network header + 16 => reg 1 ] [ cmp eq reg 1 0x0200a8c0 ] -# ip saddr \& 0xff == 1 +# ip saddr & 0xff == 1 ip test-ip4 input [ payload load 4b @ network header + 12 => reg 1 ] [ bitwise reg 1 = (reg=1 & 0xff000000 ) ^ 0x00000000 ] [ cmp eq reg 1 0x01000000 ] -# ip saddr \& 0.0.0.255 \< 0.0.0.127 +# ip saddr & 0.0.0.255 < 0.0.0.127 ip test-ip4 input [ payload load 4b @ network header + 12 => reg 1 ] [ bitwise reg 1 = (reg=1 & 0xff000000 ) ^ 0x00000000 ] [ cmp lt reg 1 0x7f000000 ] -# ip saddr \& 0xffff0000 == 0xffff0000 +# ip saddr & 0xffff0000 == 0xffff0000 ip test-ip4 input [ payload load 4b @ network header + 12 => reg 1 ] [ bitwise reg 1 = (reg=1 & 0x0000ffff ) ^ 0x00000000 ] diff --git a/tests/py/ip/ip.t.payload.bridge b/tests/py/ip/ip.t.payload.bridge index d1c57a01db739..f16759bfbbaea 100644 --- a/tests/py/ip/ip.t.payload.bridge +++ b/tests/py/ip/ip.t.payload.bridge @@ -632,7 +632,7 @@ bridge test-bridge input [ payload load 4b @ network header + 16 => reg 1 ] [ cmp eq reg 1 0x0200a8c0 ] -# ip saddr \& 0xff == 1 +# ip saddr & 0xff == 1 bridge test-bridge input [ payload load 2b @ link header + 12 => reg 1 ] [ cmp eq reg 1 0x00000008 ] @@ -640,7 +640,7 @@ bridge test-bridge input [ bitwise reg 1 = (reg=1 & 0xff000000 ) ^ 0x00000000 ] [ cmp eq reg 1 0x01000000 ] -# ip saddr \& 0.0.0.255 \< 0.0.0.127 +# ip saddr & 0.0.0.255 < 0.0.0.127 bridge test-bridge input [ payload load 2b @ link header + 12 => reg 1 ] [ cmp eq reg 1 0x00000008 ] @@ -648,7 +648,7 @@ bridge test-bridge input [ bitwise reg 1 = (reg=1 & 0xff000000 ) ^ 0x00000000 ] [ cmp lt reg 1 0x7f000000 ] -# ip saddr \& 0xffff0000 == 0xffff0000 +# ip saddr & 0xffff0000 == 0xffff0000 bridge test-bridge input [ payload load 2b @ link header + 12 => reg 1 ] [ cmp eq reg 1 0x00000008 ] diff --git a/tests/py/ip/ip.t.payload.inet b/tests/py/ip/ip.t.payload.inet index e6cb700f0db3b..12b03e2e16f24 100644 --- a/tests/py/ip/ip.t.payload.inet +++ b/tests/py/ip/ip.t.payload.inet @@ -632,7 +632,7 @@ inet test-inet input [ payload load 4b @ network header + 16 => reg 1 ] [ cmp eq reg 1 0x0200a8c0 ] -# ip saddr \& 0xff == 1 +# ip saddr & 0xff == 1 inet test-inet input [ meta load nfproto => reg 1 ] [ cmp eq reg 1 0x00000002 ] @@ -640,7 +640,7 @@ inet test-inet input [ bitwise reg 1 = (reg=1 & 0xff000000 ) ^ 0x00000000 ] [ cmp eq reg 1 0x01000000 ] -# ip saddr \& 0.0.0.255 \< 0.0.0.127 +# ip saddr & 0.0.0.255 < 0.0.0.127 inet test-inet input [ meta load nfproto => reg 1 ] [ cmp eq reg 1 0x00000002 ] @@ -648,7 +648,7 @@ inet test-inet input [ bitwise reg 1 = (reg=1 & 0xff000000 ) ^ 0x00000000 ] [ cmp lt reg 1 0x7f000000 ] -# ip saddr \& 0xffff0000 == 0xffff0000 +# ip saddr & 0xffff0000 == 0xffff0000 inet test-inet input [ meta load nfproto => reg 1 ] [ cmp eq reg 1 0x00000002 ] diff --git a/tests/py/ip/ip.t.payload.netdev b/tests/py/ip/ip.t.payload.netdev index 0f15247fa0f3e..187a39f3ab890 100644 --- a/tests/py/ip/ip.t.payload.netdev +++ b/tests/py/ip/ip.t.payload.netdev @@ -531,7 +531,7 @@ netdev test-netdev ingress [ cmp eq reg 1 0x0100a8c0 ] [ immediate reg 0 drop ] -# ip saddr \& 0xff == 1 +# ip saddr & 0xff == 1 netdev test-netdev ingress [ meta load protocol => reg 1 ] [ cmp eq reg 1 0x00000008 ] @@ -539,7 +539,7 @@ netdev test-netdev ingress [ bitwise reg 1 = (reg=1 & 0xff000000 ) ^ 0x00000000 ] [ cmp eq reg 1 0x01000000 ] -# ip saddr \& 0.0.0.255 \< 0.0.0.127 +# ip saddr & 0.0.0.255 < 0.0.0.127 netdev test-netdev ingress [ meta load protocol => reg 1 ] [ cmp eq reg 1 0x00000008 ] @@ -547,7 +547,7 @@ netdev test-netdev ingress [ bitwise reg 1 = (reg=1 & 0xff000000 ) ^ 0x00000000 ] [ cmp lt reg 1 0x7f000000 ] -# ip saddr \& 0xffff0000 == 0xffff0000 +# ip saddr & 0xffff0000 == 0xffff0000 netdev test-netdev ingress [ meta load protocol => reg 1 ] [ cmp eq reg 1 0x00000008 ] diff --git a/tests/py/ip/objects.t b/tests/py/ip/objects.t index 76b802ac969b0..5e8c763136ff7 100644 --- a/tests/py/ip/objects.t +++ b/tests/py/ip/objects.t @@ -19,8 +19,8 @@ ip saddr 192.168.1.3 quota name "qt3";fail quota name tcp dport map {443 : "qt1", 80 : "qt2", 22 : "qt1"};ok # ct helper -%cthelp1 type ct helper { type \"ftp\" protocol tcp\; };ok -%cthelp2 type ct helper { type \"ftp\" protocol tcp\; l3proto ip6\; };fail +%cthelp1 type ct helper { type "ftp" protocol tcp; };ok +%cthelp2 type ct helper { type "ftp" protocol tcp; l3proto ip6; };fail ct helper set "cthelp1";ok ct helper set tcp dport map {21 : "cthelp1", 2121 : "cthelp1" };ok diff --git a/tests/py/nft-test.py b/tests/py/nft-test.py index 7998914aa4180..d2d13218cac98 100755 --- a/tests/py/nft-test.py +++ b/tests/py/nft-test.py @@ -15,11 +15,9 @@ import sys import os -import subprocess import argparse import signal -NFT_BIN = os.getenv('NFT', "src/nft") TESTS_PATH = os.path.dirname(os.path.abspath(__file__)) TESTS_DIRECTORY = ["any", "arp", "bridge", "inet", "ip", "ip6"] LOGFILE = "/tmp/nftables-test.log" @@ -57,6 +55,9 @@ class Chain: def __eq__(self, other): return self.__dict__ == other.__dict__ + def __str__(self): + return "%s" % self.name + class Table: """Class that represents a table""" @@ -69,6 +70,9 @@ class Table: def __eq__(self, other): return self.__dict__ == other.__dict__ + def __str__(self): + return "%s %s" % (self.family, self.name) + class Set: """Class that represents a set""" @@ -133,8 +137,8 @@ def table_exist(table, filename, lineno): ''' Exists a table. ''' - cmd = NFT_BIN + " list -nnn table " + table.family + " " + table.name - ret = execute_cmd(cmd, filename, lineno) + cmd = "list table %s" % table + ret = execute_cmd(cmd, filename, lineno, numeric="all") return True if (ret == 0) else False @@ -143,7 +147,7 @@ def table_flush(table, filename, lineno): ''' Flush a table. ''' - cmd = NFT_BIN + " flush table " + table.family + " " + table.name + cmd = "flush table %s" % table execute_cmd(cmd, filename, lineno) return cmd @@ -162,7 +166,7 @@ def table_create(table, filename, lineno): table_list.append(table) # We add a new table - cmd = NFT_BIN + " add table " + table.family + " " + table.name + cmd = "add table %s" % table ret = execute_cmd(cmd, filename, lineno) if ret != 0: @@ -195,15 +199,13 @@ def table_delete(table, filename=None, lineno=None): ''' Deletes a table. ''' - table_info = " " + table.family + " " + table.name + " " - if not table_exist(table, filename, lineno): reason = "Table " + table.name + \ " does not exist but I added it before." print_error(reason, filename, lineno) return -1 - cmd = NFT_BIN + " delete table" + table_info + cmd = "delete table %s" % table ret = execute_cmd(cmd, filename, lineno) if ret != 0: reason = cmd + ": " + "I cannot delete table '" + table.name + \ @@ -224,9 +226,8 @@ def chain_exist(chain, table, filename): ''' Checks a chain ''' - table_info = " " + table.family + " " + table.name + " " - cmd = NFT_BIN + " list -nnn chain" + table_info + chain.name - ret = execute_cmd(cmd, filename, chain.lineno) + cmd = "list chain %s %s" % (table, chain) + ret = execute_cmd(cmd, filename, chain.lineno, numeric="all") return True if (ret == 0) else False @@ -235,16 +236,13 @@ def chain_create(chain, table, filename): ''' Adds a chain ''' - table_info = " " + table.family + " " + table.name + " " - if chain_exist(chain, table, filename): reason = "This chain '" + chain.name + "' exists in " + table.name + \ ". I cannot create two chains with same name." print_error(reason, filename, chain.lineno) return -1 - cmd = NFT_BIN + " add chain" + table_info + chain.name + \ - "\{ " + chain.config + "\; \}" + cmd = "add chain %s %s { %s; }" % (table, chain, chain.config) ret = execute_cmd(cmd, filename, chain.lineno) if ret != 0: @@ -265,22 +263,20 @@ def chain_delete(chain, table, filename=None, lineno=None): ''' Flushes and deletes a chain. ''' - table_info = " " + table.family + " " + table.name + " " - if not chain_exist(chain, table, filename): reason = "The chain " + chain.name + " does not exists in " + \ table.name + ". I cannot delete it." print_error(reason, filename, lineno) return -1 - cmd = NFT_BIN + " flush chain" + table_info + chain.name + cmd = "flush chain %s %s" % (table, chain) ret = execute_cmd(cmd, filename, lineno) if ret != 0: reason = "I cannot flush this chain " + chain.name print_error(reason, filename, lineno) return -1 - cmd = NFT_BIN + " delete chain" + table_info + chain.name + cmd = "delete chain %s %s" % (table, chain) ret = execute_cmd(cmd, filename, lineno) if ret != 0: reason = cmd + "I cannot delete this chain. DD" @@ -323,13 +319,11 @@ def set_add(s, test_result, filename, lineno): print_error(reason, filename, lineno) return -1 - table_handle = " " + table.family + " " + table.name + " " - if s.flags == "": - set_cmd = " " + s.name + " { type " + s.type + "\;}" - else: - set_cmd = " " + s.name + " { type " + s.type + "\; flags " + s.flags + "\; }" + flags = s.flags + if flags != "": + flags = "flags %s; " % flags - cmd = NFT_BIN + " add set" + table_handle + set_cmd + cmd = "add set %s %s { type %s; %s}" % (table, s.name, s.type, flags) ret = execute_cmd(cmd, filename, lineno) if (ret == 0 and test_result == "fail") or \ @@ -365,17 +359,8 @@ def set_add_elements(set_element, set_name, state, filename, lineno): print_error(reason, filename, lineno) return -1 - table_info = " " + table.family + " " + table.name + " " - - element = "" - for e in set_element: - if not element: - element = e - else: - element = element + ", " + e - - set_text = set_name + " { " + element + " }" - cmd = NFT_BIN + " add element" + table_info + set_text + element = ", ".join(set_element) + cmd = "add element %s %s { %s }" % (table, set_name, element) ret = execute_cmd(cmd, filename, lineno) if (state == "fail" and ret == 0) or (state == "ok" and ret != 0): @@ -397,11 +382,8 @@ def set_delete_elements(set_element, set_name, table, filename=None, ''' Deletes elements in a set. ''' - table_info = " " + table.family + " " + table.name + " " - for element in set_element: - set_text = set_name + " {" + element + "}" - cmd = NFT_BIN + " delete element" + table_info + set_text + cmd = "delete element %s %s { %s }" % (table, set_name, element) ret = execute_cmd(cmd, filename, lineno) if ret != 0: reason = "I cannot delete an element" + element + \ @@ -429,8 +411,7 @@ def set_delete(table, filename=None, lineno=None): lineno) # We delete the set. - table_info = " " + table.family + " " + table.name + " " - cmd = NFT_BIN + " delete set " + table_info + " " + set_name + cmd = "delete set %s %s" % (table, set_name) ret = execute_cmd(cmd, filename, lineno) # Check if the set still exists after I deleted it. @@ -446,9 +427,8 @@ def set_exist(set_name, table, filename, lineno): ''' Check if the set exists. ''' - table_info = " " + table.family + " " + table.name + " " - cmd = NFT_BIN + " list -nnn set" + table_info + set_name - ret = execute_cmd(cmd, filename, lineno) + cmd = "list set %s %s" % (table, set_name) + ret = execute_cmd(cmd, filename, lineno, numeric="all") return True if (ret == 0) else False @@ -457,9 +437,8 @@ def _set_exist(s, filename, lineno): ''' Check if the set exists. ''' - table_handle = " " + s.family + " " + s.table + " " - cmd = NFT_BIN + " list -nnn set" + table_handle + s.name - ret = execute_cmd(cmd, filename, lineno) + cmd = "list set %s %s %s" % (s.family, s.table, s.name) + ret = execute_cmd(cmd, filename, lineno, numeric="all") return True if (ret == 0) else False @@ -510,9 +489,7 @@ def obj_add(o, test_result, filename, lineno): print_error(reason, filename, lineno) return -1 - table_handle = " " + table.family + " " + table.name + " " - - cmd = NFT_BIN + " add " + o.type + table_handle + o.name + " " + o.spcf + cmd = "add %s %s %s %s" % (o.type, table, o.name, o.spcf) ret = execute_cmd(cmd, filename, lineno) if (ret == 0 and test_result == "fail") or \ @@ -552,8 +529,7 @@ def obj_delete(table, filename=None, lineno=None): return -1 # We delete the object. - table_info = " " + table.family + " " + table.name + " " - cmd = NFT_BIN + " delete " + o.type + table_info + " " + o.name + cmd = "delete %s %s %s" % (o.type, table, o.name) ret = execute_cmd(cmd, filename, lineno) # Check if the object still exists after I deleted it. @@ -569,9 +545,8 @@ def obj_exist(o, table, filename, lineno): ''' Check if the object exists. ''' - table_handle = " " + table.family + " " + table.name + " " - cmd = NFT_BIN + " list -nnn " + o.type + table_handle + o.name - ret = execute_cmd(cmd, filename, lineno) + cmd = "list %s %s %s" % (o.type, table, o.name) + ret = execute_cmd(cmd, filename, lineno, numeric="all") return True if (ret == 0) else False @@ -580,9 +555,8 @@ def _obj_exist(o, filename, lineno): ''' Check if the object exists. ''' - table_handle = " " + o.family + " " + o.table + " " - cmd = NFT_BIN + " list -nnn " + o.type + table_handle + o.name - ret = execute_cmd(cmd, filename, lineno) + cmd = "list %s %s %s %s" % (o.type, o.family, o.table, o.name) + ret = execute_cmd(cmd, filename, lineno, numeric="all") return True if (ret == 0) else False @@ -697,13 +671,11 @@ def rule_add(rule, filename, lineno, force_all_family_option, filename_path): chain = chain_get_by_name(table_chain) unit_tests += 1 table_flush(table, filename, lineno) - table_info = " " + table.family + " " + table.name + " " payload_log = os.tmpfile() - cmd = NFT_BIN + " add rule --debug=netlink" + table_info + \ - chain.name + " " + rule[0] - ret = execute_cmd(cmd, filename, lineno, payload_log) + cmd = "add rule %s %s %s" % (table, chain, rule[0]) + ret = execute_cmd(cmd, filename, lineno, payload_log, debug="netlink") state = rule[1].rstrip() if (ret in [0,134] and state == "fail") or (ret != 0 and state == "ok"): @@ -740,13 +712,14 @@ def rule_add(rule, filename, lineno, force_all_family_option, filename_path): gotf.name, 1) # Check output of nft - process = subprocess.Popen([NFT_BIN, '-nnns', 'list', 'table', - table.family, table.name], - shell=False, - stdout=subprocess.PIPE, - preexec_fn=preexec) - pre_output = process.communicate() - output = pre_output[0].split(";") + numeric_old = nftables.set_numeric_output("all") + stateless_old = nftables.set_stateless_output(True) + list_cmd = 'list table %s' % table + rc, pre_output, err = nftables.cmd(list_cmd) + nftables.set_numeric_output(numeric_old) + nftables.set_stateless_output(stateless_old) + + output = pre_output.split(";") if len(output) < 2: reason = cmd + ": Listing is broken." print_error(reason, filename, lineno) @@ -755,7 +728,7 @@ def rule_add(rule, filename, lineno, force_all_family_option, filename_path): if not force_all_family_option: return [ret, warning, error, unit_tests] else: - rule_output = output_clean(pre_output[0], chain) + rule_output = output_clean(pre_output, chain) if len(rule) == 3: teoric_exit = rule[2] else: @@ -809,7 +782,8 @@ def signal_handler(signal, frame): signal_received = 1 -def execute_cmd(cmd, filename, lineno, stdout_log=False): +def execute_cmd(cmd, filename, lineno, + stdout_log=False, numeric=False, debug=False): ''' Executes a command, checks for segfaults and returns the command exit code. @@ -817,23 +791,36 @@ def execute_cmd(cmd, filename, lineno, stdout_log=False): :param cmd: string with the command to be executed :param filename: name of the file tested (used for print_error purposes) :param lineno: line number being tested (used for print_error purposes) + :param stdout_log: redirect stdout to this file instead of global log_file + :param numeric: turn numeric output temporarily on + :param debug: temporarily set these debug flags ''' global log_file print >> log_file, "command: %s" % cmd if debug_option: print cmd + if numeric: + numeric_old = nftables.get_numeric_output() + nftables.set_numeric_output(numeric) + if debug: + debug_old = nftables.get_debug() + nftables.set_debug(debug) + + ret, out, err = nftables.cmd(cmd) + if not stdout_log: stdout_log = log_file - ret = subprocess.call(cmd, shell=True, universal_newlines=True, - stderr=log_file, stdout=stdout_log, - preexec_fn=preexec) + stdout_log.write(out) + stdout_log.flush() + log_file.write(err) log_file.flush() - if ret == -11: - reason = "command segfaults: " + cmd - print_error(reason, filename, lineno) + if numeric: + nftables.set_numeric_output(numeric_old) + if debug: + nftables.set_debug(debug_old) return ret @@ -1123,10 +1110,17 @@ def main(): # Change working directory to repository root os.chdir(TESTS_PATH + "/../..") - if not os.path.isfile(NFT_BIN): - print "The nft binary does not exist. You need to build the project." + sys.path.append('py/') + from nftables import Nftables + + if not os.path.exists('src/.libs/libnftables.so'): + print "The nftables library does not exist. " \ + "You need to build the project." return + global nftables + nftables = Nftables('src/.libs/libnftables.so') + test_files = files_ok = run_total = 0 tests = passed = warnings = errors = 0 global log_file -- 2.16.1 -- To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html