diff mbox series

[v5,2/2] net/route: Add netlink based route change tests

Message ID 20200423131632.20525-3-pvorel@suse.cz
State Changes Requested
Headers show
Series Route tests using netlink API (dst,gw,if) | expand

Commit Message

Petr Vorel April 23, 2020, 1:16 p.m. UTC
changing destination, gateway and interface. These tests are inspired by
shell tests, but send UDP packets instead of ICMP and there is no
receiver of the packets thus no verification. Tests are much faster that
shell tests.

lhost address is not used, but NIC is set (required by interface tests).

* use libmnl and new C API, requires pkg-config (and pkg.m4 macro for
aclocal) for detection (but that's already needed for TI-RPC tests)
* add shell wrappers
* move common code for changing iface to route-lib.sh
* travis: add libmnl libraries to most of travis jobs
* cleanup test description and Makefile

Signed-off-by: Petr Vorel <pvorel@suse.cz>
---
 configure.ac                                  |   1 +
 include/mk/config.mk.in                       |   2 +
 m4/ltp-libmnl.m4                              |   8 +
 runtest/net_stress.route                      |   6 +
 testcases/network/stress/route/.gitignore     |   1 +
 .../network/stress/route/00_Descriptions.txt  |  40 +--
 testcases/network/stress/route/Makefile       |  30 +-
 .../network/stress/route/route-change-dst.sh  |   5 +-
 .../network/stress/route/route-change-gw.sh   |  18 +-
 .../network/stress/route/route-change-if.sh   |  61 +---
 .../stress/route/route-change-netlink-dst.sh  |  36 ++
 .../stress/route/route-change-netlink-gw.sh   |  31 ++
 .../stress/route/route-change-netlink-if.sh   |  38 +++
 .../stress/route/route-change-netlink.c       | 320 ++++++++++++++++++
 testcases/network/stress/route/route-lib.sh   | 104 +++++-
 travis/debian.cross-compile.sh                |   1 +
 travis/debian.sh                              |   1 +
 travis/fedora.sh                              |   3 +
 travis/tumbleweed.sh                          |   1 +
 19 files changed, 586 insertions(+), 121 deletions(-)
 create mode 100644 m4/ltp-libmnl.m4
 create mode 100644 testcases/network/stress/route/.gitignore
 create mode 100755 testcases/network/stress/route/route-change-netlink-dst.sh
 create mode 100755 testcases/network/stress/route/route-change-netlink-gw.sh
 create mode 100755 testcases/network/stress/route/route-change-netlink-if.sh
 create mode 100644 testcases/network/stress/route/route-change-netlink.c

Comments

Alexey Kodanev April 28, 2020, 4:54 p.m. UTC | #1
On 23.04.2020 16:16, Petr Vorel wrote:
> changing destination, gateway and interface. These tests are inspired by
> shell tests, but send UDP packets instead of ICMP and there is no
> receiver of the packets thus no verification. Tests are much faster that
> shell tests.
> 
> lhost address is not used, but NIC is set (required by interface tests).
> 
> * use libmnl and new C API, requires pkg-config (and pkg.m4 macro for
> aclocal) for detection (but that's already needed for TI-RPC tests)
> * add shell wrappers
> * move common code for changing iface to route-lib.sh
> * travis: add libmnl libraries to most of travis jobs
> * cleanup test description and Makefile
> 

Hi Petr,

Minor comments below.

> Signed-off-by: Petr Vorel <pvorel@suse.cz>
> ---
>  configure.ac                                  |   1 +
>  include/mk/config.mk.in                       |   2 +
>  m4/ltp-libmnl.m4                              |   8 +
>  runtest/net_stress.route                      |   6 +
>  testcases/network/stress/route/.gitignore     |   1 +
>  .../network/stress/route/00_Descriptions.txt  |  40 +--
>  testcases/network/stress/route/Makefile       |  30 +-
>  .../network/stress/route/route-change-dst.sh  |   5 +-
>  .../network/stress/route/route-change-gw.sh   |  18 +-
>  .../network/stress/route/route-change-if.sh   |  61 +---
>  .../stress/route/route-change-netlink-dst.sh  |  36 ++
>  .../stress/route/route-change-netlink-gw.sh   |  31 ++
>  .../stress/route/route-change-netlink-if.sh   |  38 +++
>  .../stress/route/route-change-netlink.c       | 320 ++++++++++++++++++
>  testcases/network/stress/route/route-lib.sh   | 104 +++++-
>  travis/debian.cross-compile.sh                |   1 +
>  travis/debian.sh                              |   1 +
>  travis/fedora.sh                              |   3 +
>  travis/tumbleweed.sh                          |   1 +
>  19 files changed, 586 insertions(+), 121 deletions(-)
>  create mode 100644 m4/ltp-libmnl.m4
>  create mode 100644 testcases/network/stress/route/.gitignore
>  create mode 100755 testcases/network/stress/route/route-change-netlink-dst.sh
>  create mode 100755 testcases/network/stress/route/route-change-netlink-gw.sh
>  create mode 100755 testcases/network/stress/route/route-change-netlink-if.sh
>  create mode 100644 testcases/network/stress/route/route-change-netlink.c
> 
> diff --git a/configure.ac b/configure.ac
> index e1069b57e..9ccf8c3ea 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -300,6 +300,7 @@ LTP_CHECK_CRYPTO
>  LTP_CHECK_FORTIFY_SOURCE
>  LTP_CHECK_KERNEL_DEVEL
>  LTP_CHECK_KEYUTILS_SUPPORT
> +LTP_CHECK_LIBMNL
>  LTP_CHECK_LINUX_PTRACE
>  LTP_CHECK_LINUXRANDOM
>  LTP_CHECK_MREMAP_FIXED
> diff --git a/include/mk/config.mk.in b/include/mk/config.mk.in
> index 5526cfea1..87b2c833d 100644
> --- a/include/mk/config.mk.in
> +++ b/include/mk/config.mk.in
> @@ -43,6 +43,8 @@ TIRPC_CFLAGS		:= @TIRPC_CFLAGS@
>  TIRPC_LIBS		:= @TIRPC_LIBS@
>  KEYUTILS_LIBS		:= @KEYUTILS_LIBS@
>  HAVE_FTS_H		:= @HAVE_FTS_H@
> +LIBMNL_LIBS		:= @LIBMNL_LIBS@
> +LIBMNL_CFLAGS		:= @LIBMNL_CFLAGS@
>  
>  prefix			:= @prefix@
>  
> diff --git a/m4/ltp-libmnl.m4 b/m4/ltp-libmnl.m4
> new file mode 100644
> index 000000000..be8ab4584
> --- /dev/null
> +++ b/m4/ltp-libmnl.m4
> @@ -0,0 +1,8 @@
> +dnl SPDX-License-Identifier: GPL-2.0-or-later
> +dnl Copyright (c) 2019 Petr Vorel <pvorel@suse.cz>
> +
> +AC_DEFUN([LTP_CHECK_LIBMNL], [
> +    PKG_CHECK_MODULES([LIBMNL], [libmnl], [
> +        AC_DEFINE([HAVE_LIBMNL], [1], [Define to 1 if you have libmnl library and headers])
> +	], [have_libmnl=no])
> +])
> diff --git a/runtest/net_stress.route b/runtest/net_stress.route
> index 4eff6766a..8755ef0be 100644
> --- a/runtest/net_stress.route
> +++ b/runtest/net_stress.route
> @@ -2,11 +2,17 @@
>  route4-change-dst route-change-dst.sh
>  route4-change-gw route-change-gw.sh
>  route4-change-if route-change-if.sh
> +route4-change-netlink-dst route-change-netlink-dst.sh
> +route4-change-netlink-gw route-change-netlink-gw.sh
> +route4-change-netlink-if route-change-netlink-if.sh
>  route4-redirect route4-redirect
>  route4-rmmod route4-rmmod
>  
>  route6-change-dst route-change-dst.sh -6
>  route6-change-gw route-change-gw.sh -6
>  route6-change-if route-change-if.sh -6
> +route6-change-netlink-dst route-change-netlink-dst.sh -6
> +route6-change-netlink-gw route-change-netlink-gw.sh -6
> +route6-change-netlink-if route-change-netlink-if.sh -6
>  route6-redirect route6-redirect
>  route6-rmmod route6-rmmod
> diff --git a/testcases/network/stress/route/.gitignore b/testcases/network/stress/route/.gitignore
> new file mode 100644
> index 000000000..4ed647a55
> --- /dev/null
> +++ b/testcases/network/stress/route/.gitignore
> @@ -0,0 +1 @@
> +/route-change-netlink
> diff --git a/testcases/network/stress/route/00_Descriptions.txt b/testcases/network/stress/route/00_Descriptions.txt
> index e0e2e42c6..d148d51d9 100644
> --- a/testcases/network/stress/route/00_Descriptions.txt
> +++ b/testcases/network/stress/route/00_Descriptions.txt
> @@ -1,36 +1,26 @@
>  route{4,6}-change-dst
> -	Verify the IPv4/IPv6 is not broken when ip command changes route
> -	destination many times
> +	Change IPv4/IPv6 route destination by ip command
>  
>  route{4,6}-change-gw
> -	Verify the IPv4/IPv6 is not broken when ip command changes route
> -	gateway many times
> +	Change IPv4/IPv6 route gateway by ip command
>  
>  route{4,6}-change-if
> -	Verify the IPv4/IPv6 is not broken when ip command changes route
> -	interface many times
> +	Change IPv4/IPv6 route interface by ip command
>  
> -route4-redirect01
> -	Verify the kernel is not crashed when the IPv4 route is modified by
> -	ICMP Redirects frequently
> +route{4,6}-change-netlink-dst
> +	Change IPv4/IPv6 route destination by Netlink API via libmnl
>  
> -route4-rmmod01
> -	Verify the kernel is not crashed when IPv4 route is add by route command
> -	then it is deleted by the removing network driver
> +route{4,6}-change-netlink-gw
> +	Change IPv4/IPv6 route gateway by Netlink API via libmnl
>  
> -route4-rmmod02
> -	Verify the kernel is not crashed when IPv4 route is add by ip command
> -	then it is deleted by the removing network driver
> +route{4,6}-change-netlink-if
> +	Change IPv4/IPv6 route interface by Netlink API via libmnl
>  
> +route{4,6}-redirect01
> +	Change IPv4/IPv6 route by ICMP Redirects frequently
>  
> -route6-redirect01
> -	Verify the kernel is not crashed when the IPv6 route is modified by
> -	ICMP Redirects frequently
> +route{4,6}-rmmod01
> +	Add IPv4/IPv6 route by route command then delete it by the removing network driver
>  
> -route6-rmmod01
> -	Verify the kernel is not crashed when IPv6 route is add by route command
> -	then it is deleted by the removing network driver
> -
> -route6-rmmod02
> -	Verify the kernel is not crashed when IPv6 route is add by ip command
> -	then it is deleted by the removing network driver
> +route{4,6}-rmmod02
> +	Add IPv4/IPv6 route by ip command then delete it by the removing network driver
> diff --git a/testcases/network/stress/route/Makefile b/testcases/network/stress/route/Makefile
> index 2e5eaa2f2..b4e6409be 100644
> --- a/testcases/network/stress/route/Makefile
> +++ b/testcases/network/stress/route/Makefile
> @@ -1,29 +1,15 @@
> -#
> -#    network/stress/route test suite Makefile.
> -#
> -#    Copyright (C) 2009, Cisco Systems Inc.
> -#
> -#    This program is free software; you can redistribute it and/or modify
> -#    it under the terms of the GNU General Public License as published by
> -#    the Free Software Foundation; either version 2 of the License, or
> -#    (at your option) any later version.
> -#
> -#    This program is distributed in the hope that it will be useful,
> -#    but WITHOUT ANY WARRANTY; without even the implied warranty of
> -#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -#    GNU General Public License for more details.
> -#
> -#    You should have received a copy of the GNU General Public License along
> -#    with this program; if not, write to the Free Software Foundation, Inc.,
> -#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
> -#
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +# Copyright (C) 2009, Cisco Systems Inc.
> +# Copyright (c) Linux Test Project, 2006-2020
>  # Ngie Cooper, October 2009
> -#
>  
>  top_srcdir		?= ../../../..
>  
> -include $(top_srcdir)/include/mk/env_pre.mk
> +include $(top_srcdir)/include/mk/testcases.mk
>  
> -INSTALL_TARGETS		:= route*
> +INSTALL_TARGETS		+= route[4-6]-* *.sh
> +
> +route-change-netlink: CFLAGS += $(LIBMNL_CFLAGS)
> +route-change-netlink: LDLIBS += $(LIBMNL_LIBS)
>  
>  include $(top_srcdir)/include/mk/generic_leaf_target.mk
> diff --git a/testcases/network/stress/route/route-change-dst.sh b/testcases/network/stress/route/route-change-dst.sh
> index fff79fa6e..e883b7e24 100755
> --- a/testcases/network/stress/route/route-change-dst.sh
> +++ b/testcases/network/stress/route/route-change-dst.sh
> @@ -4,11 +4,14 @@
>  # Copyright (c) International Business Machines Corp., 2006
>  # Author: Mitsuru Chinen <mitch@jp.ibm.com>
>  # Rewrite into new shell API: Petr Vorel
> -#
> +
>  # Change route destination
>  # lhost: 10.0.0.2, rhost: 10.23.x.1
>  
> +TST_SETUP="setup"
> +TST_CLEANUP="route_cleanup"
>  TST_TESTFUNC="test_dst"
> +TST_CNT=$NS_TIMES
>  . route-lib.sh
>  
>  setup()
> diff --git a/testcases/network/stress/route/route-change-gw.sh b/testcases/network/stress/route/route-change-gw.sh
> index fc88ee0f3..f7a08cf91 100755
> --- a/testcases/network/stress/route/route-change-gw.sh
> +++ b/testcases/network/stress/route/route-change-gw.sh
> @@ -1,27 +1,19 @@
>  #!/bin/sh
>  # SPDX-License-Identifier: GPL-2.0-or-later
> -# Copyright (c) 2019 Petr Vorel <pvorel@suse.cz>
> +# Copyright (c) 2019-2020 Petr Vorel <pvorel@suse.cz>
>  # Copyright (c) International Business Machines Corp., 2006
>  # Author: Mitsuru Chinen <mitch@jp.ibm.com>
>  # Rewrite into new shell API: Petr Vorel
> -#
> +
>  # Change route gateway
>  # lhost: 10.23.1.1, gw (on rhost): 10.23.1.x, rhost: 10.23.0.1
>  
> +TST_SETUP="setup_gw"
> +TST_CLEANUP="route_cleanup"
>  TST_TESTFUNC="test_gw"
> +TST_CNT=$NS_TIMES
>  . route-lib.sh
>  
> -setup()
> -{
> -	tst_res TINFO "change IPv$TST_IPVER route gateway $NS_TIMES times"
> -
> -	rt="$(tst_ipaddr_un -p 0 0)"
> -	lhost="$(tst_ipaddr_un 1 1)"
> -	rhost="$(tst_ipaddr_un 0 1)"
> -	tst_add_ipaddr -s -q -a $lhost
> -	tst_add_ipaddr -s -q -a $rhost rhost
> -}
> -
>  test_gw()
>  {
>  	local gw="$(tst_ipaddr_un -h 2,254 1 $(($1 + 1)))"
> diff --git a/testcases/network/stress/route/route-change-if.sh b/testcases/network/stress/route/route-change-if.sh
> index 77661a45e..489d9be92 100755
> --- a/testcases/network/stress/route/route-change-if.sh
> +++ b/testcases/network/stress/route/route-change-if.sh
> @@ -4,61 +4,15 @@
>  # Copyright (c) International Business Machines Corp., 2006
>  # Author: Mitsuru Chinen <mitch@jp.ibm.com>
>  # Rewrite into new shell API: Petr Vorel
> -#
> +
>  # Change route interface
>  # lhost: 10.23.x.2, gw (on rhost): 10.23.x.1, rhost: 10.23.0.1, switching ifaces on lhost
>  
> +TST_SETUP="setup_if"
> +TST_CLEANUP="cleanup_if"
>  TST_TESTFUNC="test_if"
> +TST_CNT=$NS_TIMES
>  . route-lib.sh
> -TST_CLEANUP="cleanup"
> -
> -add_macvlan()
> -{
> -	local action="add"
> -	local OPTIND
> -	while getopts d opt; do
> -		case "$opt" in
> -		d) action="del";;
> -		esac
> -	done
> -	shift $((OPTIND - 1))
> -
> -	local iface="$1"
> -	local type="${2:-lhost}"
> -
> -	cmd="ip link $action $iface link $(tst_iface $type) type macvlan mode bridge"
> -	if [ $type = "lhost" ]; then
> -		ROD $cmd
> -		[ "$action" = "add" ] || return
> -		LHOST_IFACES="$LHOST_IFACES $iface"
> -	else
> -		tst_rhost_run -s -c "$cmd"
> -		[ "$action" = "add" ] || return
> -		RHOST_IFACES="$RHOST_IFACES $iface"
> -	fi
> -	tst_init_iface $type 1
> -}
> -
> -setup()
> -{
> -	tst_res TINFO "change IPv$TST_IPVER route interface $NS_TIMES times"
> -
> -	rt="$(tst_ipaddr_un -p 0)"
> -	rhost="$(tst_ipaddr_un 0 1)"
> -	tst_add_ipaddr -s -q -a $rhost rhost
> -
> -	if [ $(tst_get_ifaces_cnt) -lt 2 ]; then
> -		new_liface="ltp_mv2"
> -		tst_res TINFO "2 or more local ifaces required, adding $new_liface"
> -		add_macvlan $new_liface
> -	fi
> -
> -	if [ $(tst_get_ifaces_cnt rhost) -lt 2 ]; then
> -		new_riface="ltp_mv1"
> -		tst_res TINFO "2 or more remote ifaces required, adding $new_riface"
> -		add_macvlan $new_riface rhost
> -	fi
> -}
>  
>  test_if()
>  {
> @@ -78,11 +32,4 @@ test_if()
>  	tst_del_ipaddr -s -q -a $gw rhost $link_num
>  }
>  
> -cleanup()
> -{
> -	[ "$new_liface" ] && add_macvlan -d $new_liface
> -	[ "$new_riface" ] && add_macvlan -d $new_riface rhost
> -	route_cleanup
> -}
> -
>  tst_run
> diff --git a/testcases/network/stress/route/route-change-netlink-dst.sh b/testcases/network/stress/route/route-change-netlink-dst.sh
> new file mode 100755
> index 000000000..113bd0caa
> --- /dev/null
> +++ b/testcases/network/stress/route/route-change-netlink-dst.sh
> @@ -0,0 +1,36 @@
> +#!/bin/sh
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +# Copyright (c) 2020 Petr Vorel <pvorel@suse.cz>
> +
> +# Change route destination via netlink
> +# rhost: 10.23.x.1
> +# lhost (iface set, but not specified in Netlink API): 10.0.0.2
> +
> +TST_SETUP="setup"
> +TST_CLEANUP="route_cleanup"
> +TST_TESTFUNC="test_netlink"
> +. route-lib.sh
> +
> +setup()
> +{
> +	tst_res TINFO "change IPv$TST_IPVER route destination $NS_TIMES times"
> +
> +	local cnt=0
> +	local gw rhost rhost_all rt
> +
> +	check_max_ip
> +
> +	while [ $cnt -lt $ROUTE_MAX_IP ]; do
> +		rt="$(tst_ipaddr_un -p $cnt)"
> +		rhost="$(tst_ipaddr_un $cnt 1)"
> +		rhost_all="$rhost$IP_ADDR_DELIM$rhost_all"
> +
> +		tst_add_ipaddr -s -q -a $rhost rhost
> +		ROD ip route add $rt dev $(tst_iface)
> +		cnt=$((cnt+1))
> +	done
> +
> +	ROUTE_CHANGE_NETLINK_PARAMS="-d $(tst_iface) -r '$rhost_all'"
> +}
> +
> +tst_run
> diff --git a/testcases/network/stress/route/route-change-netlink-gw.sh b/testcases/network/stress/route/route-change-netlink-gw.sh
> new file mode 100755
> index 000000000..fec09683c
> --- /dev/null
> +++ b/testcases/network/stress/route/route-change-netlink-gw.sh
> @@ -0,0 +1,31 @@
> +#!/bin/sh
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +# Copyright (c) 2020 Petr Vorel <pvorel@suse.cz>
> +
> +# Change route gateway via netlink
> +# gw (on rhost): 10.23.1.x, rhost: 10.23.0.1
> +# lhost (iface set, but not specified in Netlink API): 10.23.1.1
> +
> +TST_SETUP="setup"
> +TST_CLEANUP="route_cleanup"
> +TST_TESTFUNC="test_netlink"
> +. route-lib.sh
> +
> +setup()
> +{
> +	local cnt=0
> +
> +	check_max_ip
> +	setup_gw
> +
> +	while [ $cnt -lt $ROUTE_MAX_IP ]; do
> +		gw="$(tst_ipaddr_un -h 2,$max_ip_limit 1 $(($cnt + 1)))"
> +		gw_all="$gw$IP_ADDR_DELIM$gw_all"
> +		tst_add_ipaddr -s -q -a $gw rhost
> +		cnt=$((cnt+1))
> +	done
> +
> +	ROUTE_CHANGE_NETLINK_PARAMS="-d $(tst_iface) -g "$gw_all" -r $rhost"
> +}
> +
> +tst_run
> diff --git a/testcases/network/stress/route/route-change-netlink-if.sh b/testcases/network/stress/route/route-change-netlink-if.sh
> new file mode 100755
> index 000000000..3a095ff4b
> --- /dev/null
> +++ b/testcases/network/stress/route/route-change-netlink-if.sh
> @@ -0,0 +1,38 @@
> +#!/bin/sh
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +# Copyright (c) 2020 Petr Vorel <pvorel@suse.cz>
> +
> +# Change route interface
> +# gw (on rhost): 10.23.x.1, rhost: 10.23.0.1, switching ifaces on lhost
> +# lhost (iface set, but not specified in Netlink API): 10.23.x.2
> +
> +TST_SETUP="setup"
> +TST_CLEANUP="cleanup_if"
> +TST_TESTFUNC="test_netlink"
> +. route-lib.sh
> +
> +setup()
> +{
> +	local gw gw_all iface iface_all
> +	local cnt=0
> +
> +	setup_if
> +
> +	while [ $cnt -lt $(tst_get_ifaces_cnt) ]; do
> +		gw="$(tst_ipaddr_un -n1 $cnt 1)"
> +		iface="$(tst_iface lhost $cnt)"
> +		lhost="$(tst_ipaddr_un -n1 $cnt 2)"
> +
> +		tst_add_ipaddr -s -q -a $lhost lhost $cnt
> +		tst_add_ipaddr -s -q -a $gw rhost $cnt
> +
> +		gw_all="$gw$IP_ADDR_DELIM$gw_all"
> +		iface_all="$iface$IP_ADDR_DELIM$iface_all"
> +
> +		cnt=$((cnt+1))
> +	done
> +
> +	ROUTE_CHANGE_NETLINK_PARAMS="-d '$iface_all' -g '$gw_all' -r $rhost"
> +}
> +
> +tst_run
> diff --git a/testcases/network/stress/route/route-change-netlink.c b/testcases/network/stress/route/route-change-netlink.c
> new file mode 100644
> index 000000000..57ae02a3c
> --- /dev/null
> +++ b/testcases/network/stress/route/route-change-netlink.c
> @@ -0,0 +1,320 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (c) 2020 Petr Vorel <pvorel@suse.cz>
> + */
> +
> +#include "config.h"
> +#include "tst_test.h"
> +
> +#ifdef HAVE_LIBMNL
> +
> +#include <string.h>
> +
> +#include <libmnl/libmnl.h>
> +#include <linux/rtnetlink.h>
> +#include <net/if.h>
> +#include <netdb.h>
> +#include <netinet/in.h>
> +
> +#include "tst_net.h"
> +#include "tst_safe_net.h"
> +#include "tst_safe_stdio.h"
> +
> +#define NS_TIMES_MAX 65535
> +
> +#define IP_ADDR_DELIM ','
> +#define STR(x) #x
> +#define CHR2STR(x) STR(x)
> +
> +static char *c_opt, *d_opt, *g_opt, *ipv6_opt, *p_opt, *r_opt;
> +
> +static int family = AF_INET;
> +static int fd, num_loops, port;
> +
> +static unsigned int is_ipv6, max, prefix;
> +
> +static struct mnl_socket *nl;
> +static struct addrinfo hints;
> +
> +struct iface {
> +	unsigned int index;
> +	struct iface *next;
> +	char iface[IFNAMSIZ];
> +};
> +
> +struct ip_addr {
> +	struct addrinfo *ip;
> +	struct ip_addr *next;
> +	char ip_str[INET6_ADDRSTRLEN];
> +};
> +
> +static struct ip_addr *dst, *gw, *rhost;
> +static struct iface *iface;
> +static unsigned int gw_len, iface_len, rhost_len;
> +
> +void save_iface(void **data, const char *item)
> +{
> +	struct iface *n = SAFE_MALLOC(sizeof(*n));
> +	struct iface **list = (struct iface**)data;
> +
> +	strncpy(n->iface, item, sizeof(n->iface));
> +	n->iface[sizeof(n->iface)-1] = '\0';
> +
> +	n->index = if_nametoindex(item);
> +	if (!n->index)
> +		tst_brk(TBROK, "if_nametoindex failed, '%s' not found", item);
> +	n->next = *list;
> +	*list = n;
> +}
> +
> +void save_ip(void **data, const char *item)
> +{
> +	struct ip_addr *n = SAFE_MALLOC(sizeof(*n));
> +	struct ip_addr **list = (struct ip_addr**)data;
> +
> +	strncpy(n->ip_str, item, sizeof(n->ip_str));
> +	n->ip_str[sizeof(n->ip_str)-1] = '\0';
> +
> +	SAFE_GETADDRINFO(item, p_opt, &hints, &n->ip);
> +	n->next = *list;
> +	*list = n;
> +}
> +
> +int save_item(void **list, char *item, void (*callback)(void **, const char *))
> +{
> +	int len = 0;
> +
> +	while ((item = strtok(item, CHR2STR(IP_ADDR_DELIM))) != NULL) {
> +		callback(list, item);
> +		item = NULL;
> +		len++;
> +	}
> +
> +	return len;
> +}
> +
> +static void setup(void)
> +{
> +	prefix = 24;
> +	if (ipv6_opt) {
> +		family = AF_INET6;
> +		is_ipv6 = 1;
> +		prefix = 64;
> +	}
> +
> +	if (!c_opt)
> +		tst_brk(TBROK, "missing number of loops (-c num)");
> +
> +	if (!d_opt)
> +		tst_brk(TBROK, "missing iface (-d iface)");
> +
> +	if (!p_opt)
> +		tst_brk(TBROK, "missing rhost port (-p port)");
> +
> +	if (!r_opt)
> +		tst_brk(TBROK, "missing rhost IP (-r IP)");
> +
> +	if (tst_parse_int(p_opt, &port, 1, 65535))
> +		tst_brk(TBROK, "invalid rhost port '%s'", p_opt);
> +
> +	if (tst_parse_int(c_opt, &num_loops, 1, NS_TIMES_MAX)) {
> +		num_loops = NS_TIMES_MAX;
> +		tst_res(TWARN, "invalid number of loops (-c %s), using: %d",
> +			c_opt, num_loops);
> +	}
> +
> +	iface_len = save_item((void **)&iface, d_opt, save_iface);
> +	rhost_len = save_item((void **)&rhost, r_opt, save_ip);
> +
> +	max = MAX(iface_len, rhost_len);
> +	if (iface_len > 1 && rhost_len > 1 && iface_len != max)
> +		tst_brk(TBROK, "-d specifies more NICs and -r more IPs, they need to have the same count");
> +
> +	if (g_opt) {
> +		gw_len = save_item((void **)&gw, g_opt, save_ip);
> +		max = MAX(gw_len, max);
> +
> +		if (gw_len > 1 && max > 1 && gw_len != max) {
> +			if (iface_len == max)
> +				tst_brk(TBROK, "-d specifies more NICs and -r more IPs, they need to have the same count");
> +			else
> +				tst_brk(TBROK, "-g and -r specify more IP, they need to have the same count");
> +		}
> +	}
> +
> +	struct ip_addr *p_rhost = rhost;
> +
> +	while (p_rhost) {
> +		char dst_str[INET6_ADDRSTRLEN];
> +
> +		if (!strncpy(dst_str, p_rhost->ip_str, sizeof(dst_str)))
> +			tst_brk(TBROK, "failed copy IP '%s'", p_rhost->ip_str);
> +		dst_str[strlen(p_rhost->ip_str)-1] = '\0';
> +
> +		if (!strcat(dst_str, "0"))
> +			tst_brk(TBROK, "strcat failed: '%s'", dst_str);
> +
> +		save_ip((void **)&dst, dst_str);
> +		p_rhost = p_rhost->next;
> +	}
> +
> +	fd = SAFE_SOCKET(family, SOCK_DGRAM, IPPROTO_UDP);
> +
> +	memset(&hints, 0, sizeof(struct addrinfo));
> +	hints.ai_family = family;
> +	hints.ai_socktype = SOCK_DGRAM;
> +	hints.ai_flags = 0;
> +	hints.ai_protocol = 0;
> +	hints.ai_addr = INADDR_ANY;
> +}
> +
> +static void cleanup(void)
> +{
> +	if (fd > 0)
> +		close(fd);
> +
> +	if (nl)
> +		mnl_socket_close(nl);
> +}
> +
> +static void rtnl_route(int iface, struct addrinfo *dst, struct addrinfo *gw, uint32_t prefix, int type)
> +{
> +	struct mnl_socket *nl;
> +	char buf[MNL_SOCKET_BUFFER_SIZE];
> +	struct nlmsghdr *nlh;
> +	struct rtmsg *rtm;
> +	uint32_t seq, portid;
> +	struct in6_addr dst_in6, gw_in6;
> +	in_addr_t dst_ip, gw_ip;
> +	int ret;
> +
> +	nlh = mnl_nlmsg_put_header(buf);
> +	nlh->nlmsg_type	= type;
> +
> +	nlh->nlmsg_flags = NLM_F_ACK;
> +	if (type == RTM_NEWROUTE)
> +		nlh->nlmsg_flags |= NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE;
> +
> +	nlh->nlmsg_seq = seq = time(NULL);
> +
> +	rtm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct rtmsg));
> +	rtm->rtm_family = family;
> +	rtm->rtm_dst_len = prefix;
> +	rtm->rtm_src_len = 0;
> +	rtm->rtm_tos = 0;
> +	rtm->rtm_protocol = RTPROT_STATIC;
> +	rtm->rtm_table = RT_TABLE_MAIN;
> +	rtm->rtm_type = RTN_UNICAST;
> +	rtm->rtm_scope = gw ? RT_SCOPE_UNIVERSE : RT_SCOPE_LINK;
> +	rtm->rtm_flags = 0;
> +
> +	if (is_ipv6) {
> +		dst_in6 = ((struct sockaddr_in6 *)dst->ai_addr)->sin6_addr;
> +		mnl_attr_put(nlh, RTA_DST, sizeof(struct in6_addr), &dst_in6);
> +	} else {
> +		dst_ip = ((struct sockaddr_in *)dst->ai_addr)->sin_addr.s_addr;
> +		mnl_attr_put_u32(nlh, RTA_DST, dst_ip);
> +	}
> +
> +	mnl_attr_put_u32(nlh, RTA_OIF, iface);
> +
> +	if (gw) {
> +		if (is_ipv6) {
> +			gw_in6 = ((struct sockaddr_in6 *)gw->ai_addr)->sin6_addr;
> +			mnl_attr_put(nlh, RTA_GATEWAY, sizeof(struct in6_addr), &gw_in6);
> +		} else {
> +			gw_ip = ((struct sockaddr_in *)gw->ai_addr)->sin_addr.s_addr;
> +			mnl_attr_put_u32(nlh, RTA_GATEWAY, gw_ip);
> +		}
> +	}
> +
> +	nl = mnl_socket_open(NETLINK_ROUTE);
> +	if (nl == NULL)
> +		tst_brk(TBROK, "mnl_socket_open failed (errno=%d): %s", errno,
> +			strerror(errno));
> +
> +	if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0)
> +		tst_brk(TBROK, "mnl_socket_bind failed (errno=%d): %s", errno,
> +			strerror(errno));
> +
> +	portid = mnl_socket_get_portid(nl);
> +
> +	if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0)
> +		tst_brk(TBROK, "mnl_socket_sendto failed (errno=%d): %s", errno,
> +			strerror(errno));
> +
> +	ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
> +	if (ret < 0)
> +		tst_brk(TBROK, "mnl_socket_recvfrom failed (ret=%d, errno=%d): %s",
> +			ret, errno, strerror(errno));
> +
> +	ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL);
> +	if (ret < 0)
> +		tst_brk(TBROK, "mnl_cb_run failed (ret=%d, errno=%d): %s", ret,
> +			errno, strerror(errno));
> +
> +	mnl_socket_close(nl);
> +}
> +
> +static void send_udp(struct addrinfo *rhost_addrinfo)
> +{
> +	const char *msg = "foo";
> +
> +	SAFE_SENDTO(1, fd, msg, sizeof(msg), MSG_CONFIRM,
> +		rhost_addrinfo->ai_addr, rhost_addrinfo->ai_addrlen);
> +}
> +
> +static void run(void)
> +{
> +	int i;
> +
> +	tst_res(TINFO, "adding and deleting route %d times", num_loops);
> +
> +	struct ip_addr *p_dst = dst, *p_gw = gw, *p_rhost = rhost;
> +	struct iface *p_iface = iface;
> +
> +	for (i = 0; i < num_loops; i++) {
> +		if (iface_len > 1)
> +			tst_res(TINFO, "testing gw: %s, iface: %s",
> +					p_gw->ip_str, p_iface->iface);
> +		else if (gw_len > 1)
> +			tst_res(TINFO, "testing gw: %s", p_gw->ip_str);
> +		else
> +			tst_res(TINFO, "testing dst: %s/%d", p_dst->ip_str, prefix);


It would be better to avoid printing it on every iteration, especially
with the large NS_TIMES.

> +
> +		rtnl_route(p_iface->index, p_dst->ip, gw ? p_gw->ip : NULL,
> +			   prefix, RTM_NEWROUTE);
> +		send_udp(p_rhost->ip);
> +		rtnl_route(p_iface->index, p_dst->ip, gw ? p_gw->ip : NULL,
> +			   prefix, RTM_DELROUTE);
> +
> +		if (gw)
> +			p_gw = p_gw->next ?: gw;
> +		p_dst = p_dst->next ?: dst;
> +		p_iface = p_iface->next ?: iface;
> +		p_rhost = p_rhost->next ?: rhost;
> +	}
> +
> +	tst_res(TPASS, "routes created and deleted");
> +}
> +
> +static struct tst_option options[] = {
> +	{"6", &ipv6_opt, "-6       Use IPv6 (default is IPv4)"},
> +	{"c:", &c_opt, "         Num loops (mandatory)"},

"-c x ...

> +	{"d:", &d_opt, "-d iface Interface to work on (mandatory)"},
> +	{"g:", &g_opt, "-g x     Gateway IP"},
> +	{"p:", &p_opt, "-p port  Rhost port (mandatory)"},
> +	{"r:", &r_opt, "-r x     Rhost IP (mandatory)\n\n-g, -r IP parameter can contain more IP, separated by "
> +		CHR2STR(IP_ADDR_DELIM)},
> +	{NULL, NULL, NULL}
> +};
> +static struct tst_test test = {
> +	.test_all = run,
> +	.needs_root = 1,
> +	.setup = setup,
> +	.cleanup = cleanup,
> +	.options = options,
> +};
> +#else
> +	TST_TEST_TCONF("libmnl library and headers are required");
> +#endif /* HAVE_LIBMNL */
> diff --git a/testcases/network/stress/route/route-lib.sh b/testcases/network/stress/route/route-lib.sh
> index 4afbe6323..45fa7f3c9 100644
> --- a/testcases/network/stress/route/route-lib.sh
> +++ b/testcases/network/stress/route/route-lib.sh
> @@ -3,15 +3,113 @@
>  # Copyright (c) 2019 Petr Vorel <pvorel@suse.cz>
>  
>  TST_NEEDS_ROOT=1
> -TST_SETUP="setup"
> -TST_CLEANUP="route_cleanup"
>  TST_NEEDS_CMDS="ip"
> -TST_CNT=$NS_TIMES
>  
>  . tst_net.sh
>  
> +ROUTE_RHOST_PORT=${ROUTE_RHOST_PORT:-65535}
> +ROUTE_MAX_IP=${ROUTE_MAX_IP:-5}
> +
> +IP_ADDR_DELIM=','
> +
> +add_macvlan()
> +{
> +	local action="add"
> +	local OPTIND
> +	while getopts d opt; do
> +		case "$opt" in
> +		d) action="del";;
> +		esac
> +	done
> +	shift $((OPTIND - 1))
> +
> +	local iface="$1"
> +	local type="${2:-lhost}"
> +
> +	cmd="ip link $action $iface link $(tst_iface $type) type macvlan mode bridge"
> +	if [ $type = "lhost" ]; then
> +		ROD $cmd
> +		[ "$action" = "add" ] || return
> +		LHOST_IFACES="$LHOST_IFACES $iface"
> +	else
> +		tst_rhost_run -s -c "$cmd"
> +		[ "$action" = "add" ] || return
> +		RHOST_IFACES="$RHOST_IFACES $iface"
> +	fi
> +	tst_init_iface $type 1
> +}
> +
> +check_max_ip()
> +{
> +	local max_ip_limit=254
> +	[ "$TST_IPV6" ] && max_ip_limit=65534
> +
> +	tst_is_int "$ROUTE_MAX_IP" || tst_brk TBROK "\$ROUTE_MAX_IP not int ($ROUTE_MAX_IP)"
> +	[ $ROUTE_MAX_IP -gt $max_ip_limit ] && ROUTE_MAX_IP=$max_ip_limit
> +	[ $ROUTE_MAX_IP -gt $NS_TIMES ] && ROUTE_MAX_IP=$NS_TIMES
> +}
> +
> +cleanup_if()
> +{
> +	[ "$new_liface" ] && add_macvlan -d $new_liface
> +	[ "$new_riface" ] && add_macvlan -d $new_riface rhost
> +	route_cleanup
> +}
> +
>  route_cleanup()
>  {
>  	tst_restore_ipaddr
>  	tst_restore_ipaddr rhost
>  }
> +
> +setup_gw()
> +{
> +	tst_res TINFO "change IPv$TST_IPVER route gateway $NS_TIMES times"
> +
> +	rt="$(tst_ipaddr_un -p 0 0)"
> +	lhost="$(tst_ipaddr_un 1 1)"
> +	rhost="$(tst_ipaddr_un 0 1)"
> +	tst_add_ipaddr -s -q -a $lhost
> +	tst_add_ipaddr -s -q -a $rhost rhost
> +}
> +
> +setup_if()
> +{
> +	tst_res TINFO "change IPv$TST_IPVER route interface $NS_TIMES times"
> +
> +	rt="$(tst_ipaddr_un -p 0)"
> +	rhost="$(tst_ipaddr_un 0 1)"
> +	tst_add_ipaddr -s -q -a $rhost rhost
> +
> +	if [ $(tst_get_ifaces_cnt) -lt 2 ]; then
> +		new_liface="ltp_mv2"
> +		tst_res TINFO "2 or more local ifaces required, adding '$new_liface'"
> +		add_macvlan $new_liface
> +	fi
> +
> +	if [ $(tst_get_ifaces_cnt rhost) -lt 2 ]; then
> +		new_riface="ltp_mv1"
> +		tst_res TINFO "2 or more remote ifaces required, adding '$new_riface'"
> +		add_macvlan $new_riface rhost
> +	fi
> +}
> +
> +test_netlink()
> +{
> +	local ret=0
> +	local cmd ip_flag
> +	[ "$TST_IPV6" ] && ip_flag="-6"
> +
> +	cmd="route-change-netlink -c $NS_TIMES $ip_flag -p $ROUTE_RHOST_PORT $ROUTE_CHANGE_NETLINK_PARAMS"
> +	tst_res TINFO "running $cmd"
> +	$cmd || ret=$?
> +	if [ "$ret" -ne 0 ]; then
> +		[ $((ret & 3)) -ne 0 ] && \
> +			tst_brk TFAIL "route-change-netlink failed"
> +		[ $((ret & 32)) -ne 0 ] && \
> +			tst_brk TCONF "not supported configuration"
> +		[ $((ret & 4)) -ne 0 ] && \
> +			tst_res TWARN "route-change-netlink has warnings"
> +	fi
> +	tst_res TPASS "route-change-netlink passed"
> +}
> diff --git a/travis/debian.cross-compile.sh b/travis/debian.cross-compile.sh
> index 67307d1e1..08c3805aa 100755
> --- a/travis/debian.cross-compile.sh
> +++ b/travis/debian.cross-compile.sh
> @@ -20,4 +20,5 @@ apt update
>  apt install -y --no-install-recommends \
>  	gcc-${gcc_arch}-linux-gnu \
>  	libc6-dev-${ARCH}-cross \
> +	libmnl-dev:$ARCH \
>  	libtirpc-dev:$ARCH
> diff --git a/travis/debian.sh b/travis/debian.sh
> index 97fa91a0d..b759a9576 100755
> --- a/travis/debian.sh
> +++ b/travis/debian.sh
> @@ -28,6 +28,7 @@ apt install -y --no-install-recommends \
>  	libkeyutils-dev \
>  	libkeyutils1 \
>  	libmm-dev \
> +	libmnl-dev \
>  	libnuma-dev \
>  	libnuma1 \
>  	libselinux1-dev \
> diff --git a/travis/fedora.sh b/travis/fedora.sh
> index d68e7573e..990a84daf 100755
> --- a/travis/fedora.sh
> +++ b/travis/fedora.sh
> @@ -13,3 +13,6 @@ yum -y install \
>  	libtirpc-devel \
>  	pkg-config \
>  	redhat-lsb-core
> +
> +# CentOS 8 doesn't have libmnl-devel
> +yum -y install libmnl-devel || yum -y install libmnl
> diff --git a/travis/tumbleweed.sh b/travis/tumbleweed.sh
> index 4dd71fd13..8143fc885 100755
> --- a/travis/tumbleweed.sh
> +++ b/travis/tumbleweed.sh
> @@ -15,6 +15,7 @@ zypper --non-interactive install --no-recommends \
>  	libacl-devel \
>  	libaio-devel \
>  	libcap-devel \
> +	libmnl-devel \
>  	libnuma-devel \
>  	libopenssl-devel \
>  	libselinux-devel \
> 

Reviewed-by: Alexey Kodanev <alexey.kodanev@oracle.com>
Petr Vorel April 29, 2020, 10:41 a.m. UTC | #2
Hi Alexey,

> Minor comments below.
Thanks for your review!

> > +	for (i = 0; i < num_loops; i++) {
> > +		if (iface_len > 1)
> > +			tst_res(TINFO, "testing gw: %s, iface: %s",
> > +					p_gw->ip_str, p_iface->iface);
> > +		else if (gw_len > 1)
> > +			tst_res(TINFO, "testing gw: %s", p_gw->ip_str);
> > +		else
> > +			tst_res(TINFO, "testing dst: %s/%d", p_dst->ip_str, prefix);


> It would be better to avoid printing it on every iteration, especially
> with the large NS_TIMES.
Understand, I thought you wouldn't like it. How about print only on error?

> > +
> > +		rtnl_route(p_iface->index, p_dst->ip, gw ? p_gw->ip : NULL,
> > +			   prefix, RTM_NEWROUTE);
> > +		send_udp(p_rhost->ip);
> > +		rtnl_route(p_iface->index, p_dst->ip, gw ? p_gw->ip : NULL,
> > +			   prefix, RTM_DELROUTE);
> > +
> > +		if (gw)
> > +			p_gw = p_gw->next ?: gw;
> > +		p_dst = p_dst->next ?: dst;
> > +		p_iface = p_iface->next ?: iface;
> > +		p_rhost = p_rhost->next ?: rhost;
> > +	}
> > +
> > +	tst_res(TPASS, "routes created and deleted");
> > +}

Something like this?

static void print_route_info(int iface, struct sockaddr *dst,
			     struct sockaddr *gw, int type)
{
	char dst_str[INET6_ADDRSTRLEN], gw_str[INET6_ADDRSTRLEN];
	tst_sock_addr(dst, sizeof(dst), dst_str, sizeof(dst_str));
	if (gw)
		tst_sock_addr(gw, sizeof(gw), gw_str, sizeof(gw_str));

	tst_res(TINFO, "type: %s, iface: %d, dst: %s, gw: %s",
		type == RTM_NEWROUTE ? "RTM_NEWROUTE" : "RTM_DELROUTE",
		iface, dst_str, gw ? gw_str : "null");
	tst_brk(TBROK, "failed due previous netlink errors");
}

static void rtnl_route(int iface, struct addrinfo *dst, struct addrinfo *gw,
		       int type)
{
	struct mnl_socket *nl;
	char buf[MNL_SOCKET_BUFFER_SIZE];
	struct nlmsghdr *nlh;
	struct rtmsg *rtm;
	uint32_t seq, portid;
	struct in6_addr dst_in6, gw_in6;
	in_addr_t dst_ip, gw_ip;
	int ret;

	nlh = mnl_nlmsg_put_header(buf);
	nlh->nlmsg_type	= type;

	nlh->nlmsg_flags = NLM_F_ACK;
	if (type == RTM_NEWROUTE)
		nlh->nlmsg_flags |= NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE;

	nlh->nlmsg_seq = seq = time(NULL);

	rtm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct rtmsg));
	rtm->rtm_family = family;
	rtm->rtm_dst_len = prefix;
	rtm->rtm_src_len = 0;
	rtm->rtm_tos = 0;
	rtm->rtm_protocol = RTPROT_STATIC;
	rtm->rtm_table = RT_TABLE_MAIN;
	rtm->rtm_type = RTN_UNICAST;
	rtm->rtm_scope = gw ? RT_SCOPE_UNIVERSE : RT_SCOPE_LINK;
	rtm->rtm_flags = 0;

	if (is_ipv6) {
		dst_in6 = ((struct sockaddr_in6 *)dst->ai_addr)->sin6_addr;
		mnl_attr_put(nlh, RTA_DST, sizeof(struct in6_addr), &dst_in6);
	} else {
		dst_ip = ((struct sockaddr_in *)dst->ai_addr)->sin_addr.s_addr;
		mnl_attr_put_u32(nlh, RTA_DST, dst_ip);
	}

	mnl_attr_put_u32(nlh, RTA_OIF, iface);

	if (gw) {
		if (is_ipv6) {
			gw_in6 = ((struct sockaddr_in6 *)gw->ai_addr)->sin6_addr;
			mnl_attr_put(nlh, RTA_GATEWAY, sizeof(struct in6_addr), &gw_in6);
		} else {
			gw_ip = ((struct sockaddr_in *)gw->ai_addr)->sin_addr.s_addr;
			mnl_attr_put_u32(nlh, RTA_GATEWAY, gw_ip);
		}
	}

	nl = mnl_socket_open(NETLINK_ROUTE);
	if (nl == NULL) {
		tst_res(TFAIL, "mnl_socket_open failed (errno=%d): %s", errno,
			strerror(errno));
		goto err;
	}

	if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
		tst_res(TFAIL, "mnl_socket_bind failed (errno=%d): %s", errno,
			strerror(errno));
		goto err;
	}

	portid = mnl_socket_get_portid(nl);

	if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) {
		tst_res(TFAIL, "mnl_socket_sendto failed (errno=%d): %s", errno,
			strerror(errno));
		goto err;
	}

	ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
	if (ret < 0) {
		tst_res(TFAIL, "mnl_socket_recvfrom failed (ret=%d, errno=%d): %s",
			ret, errno, strerror(errno));
		goto err;
	}

	ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL);
	if (ret < 0) {
		tst_res(TFAIL, "mnl_cb_run failed (ret=%d, errno=%d): %s", ret,
			errno, strerror(errno));
		goto err;
	}

	mnl_socket_close(nl);
	return;
err:
	print_route_info(iface, dst->ai_addr, gw ? gw->ai_addr: NULL, type);
}


> > +
> > +static struct tst_option options[] = {
> > +	{"6", &ipv6_opt, "-6       Use IPv6 (default is IPv4)"},
> > +	{"c:", &c_opt, "         Num loops (mandatory)"},

> "-c x ...
Fixed.


BTW I also removed prefix parameter from rtnl_route() (it's a global parameter,
family is not passed either).

https://github.com/pevik/ltp/blob/route/c.v5.fixes/testcases/network/stress/route/route-change-netlink.c
+ below is full diff against posted version.

Kind regards,
Petr

diff --git testcases/network/stress/route/route-change-netlink.c testcases/network/stress/route/route-change-netlink.c
index 57ae02a3c..80677f1a4 100644
--- testcases/network/stress/route/route-change-netlink.c
+++ testcases/network/stress/route/route-change-netlink.c
@@ -177,7 +177,22 @@ static void cleanup(void)
 		mnl_socket_close(nl);
 }
 
-static void rtnl_route(int iface, struct addrinfo *dst, struct addrinfo *gw, uint32_t prefix, int type)
+static void print_route_info(int iface, struct sockaddr *dst,
+			     struct sockaddr *gw, int type)
+{
+	char dst_str[INET6_ADDRSTRLEN], gw_str[INET6_ADDRSTRLEN];
+	tst_sock_addr(dst, sizeof(dst), dst_str, sizeof(dst_str));
+	if (gw)
+		tst_sock_addr(gw, sizeof(gw), gw_str, sizeof(gw_str));
+
+	tst_res(TINFO, "type: %s, iface: %d, dst: %s, gw: %s",
+		type == RTM_NEWROUTE ? "RTM_NEWROUTE" : "RTM_DELROUTE",
+		iface, dst_str, gw ? gw_str : "null");
+	tst_brk(TBROK, "failed due previous netlink errors");
+}
+
+static void rtnl_route(int iface, struct addrinfo *dst, struct addrinfo *gw,
+		       int type)
 {
 	struct mnl_socket *nl;
 	char buf[MNL_SOCKET_BUFFER_SIZE];
@@ -229,31 +244,44 @@ static void rtnl_route(int iface, struct addrinfo *dst, struct addrinfo *gw, uin
 	}
 
 	nl = mnl_socket_open(NETLINK_ROUTE);
-	if (nl == NULL)
-		tst_brk(TBROK, "mnl_socket_open failed (errno=%d): %s", errno,
+	if (nl == NULL) {
+		tst_res(TFAIL, "mnl_socket_open failed (errno=%d): %s", errno,
 			strerror(errno));
+		goto err;
+	}
 
-	if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0)
-		tst_brk(TBROK, "mnl_socket_bind failed (errno=%d): %s", errno,
+	if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
+		tst_res(TFAIL, "mnl_socket_bind failed (errno=%d): %s", errno,
 			strerror(errno));
+		goto err;
+	}
 
 	portid = mnl_socket_get_portid(nl);
 
-	if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0)
-		tst_brk(TBROK, "mnl_socket_sendto failed (errno=%d): %s", errno,
+	if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) {
+		tst_res(TFAIL, "mnl_socket_sendto failed (errno=%d): %s", errno,
 			strerror(errno));
+		goto err;
+	}
 
 	ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
-	if (ret < 0)
-		tst_brk(TBROK, "mnl_socket_recvfrom failed (ret=%d, errno=%d): %s",
+	if (ret < 0) {
+		tst_res(TFAIL, "mnl_socket_recvfrom failed (ret=%d, errno=%d): %s",
 			ret, errno, strerror(errno));
+		goto err;
+	}
 
 	ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL);
-	if (ret < 0)
-		tst_brk(TBROK, "mnl_cb_run failed (ret=%d, errno=%d): %s", ret,
+	if (ret < 0) {
+		tst_res(TFAIL, "mnl_cb_run failed (ret=%d, errno=%d): %s", ret,
 			errno, strerror(errno));
+		goto err;
+	}
 
 	mnl_socket_close(nl);
+	return;
+err:
+	print_route_info(iface, dst->ai_addr, gw ? gw->ai_addr: NULL, type);
 }
 
 static void send_udp(struct addrinfo *rhost_addrinfo)
@@ -274,19 +302,11 @@ static void run(void)
 	struct iface *p_iface = iface;
 
 	for (i = 0; i < num_loops; i++) {
-		if (iface_len > 1)
-			tst_res(TINFO, "testing gw: %s, iface: %s",
-					p_gw->ip_str, p_iface->iface);
-		else if (gw_len > 1)
-			tst_res(TINFO, "testing gw: %s", p_gw->ip_str);
-		else
-			tst_res(TINFO, "testing dst: %s/%d", p_dst->ip_str, prefix);
-
 		rtnl_route(p_iface->index, p_dst->ip, gw ? p_gw->ip : NULL,
-			   prefix, RTM_NEWROUTE);
+			   RTM_NEWROUTE);
 		send_udp(p_rhost->ip);
 		rtnl_route(p_iface->index, p_dst->ip, gw ? p_gw->ip : NULL,
-			   prefix, RTM_DELROUTE);
+			   RTM_DELROUTE);
 
 		if (gw)
 			p_gw = p_gw->next ?: gw;
@@ -300,7 +320,7 @@ static void run(void)
 
 static struct tst_option options[] = {
 	{"6", &ipv6_opt, "-6       Use IPv6 (default is IPv4)"},
-	{"c:", &c_opt, "         Num loops (mandatory)"},
+	{"c:", &c_opt, "-c x     Num loops (mandatory)"},
 	{"d:", &d_opt, "-d iface Interface to work on (mandatory)"},
 	{"g:", &g_opt, "-g x     Gateway IP"},
 	{"p:", &p_opt, "-p port  Rhost port (mandatory)"},
Alexey Kodanev April 29, 2020, 5:04 p.m. UTC | #3
On 29.04.2020 13:41, Petr Vorel wrote:
> Hi Alexey,
> 
>> Minor comments below.
> Thanks for your review!
> 

Hi Petr,

>>> +	for (i = 0; i < num_loops; i++) {
>>> +		if (iface_len > 1)
>>> +			tst_res(TINFO, "testing gw: %s, iface: %s",
>>> +					p_gw->ip_str, p_iface->iface);
>>> +		else if (gw_len > 1)
>>> +			tst_res(TINFO, "testing gw: %s", p_gw->ip_str);
>>> +		else
>>> +			tst_res(TINFO, "testing dst: %s/%d", p_dst->ip_str, prefix);
> 
> 
>> It would be better to avoid printing it on every iteration, especially
>> with the large NS_TIMES.
> Understand, I thought you wouldn't like it. How about print only on error?
> 
>>> +
>>> +		rtnl_route(p_iface->index, p_dst->ip, gw ? p_gw->ip : NULL,
>>> +			   prefix, RTM_NEWROUTE);
>>> +		send_udp(p_rhost->ip);
>>> +		rtnl_route(p_iface->index, p_dst->ip, gw ? p_gw->ip : NULL,
>>> +			   prefix, RTM_DELROUTE);
>>> +
>>> +		if (gw)
>>> +			p_gw = p_gw->next ?: gw;
>>> +		p_dst = p_dst->next ?: dst;
>>> +		p_iface = p_iface->next ?: iface;
>>> +		p_rhost = p_rhost->next ?: rhost;
>>> +	}
>>> +
>>> +	tst_res(TPASS, "routes created and deleted");
>>> +}
> 
> Something like this?
> 
> static void print_route_info(int iface, struct sockaddr *dst,
> 			     struct sockaddr *gw, int type)
> {

Perhaps brk_on_route_error() as it has tst_brk()?

> 	char dst_str[INET6_ADDRSTRLEN], gw_str[INET6_ADDRSTRLEN];
> 	tst_sock_addr(dst, sizeof(dst), dst_str, sizeof(dst_str));
> 	if (gw)
> 		tst_sock_addr(gw, sizeof(gw), gw_str, sizeof(gw_str));
> 
> 	tst_res(TINFO, "type: %s, iface: %d, dst: %s, gw: %s",
> 		type == RTM_NEWROUTE ? "RTM_NEWROUTE" : "RTM_DELROUTE",
> 		iface, dst_str, gw ? gw_str : "null");
> 	tst_brk(TBROK, "failed due previous netlink errors");

"failed due to the previous..."


Also, what about passing the error message type and errno to this
function, i.e. changing "TFAIL, TINFO, TBROK" to "TINFO, TFAIL"?

static void brk_on_route_error(const char *msg, int errno, int iface, ...
{
...
        tst_res(TINFO, "...");
	tst_brk(TFAIL, "%s failed (errno=%d): %s", msg, errno, strerror(errno));

> }
> 
> static void rtnl_route(int iface, struct addrinfo *dst, struct addrinfo *gw,
> 		       int type)
> {
> 	struct mnl_socket *nl;
> 	char buf[MNL_SOCKET_BUFFER_SIZE];
> 	struct nlmsghdr *nlh;
> 	struct rtmsg *rtm;
> 	uint32_t seq, portid;
> 	struct in6_addr dst_in6, gw_in6;
> 	in_addr_t dst_ip, gw_ip;
> 	int ret;> 
> 	nlh = mnl_nlmsg_put_header(buf);
> 	nlh->nlmsg_type	= type;
> 
> 	nlh->nlmsg_flags = NLM_F_ACK;
> 	if (type == RTM_NEWROUTE)
> 		nlh->nlmsg_flags |= NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE;
> 
> 	nlh->nlmsg_seq = seq = time(NULL);
> 
> 	rtm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct rtmsg));
> 	rtm->rtm_family = family;
> 	rtm->rtm_dst_len = prefix;
> 	rtm->rtm_src_len = 0;
> 	rtm->rtm_tos = 0;
> 	rtm->rtm_protocol = RTPROT_STATIC;
> 	rtm->rtm_table = RT_TABLE_MAIN;
> 	rtm->rtm_type = RTN_UNICAST;
> 	rtm->rtm_scope = gw ? RT_SCOPE_UNIVERSE : RT_SCOPE_LINK;
> 	rtm->rtm_flags = 0;
> 
> 	if (is_ipv6) {
> 		dst_in6 = ((struct sockaddr_in6 *)dst->ai_addr)->sin6_addr;
> 		mnl_attr_put(nlh, RTA_DST, sizeof(struct in6_addr), &dst_in6);
> 	} else {
> 		dst_ip = ((struct sockaddr_in *)dst->ai_addr)->sin_addr.s_addr;
> 		mnl_attr_put_u32(nlh, RTA_DST, dst_ip);
> 	}
> 
> 	mnl_attr_put_u32(nlh, RTA_OIF, iface);
> 
> 	if (gw) {
> 		if (is_ipv6) {
> 			gw_in6 = ((struct sockaddr_in6 *)gw->ai_addr)->sin6_addr;
> 			mnl_attr_put(nlh, RTA_GATEWAY, sizeof(struct in6_addr), &gw_in6);
> 		} else {
> 			gw_ip = ((struct sockaddr_in *)gw->ai_addr)->sin_addr.s_addr;
> 			mnl_attr_put_u32(nlh, RTA_GATEWAY, gw_ip);
> 		}
> 	}
> 
> 	nl = mnl_socket_open(NETLINK_ROUTE);
> 	if (nl == NULL) {
> 		tst_res(TFAIL, "mnl_socket_open failed (errno=%d): %s", errno,
> 			strerror(errno));
> 		goto err;

Then:
		brk_on_route_error("mnl_socket_open", errno, iface, dst, gw, type);


> 	}
> 
> 	if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
> 		tst_res(TFAIL, "mnl_socket_bind failed (errno=%d): %s", errno,
> 			strerror(errno));
> 		goto err;
> 	}
> 
> 	portid = mnl_socket_get_portid(nl);
> 
> 	if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) {
> 		tst_res(TFAIL, "mnl_socket_sendto failed (errno=%d): %s", errno,
> 			strerror(errno));
> 		goto err;
> 	}
> 
> 	ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
> 	if (ret < 0) {
> 		tst_res(TFAIL, "mnl_socket_recvfrom failed (ret=%d, errno=%d): %s",
> 			ret, errno, strerror(errno));
> 		goto err;
> 	}
> 
> 	ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL);
> 	if (ret < 0) {
> 		tst_res(TFAIL, "mnl_cb_run failed (ret=%d, errno=%d): %s", ret,
> 			errno, strerror(errno));
> 		goto err;
> 	}
> 
> 	mnl_socket_close(nl);
> 	return;
> err:
> 	print_route_info(iface, dst->ai_addr, gw ? gw->ai_addr: NULL, type);
> }
> 
> 
>>> +
>>> +static struct tst_option options[] = {
>>> +	{"6", &ipv6_opt, "-6       Use IPv6 (default is IPv4)"},
>>> +	{"c:", &c_opt, "         Num loops (mandatory)"},
> 
>> "-c x ...
> Fixed.
> 
> 
> BTW I also removed prefix parameter from rtnl_route() (it's a global parameter,
> family is not passed either).
> 
> https://github.com/pevik/ltp/blob/route/c.v5.fixes/testcases/network/stress/route/route-change-netlink.c
> + below is full diff against posted version.
> 
> Kind regards,
> Petr
> 
> diff --git testcases/network/stress/route/route-change-netlink.c testcases/network/stress/route/route-change-netlink.c
> index 57ae02a3c..80677f1a4 100644
> --- testcases/network/stress/route/route-change-netlink.c
> +++ testcases/network/stress/route/route-change-netlink.c
> @@ -177,7 +177,22 @@ static void cleanup(void)
>  		mnl_socket_close(nl);
>  }
>  
> -static void rtnl_route(int iface, struct addrinfo *dst, struct addrinfo *gw, uint32_t prefix, int type)
> +static void print_route_info(int iface, struct sockaddr *dst,
> +			     struct sockaddr *gw, int type)
> +{
> +	char dst_str[INET6_ADDRSTRLEN], gw_str[INET6_ADDRSTRLEN];
> +	tst_sock_addr(dst, sizeof(dst), dst_str, sizeof(dst_str));
> +	if (gw)
> +		tst_sock_addr(gw, sizeof(gw), gw_str, sizeof(gw_str));
> +
> +	tst_res(TINFO, "type: %s, iface: %d, dst: %s, gw: %s",
> +		type == RTM_NEWROUTE ? "RTM_NEWROUTE" : "RTM_DELROUTE",
> +		iface, dst_str, gw ? gw_str : "null");
> +	tst_brk(TBROK, "failed due previous netlink errors");
> +}
> +
> +static void rtnl_route(int iface, struct addrinfo *dst, struct addrinfo *gw,
> +		       int type)
>  {
>  	struct mnl_socket *nl;
>  	char buf[MNL_SOCKET_BUFFER_SIZE];
> @@ -229,31 +244,44 @@ static void rtnl_route(int iface, struct addrinfo *dst, struct addrinfo *gw, uin
>  	}
>  
>  	nl = mnl_socket_open(NETLINK_ROUTE);
> -	if (nl == NULL)
> -		tst_brk(TBROK, "mnl_socket_open failed (errno=%d): %s", errno,
> +	if (nl == NULL) {
> +		tst_res(TFAIL, "mnl_socket_open failed (errno=%d): %s", errno,
>  			strerror(errno));
> +		goto err;
> +	}
>  
> -	if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0)
> -		tst_brk(TBROK, "mnl_socket_bind failed (errno=%d): %s", errno,
> +	if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
> +		tst_res(TFAIL, "mnl_socket_bind failed (errno=%d): %s", errno,
>  			strerror(errno));
> +		goto err;
> +	}
>  
>  	portid = mnl_socket_get_portid(nl);
>  
> -	if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0)
> -		tst_brk(TBROK, "mnl_socket_sendto failed (errno=%d): %s", errno,
> +	if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) {
> +		tst_res(TFAIL, "mnl_socket_sendto failed (errno=%d): %s", errno,
>  			strerror(errno));
> +		goto err;
> +	}
>  
>  	ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
> -	if (ret < 0)
> -		tst_brk(TBROK, "mnl_socket_recvfrom failed (ret=%d, errno=%d): %s",
> +	if (ret < 0) {
> +		tst_res(TFAIL, "mnl_socket_recvfrom failed (ret=%d, errno=%d): %s",
>  			ret, errno, strerror(errno));
> +		goto err;
> +	}
>  
>  	ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL);
> -	if (ret < 0)
> -		tst_brk(TBROK, "mnl_cb_run failed (ret=%d, errno=%d): %s", ret,
> +	if (ret < 0) {
> +		tst_res(TFAIL, "mnl_cb_run failed (ret=%d, errno=%d): %s", ret,
>  			errno, strerror(errno));
> +		goto err;
> +	}
>  
>  	mnl_socket_close(nl);
> +	return;
> +err:
> +	print_route_info(iface, dst->ai_addr, gw ? gw->ai_addr: NULL, type);
>  }
>  
>  static void send_udp(struct addrinfo *rhost_addrinfo)
> @@ -274,19 +302,11 @@ static void run(void)
>  	struct iface *p_iface = iface;
>  
>  	for (i = 0; i < num_loops; i++) {
> -		if (iface_len > 1)
> -			tst_res(TINFO, "testing gw: %s, iface: %s",
> -					p_gw->ip_str, p_iface->iface);
> -		else if (gw_len > 1)
> -			tst_res(TINFO, "testing gw: %s", p_gw->ip_str);
> -		else
> -			tst_res(TINFO, "testing dst: %s/%d", p_dst->ip_str, prefix);
> -
>  		rtnl_route(p_iface->index, p_dst->ip, gw ? p_gw->ip : NULL,
> -			   prefix, RTM_NEWROUTE);
> +			   RTM_NEWROUTE);
>  		send_udp(p_rhost->ip);
>  		rtnl_route(p_iface->index, p_dst->ip, gw ? p_gw->ip : NULL,
> -			   prefix, RTM_DELROUTE);
> +			   RTM_DELROUTE);
>  
>  		if (gw)
>  			p_gw = p_gw->next ?: gw;
> @@ -300,7 +320,7 @@ static void run(void)
>  
>  static struct tst_option options[] = {
>  	{"6", &ipv6_opt, "-6       Use IPv6 (default is IPv4)"},
> -	{"c:", &c_opt, "         Num loops (mandatory)"},
> +	{"c:", &c_opt, "-c x     Num loops (mandatory)"},
>  	{"d:", &d_opt, "-d iface Interface to work on (mandatory)"},
>  	{"g:", &g_opt, "-g x     Gateway IP"},
>  	{"p:", &p_opt, "-p port  Rhost port (mandatory)"},
>
Petr Vorel April 29, 2020, 6:47 p.m. UTC | #4
Hi Alexey,

> Perhaps brk_on_route_error() as it has tst_brk()?

> > 	char dst_str[INET6_ADDRSTRLEN], gw_str[INET6_ADDRSTRLEN];
> > 	tst_sock_addr(dst, sizeof(dst), dst_str, sizeof(dst_str));
> > 	if (gw)
> > 		tst_sock_addr(gw, sizeof(gw), gw_str, sizeof(gw_str));

> > 	tst_res(TINFO, "type: %s, iface: %d, dst: %s, gw: %s",
> > 		type == RTM_NEWROUTE ? "RTM_NEWROUTE" : "RTM_DELROUTE",
> > 		iface, dst_str, gw ? gw_str : "null");
> > 	tst_brk(TBROK, "failed due previous netlink errors");

> "failed due to the previous..."


> Also, what about passing the error message type and errno to this
> function, i.e. changing "TFAIL, TINFO, TBROK" to "TINFO, TFAIL"?

> static void brk_on_route_error(const char *msg, int errno, int iface, ...
> {
> ...
>         tst_res(TINFO, "...");
> 	tst_brk(TFAIL, "%s failed (errno=%d): %s", msg, errno, strerror(errno));

...
> Then:
> 		brk_on_route_error("mnl_socket_open", errno, iface, dst, gw, type);

Thanks for a suggestion, that's much better.

I've just removed errno as a parameter and used it directly, because gcc complained:

route-change-netlink.c:248:41: warning: passing argument 2 of ‘brk_on_route_error’ makes pointer from integer without a cast [-Wint-conversion]
  248 |   brk_on_route_error("mnl_socket_open", errno, iface,
      |                                         ^~~~~
      |                                         |
      |                                         int
In file included from ../../../../include/tst_test.h:17,
                 from route-change-netlink.c:7:
route-change-netlink.c:180:53: note: expected ‘int * (*)()’ but argument is of type ‘int’
  180 | static void brk_on_route_error(const char *msg, int errno, int iface,

I'll send v6, which hopefully could be final version.

Kind regards,
Petr
diff mbox series

Patch

diff --git a/configure.ac b/configure.ac
index e1069b57e..9ccf8c3ea 100644
--- a/configure.ac
+++ b/configure.ac
@@ -300,6 +300,7 @@  LTP_CHECK_CRYPTO
 LTP_CHECK_FORTIFY_SOURCE
 LTP_CHECK_KERNEL_DEVEL
 LTP_CHECK_KEYUTILS_SUPPORT
+LTP_CHECK_LIBMNL
 LTP_CHECK_LINUX_PTRACE
 LTP_CHECK_LINUXRANDOM
 LTP_CHECK_MREMAP_FIXED
diff --git a/include/mk/config.mk.in b/include/mk/config.mk.in
index 5526cfea1..87b2c833d 100644
--- a/include/mk/config.mk.in
+++ b/include/mk/config.mk.in
@@ -43,6 +43,8 @@  TIRPC_CFLAGS		:= @TIRPC_CFLAGS@
 TIRPC_LIBS		:= @TIRPC_LIBS@
 KEYUTILS_LIBS		:= @KEYUTILS_LIBS@
 HAVE_FTS_H		:= @HAVE_FTS_H@
+LIBMNL_LIBS		:= @LIBMNL_LIBS@
+LIBMNL_CFLAGS		:= @LIBMNL_CFLAGS@
 
 prefix			:= @prefix@
 
diff --git a/m4/ltp-libmnl.m4 b/m4/ltp-libmnl.m4
new file mode 100644
index 000000000..be8ab4584
--- /dev/null
+++ b/m4/ltp-libmnl.m4
@@ -0,0 +1,8 @@ 
+dnl SPDX-License-Identifier: GPL-2.0-or-later
+dnl Copyright (c) 2019 Petr Vorel <pvorel@suse.cz>
+
+AC_DEFUN([LTP_CHECK_LIBMNL], [
+    PKG_CHECK_MODULES([LIBMNL], [libmnl], [
+        AC_DEFINE([HAVE_LIBMNL], [1], [Define to 1 if you have libmnl library and headers])
+	], [have_libmnl=no])
+])
diff --git a/runtest/net_stress.route b/runtest/net_stress.route
index 4eff6766a..8755ef0be 100644
--- a/runtest/net_stress.route
+++ b/runtest/net_stress.route
@@ -2,11 +2,17 @@ 
 route4-change-dst route-change-dst.sh
 route4-change-gw route-change-gw.sh
 route4-change-if route-change-if.sh
+route4-change-netlink-dst route-change-netlink-dst.sh
+route4-change-netlink-gw route-change-netlink-gw.sh
+route4-change-netlink-if route-change-netlink-if.sh
 route4-redirect route4-redirect
 route4-rmmod route4-rmmod
 
 route6-change-dst route-change-dst.sh -6
 route6-change-gw route-change-gw.sh -6
 route6-change-if route-change-if.sh -6
+route6-change-netlink-dst route-change-netlink-dst.sh -6
+route6-change-netlink-gw route-change-netlink-gw.sh -6
+route6-change-netlink-if route-change-netlink-if.sh -6
 route6-redirect route6-redirect
 route6-rmmod route6-rmmod
diff --git a/testcases/network/stress/route/.gitignore b/testcases/network/stress/route/.gitignore
new file mode 100644
index 000000000..4ed647a55
--- /dev/null
+++ b/testcases/network/stress/route/.gitignore
@@ -0,0 +1 @@ 
+/route-change-netlink
diff --git a/testcases/network/stress/route/00_Descriptions.txt b/testcases/network/stress/route/00_Descriptions.txt
index e0e2e42c6..d148d51d9 100644
--- a/testcases/network/stress/route/00_Descriptions.txt
+++ b/testcases/network/stress/route/00_Descriptions.txt
@@ -1,36 +1,26 @@ 
 route{4,6}-change-dst
-	Verify the IPv4/IPv6 is not broken when ip command changes route
-	destination many times
+	Change IPv4/IPv6 route destination by ip command
 
 route{4,6}-change-gw
-	Verify the IPv4/IPv6 is not broken when ip command changes route
-	gateway many times
+	Change IPv4/IPv6 route gateway by ip command
 
 route{4,6}-change-if
-	Verify the IPv4/IPv6 is not broken when ip command changes route
-	interface many times
+	Change IPv4/IPv6 route interface by ip command
 
-route4-redirect01
-	Verify the kernel is not crashed when the IPv4 route is modified by
-	ICMP Redirects frequently
+route{4,6}-change-netlink-dst
+	Change IPv4/IPv6 route destination by Netlink API via libmnl
 
-route4-rmmod01
-	Verify the kernel is not crashed when IPv4 route is add by route command
-	then it is deleted by the removing network driver
+route{4,6}-change-netlink-gw
+	Change IPv4/IPv6 route gateway by Netlink API via libmnl
 
-route4-rmmod02
-	Verify the kernel is not crashed when IPv4 route is add by ip command
-	then it is deleted by the removing network driver
+route{4,6}-change-netlink-if
+	Change IPv4/IPv6 route interface by Netlink API via libmnl
 
+route{4,6}-redirect01
+	Change IPv4/IPv6 route by ICMP Redirects frequently
 
-route6-redirect01
-	Verify the kernel is not crashed when the IPv6 route is modified by
-	ICMP Redirects frequently
+route{4,6}-rmmod01
+	Add IPv4/IPv6 route by route command then delete it by the removing network driver
 
-route6-rmmod01
-	Verify the kernel is not crashed when IPv6 route is add by route command
-	then it is deleted by the removing network driver
-
-route6-rmmod02
-	Verify the kernel is not crashed when IPv6 route is add by ip command
-	then it is deleted by the removing network driver
+route{4,6}-rmmod02
+	Add IPv4/IPv6 route by ip command then delete it by the removing network driver
diff --git a/testcases/network/stress/route/Makefile b/testcases/network/stress/route/Makefile
index 2e5eaa2f2..b4e6409be 100644
--- a/testcases/network/stress/route/Makefile
+++ b/testcases/network/stress/route/Makefile
@@ -1,29 +1,15 @@ 
-#
-#    network/stress/route test suite Makefile.
-#
-#    Copyright (C) 2009, Cisco Systems Inc.
-#
-#    This program is free software; you can redistribute it and/or modify
-#    it under the terms of the GNU General Public License as published by
-#    the Free Software Foundation; either version 2 of the License, or
-#    (at your option) any later version.
-#
-#    This program is distributed in the hope that it will be useful,
-#    but WITHOUT ANY WARRANTY; without even the implied warranty of
-#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#    GNU General Public License for more details.
-#
-#    You should have received a copy of the GNU General Public License along
-#    with this program; if not, write to the Free Software Foundation, Inc.,
-#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-#
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (C) 2009, Cisco Systems Inc.
+# Copyright (c) Linux Test Project, 2006-2020
 # Ngie Cooper, October 2009
-#
 
 top_srcdir		?= ../../../..
 
-include $(top_srcdir)/include/mk/env_pre.mk
+include $(top_srcdir)/include/mk/testcases.mk
 
-INSTALL_TARGETS		:= route*
+INSTALL_TARGETS		+= route[4-6]-* *.sh
+
+route-change-netlink: CFLAGS += $(LIBMNL_CFLAGS)
+route-change-netlink: LDLIBS += $(LIBMNL_LIBS)
 
 include $(top_srcdir)/include/mk/generic_leaf_target.mk
diff --git a/testcases/network/stress/route/route-change-dst.sh b/testcases/network/stress/route/route-change-dst.sh
index fff79fa6e..e883b7e24 100755
--- a/testcases/network/stress/route/route-change-dst.sh
+++ b/testcases/network/stress/route/route-change-dst.sh
@@ -4,11 +4,14 @@ 
 # Copyright (c) International Business Machines Corp., 2006
 # Author: Mitsuru Chinen <mitch@jp.ibm.com>
 # Rewrite into new shell API: Petr Vorel
-#
+
 # Change route destination
 # lhost: 10.0.0.2, rhost: 10.23.x.1
 
+TST_SETUP="setup"
+TST_CLEANUP="route_cleanup"
 TST_TESTFUNC="test_dst"
+TST_CNT=$NS_TIMES
 . route-lib.sh
 
 setup()
diff --git a/testcases/network/stress/route/route-change-gw.sh b/testcases/network/stress/route/route-change-gw.sh
index fc88ee0f3..f7a08cf91 100755
--- a/testcases/network/stress/route/route-change-gw.sh
+++ b/testcases/network/stress/route/route-change-gw.sh
@@ -1,27 +1,19 @@ 
 #!/bin/sh
 # SPDX-License-Identifier: GPL-2.0-or-later
-# Copyright (c) 2019 Petr Vorel <pvorel@suse.cz>
+# Copyright (c) 2019-2020 Petr Vorel <pvorel@suse.cz>
 # Copyright (c) International Business Machines Corp., 2006
 # Author: Mitsuru Chinen <mitch@jp.ibm.com>
 # Rewrite into new shell API: Petr Vorel
-#
+
 # Change route gateway
 # lhost: 10.23.1.1, gw (on rhost): 10.23.1.x, rhost: 10.23.0.1
 
+TST_SETUP="setup_gw"
+TST_CLEANUP="route_cleanup"
 TST_TESTFUNC="test_gw"
+TST_CNT=$NS_TIMES
 . route-lib.sh
 
-setup()
-{
-	tst_res TINFO "change IPv$TST_IPVER route gateway $NS_TIMES times"
-
-	rt="$(tst_ipaddr_un -p 0 0)"
-	lhost="$(tst_ipaddr_un 1 1)"
-	rhost="$(tst_ipaddr_un 0 1)"
-	tst_add_ipaddr -s -q -a $lhost
-	tst_add_ipaddr -s -q -a $rhost rhost
-}
-
 test_gw()
 {
 	local gw="$(tst_ipaddr_un -h 2,254 1 $(($1 + 1)))"
diff --git a/testcases/network/stress/route/route-change-if.sh b/testcases/network/stress/route/route-change-if.sh
index 77661a45e..489d9be92 100755
--- a/testcases/network/stress/route/route-change-if.sh
+++ b/testcases/network/stress/route/route-change-if.sh
@@ -4,61 +4,15 @@ 
 # Copyright (c) International Business Machines Corp., 2006
 # Author: Mitsuru Chinen <mitch@jp.ibm.com>
 # Rewrite into new shell API: Petr Vorel
-#
+
 # Change route interface
 # lhost: 10.23.x.2, gw (on rhost): 10.23.x.1, rhost: 10.23.0.1, switching ifaces on lhost
 
+TST_SETUP="setup_if"
+TST_CLEANUP="cleanup_if"
 TST_TESTFUNC="test_if"
+TST_CNT=$NS_TIMES
 . route-lib.sh
-TST_CLEANUP="cleanup"
-
-add_macvlan()
-{
-	local action="add"
-	local OPTIND
-	while getopts d opt; do
-		case "$opt" in
-		d) action="del";;
-		esac
-	done
-	shift $((OPTIND - 1))
-
-	local iface="$1"
-	local type="${2:-lhost}"
-
-	cmd="ip link $action $iface link $(tst_iface $type) type macvlan mode bridge"
-	if [ $type = "lhost" ]; then
-		ROD $cmd
-		[ "$action" = "add" ] || return
-		LHOST_IFACES="$LHOST_IFACES $iface"
-	else
-		tst_rhost_run -s -c "$cmd"
-		[ "$action" = "add" ] || return
-		RHOST_IFACES="$RHOST_IFACES $iface"
-	fi
-	tst_init_iface $type 1
-}
-
-setup()
-{
-	tst_res TINFO "change IPv$TST_IPVER route interface $NS_TIMES times"
-
-	rt="$(tst_ipaddr_un -p 0)"
-	rhost="$(tst_ipaddr_un 0 1)"
-	tst_add_ipaddr -s -q -a $rhost rhost
-
-	if [ $(tst_get_ifaces_cnt) -lt 2 ]; then
-		new_liface="ltp_mv2"
-		tst_res TINFO "2 or more local ifaces required, adding $new_liface"
-		add_macvlan $new_liface
-	fi
-
-	if [ $(tst_get_ifaces_cnt rhost) -lt 2 ]; then
-		new_riface="ltp_mv1"
-		tst_res TINFO "2 or more remote ifaces required, adding $new_riface"
-		add_macvlan $new_riface rhost
-	fi
-}
 
 test_if()
 {
@@ -78,11 +32,4 @@  test_if()
 	tst_del_ipaddr -s -q -a $gw rhost $link_num
 }
 
-cleanup()
-{
-	[ "$new_liface" ] && add_macvlan -d $new_liface
-	[ "$new_riface" ] && add_macvlan -d $new_riface rhost
-	route_cleanup
-}
-
 tst_run
diff --git a/testcases/network/stress/route/route-change-netlink-dst.sh b/testcases/network/stress/route/route-change-netlink-dst.sh
new file mode 100755
index 000000000..113bd0caa
--- /dev/null
+++ b/testcases/network/stress/route/route-change-netlink-dst.sh
@@ -0,0 +1,36 @@ 
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (c) 2020 Petr Vorel <pvorel@suse.cz>
+
+# Change route destination via netlink
+# rhost: 10.23.x.1
+# lhost (iface set, but not specified in Netlink API): 10.0.0.2
+
+TST_SETUP="setup"
+TST_CLEANUP="route_cleanup"
+TST_TESTFUNC="test_netlink"
+. route-lib.sh
+
+setup()
+{
+	tst_res TINFO "change IPv$TST_IPVER route destination $NS_TIMES times"
+
+	local cnt=0
+	local gw rhost rhost_all rt
+
+	check_max_ip
+
+	while [ $cnt -lt $ROUTE_MAX_IP ]; do
+		rt="$(tst_ipaddr_un -p $cnt)"
+		rhost="$(tst_ipaddr_un $cnt 1)"
+		rhost_all="$rhost$IP_ADDR_DELIM$rhost_all"
+
+		tst_add_ipaddr -s -q -a $rhost rhost
+		ROD ip route add $rt dev $(tst_iface)
+		cnt=$((cnt+1))
+	done
+
+	ROUTE_CHANGE_NETLINK_PARAMS="-d $(tst_iface) -r '$rhost_all'"
+}
+
+tst_run
diff --git a/testcases/network/stress/route/route-change-netlink-gw.sh b/testcases/network/stress/route/route-change-netlink-gw.sh
new file mode 100755
index 000000000..fec09683c
--- /dev/null
+++ b/testcases/network/stress/route/route-change-netlink-gw.sh
@@ -0,0 +1,31 @@ 
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (c) 2020 Petr Vorel <pvorel@suse.cz>
+
+# Change route gateway via netlink
+# gw (on rhost): 10.23.1.x, rhost: 10.23.0.1
+# lhost (iface set, but not specified in Netlink API): 10.23.1.1
+
+TST_SETUP="setup"
+TST_CLEANUP="route_cleanup"
+TST_TESTFUNC="test_netlink"
+. route-lib.sh
+
+setup()
+{
+	local cnt=0
+
+	check_max_ip
+	setup_gw
+
+	while [ $cnt -lt $ROUTE_MAX_IP ]; do
+		gw="$(tst_ipaddr_un -h 2,$max_ip_limit 1 $(($cnt + 1)))"
+		gw_all="$gw$IP_ADDR_DELIM$gw_all"
+		tst_add_ipaddr -s -q -a $gw rhost
+		cnt=$((cnt+1))
+	done
+
+	ROUTE_CHANGE_NETLINK_PARAMS="-d $(tst_iface) -g "$gw_all" -r $rhost"
+}
+
+tst_run
diff --git a/testcases/network/stress/route/route-change-netlink-if.sh b/testcases/network/stress/route/route-change-netlink-if.sh
new file mode 100755
index 000000000..3a095ff4b
--- /dev/null
+++ b/testcases/network/stress/route/route-change-netlink-if.sh
@@ -0,0 +1,38 @@ 
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (c) 2020 Petr Vorel <pvorel@suse.cz>
+
+# Change route interface
+# gw (on rhost): 10.23.x.1, rhost: 10.23.0.1, switching ifaces on lhost
+# lhost (iface set, but not specified in Netlink API): 10.23.x.2
+
+TST_SETUP="setup"
+TST_CLEANUP="cleanup_if"
+TST_TESTFUNC="test_netlink"
+. route-lib.sh
+
+setup()
+{
+	local gw gw_all iface iface_all
+	local cnt=0
+
+	setup_if
+
+	while [ $cnt -lt $(tst_get_ifaces_cnt) ]; do
+		gw="$(tst_ipaddr_un -n1 $cnt 1)"
+		iface="$(tst_iface lhost $cnt)"
+		lhost="$(tst_ipaddr_un -n1 $cnt 2)"
+
+		tst_add_ipaddr -s -q -a $lhost lhost $cnt
+		tst_add_ipaddr -s -q -a $gw rhost $cnt
+
+		gw_all="$gw$IP_ADDR_DELIM$gw_all"
+		iface_all="$iface$IP_ADDR_DELIM$iface_all"
+
+		cnt=$((cnt+1))
+	done
+
+	ROUTE_CHANGE_NETLINK_PARAMS="-d '$iface_all' -g '$gw_all' -r $rhost"
+}
+
+tst_run
diff --git a/testcases/network/stress/route/route-change-netlink.c b/testcases/network/stress/route/route-change-netlink.c
new file mode 100644
index 000000000..57ae02a3c
--- /dev/null
+++ b/testcases/network/stress/route/route-change-netlink.c
@@ -0,0 +1,320 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2020 Petr Vorel <pvorel@suse.cz>
+ */
+
+#include "config.h"
+#include "tst_test.h"
+
+#ifdef HAVE_LIBMNL
+
+#include <string.h>
+
+#include <libmnl/libmnl.h>
+#include <linux/rtnetlink.h>
+#include <net/if.h>
+#include <netdb.h>
+#include <netinet/in.h>
+
+#include "tst_net.h"
+#include "tst_safe_net.h"
+#include "tst_safe_stdio.h"
+
+#define NS_TIMES_MAX 65535
+
+#define IP_ADDR_DELIM ','
+#define STR(x) #x
+#define CHR2STR(x) STR(x)
+
+static char *c_opt, *d_opt, *g_opt, *ipv6_opt, *p_opt, *r_opt;
+
+static int family = AF_INET;
+static int fd, num_loops, port;
+
+static unsigned int is_ipv6, max, prefix;
+
+static struct mnl_socket *nl;
+static struct addrinfo hints;
+
+struct iface {
+	unsigned int index;
+	struct iface *next;
+	char iface[IFNAMSIZ];
+};
+
+struct ip_addr {
+	struct addrinfo *ip;
+	struct ip_addr *next;
+	char ip_str[INET6_ADDRSTRLEN];
+};
+
+static struct ip_addr *dst, *gw, *rhost;
+static struct iface *iface;
+static unsigned int gw_len, iface_len, rhost_len;
+
+void save_iface(void **data, const char *item)
+{
+	struct iface *n = SAFE_MALLOC(sizeof(*n));
+	struct iface **list = (struct iface**)data;
+
+	strncpy(n->iface, item, sizeof(n->iface));
+	n->iface[sizeof(n->iface)-1] = '\0';
+
+	n->index = if_nametoindex(item);
+	if (!n->index)
+		tst_brk(TBROK, "if_nametoindex failed, '%s' not found", item);
+	n->next = *list;
+	*list = n;
+}
+
+void save_ip(void **data, const char *item)
+{
+	struct ip_addr *n = SAFE_MALLOC(sizeof(*n));
+	struct ip_addr **list = (struct ip_addr**)data;
+
+	strncpy(n->ip_str, item, sizeof(n->ip_str));
+	n->ip_str[sizeof(n->ip_str)-1] = '\0';
+
+	SAFE_GETADDRINFO(item, p_opt, &hints, &n->ip);
+	n->next = *list;
+	*list = n;
+}
+
+int save_item(void **list, char *item, void (*callback)(void **, const char *))
+{
+	int len = 0;
+
+	while ((item = strtok(item, CHR2STR(IP_ADDR_DELIM))) != NULL) {
+		callback(list, item);
+		item = NULL;
+		len++;
+	}
+
+	return len;
+}
+
+static void setup(void)
+{
+	prefix = 24;
+	if (ipv6_opt) {
+		family = AF_INET6;
+		is_ipv6 = 1;
+		prefix = 64;
+	}
+
+	if (!c_opt)
+		tst_brk(TBROK, "missing number of loops (-c num)");
+
+	if (!d_opt)
+		tst_brk(TBROK, "missing iface (-d iface)");
+
+	if (!p_opt)
+		tst_brk(TBROK, "missing rhost port (-p port)");
+
+	if (!r_opt)
+		tst_brk(TBROK, "missing rhost IP (-r IP)");
+
+	if (tst_parse_int(p_opt, &port, 1, 65535))
+		tst_brk(TBROK, "invalid rhost port '%s'", p_opt);
+
+	if (tst_parse_int(c_opt, &num_loops, 1, NS_TIMES_MAX)) {
+		num_loops = NS_TIMES_MAX;
+		tst_res(TWARN, "invalid number of loops (-c %s), using: %d",
+			c_opt, num_loops);
+	}
+
+	iface_len = save_item((void **)&iface, d_opt, save_iface);
+	rhost_len = save_item((void **)&rhost, r_opt, save_ip);
+
+	max = MAX(iface_len, rhost_len);
+	if (iface_len > 1 && rhost_len > 1 && iface_len != max)
+		tst_brk(TBROK, "-d specifies more NICs and -r more IPs, they need to have the same count");
+
+	if (g_opt) {
+		gw_len = save_item((void **)&gw, g_opt, save_ip);
+		max = MAX(gw_len, max);
+
+		if (gw_len > 1 && max > 1 && gw_len != max) {
+			if (iface_len == max)
+				tst_brk(TBROK, "-d specifies more NICs and -r more IPs, they need to have the same count");
+			else
+				tst_brk(TBROK, "-g and -r specify more IP, they need to have the same count");
+		}
+	}
+
+	struct ip_addr *p_rhost = rhost;
+
+	while (p_rhost) {
+		char dst_str[INET6_ADDRSTRLEN];
+
+		if (!strncpy(dst_str, p_rhost->ip_str, sizeof(dst_str)))
+			tst_brk(TBROK, "failed copy IP '%s'", p_rhost->ip_str);
+		dst_str[strlen(p_rhost->ip_str)-1] = '\0';
+
+		if (!strcat(dst_str, "0"))
+			tst_brk(TBROK, "strcat failed: '%s'", dst_str);
+
+		save_ip((void **)&dst, dst_str);
+		p_rhost = p_rhost->next;
+	}
+
+	fd = SAFE_SOCKET(family, SOCK_DGRAM, IPPROTO_UDP);
+
+	memset(&hints, 0, sizeof(struct addrinfo));
+	hints.ai_family = family;
+	hints.ai_socktype = SOCK_DGRAM;
+	hints.ai_flags = 0;
+	hints.ai_protocol = 0;
+	hints.ai_addr = INADDR_ANY;
+}
+
+static void cleanup(void)
+{
+	if (fd > 0)
+		close(fd);
+
+	if (nl)
+		mnl_socket_close(nl);
+}
+
+static void rtnl_route(int iface, struct addrinfo *dst, struct addrinfo *gw, uint32_t prefix, int type)
+{
+	struct mnl_socket *nl;
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	struct nlmsghdr *nlh;
+	struct rtmsg *rtm;
+	uint32_t seq, portid;
+	struct in6_addr dst_in6, gw_in6;
+	in_addr_t dst_ip, gw_ip;
+	int ret;
+
+	nlh = mnl_nlmsg_put_header(buf);
+	nlh->nlmsg_type	= type;
+
+	nlh->nlmsg_flags = NLM_F_ACK;
+	if (type == RTM_NEWROUTE)
+		nlh->nlmsg_flags |= NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE;
+
+	nlh->nlmsg_seq = seq = time(NULL);
+
+	rtm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct rtmsg));
+	rtm->rtm_family = family;
+	rtm->rtm_dst_len = prefix;
+	rtm->rtm_src_len = 0;
+	rtm->rtm_tos = 0;
+	rtm->rtm_protocol = RTPROT_STATIC;
+	rtm->rtm_table = RT_TABLE_MAIN;
+	rtm->rtm_type = RTN_UNICAST;
+	rtm->rtm_scope = gw ? RT_SCOPE_UNIVERSE : RT_SCOPE_LINK;
+	rtm->rtm_flags = 0;
+
+	if (is_ipv6) {
+		dst_in6 = ((struct sockaddr_in6 *)dst->ai_addr)->sin6_addr;
+		mnl_attr_put(nlh, RTA_DST, sizeof(struct in6_addr), &dst_in6);
+	} else {
+		dst_ip = ((struct sockaddr_in *)dst->ai_addr)->sin_addr.s_addr;
+		mnl_attr_put_u32(nlh, RTA_DST, dst_ip);
+	}
+
+	mnl_attr_put_u32(nlh, RTA_OIF, iface);
+
+	if (gw) {
+		if (is_ipv6) {
+			gw_in6 = ((struct sockaddr_in6 *)gw->ai_addr)->sin6_addr;
+			mnl_attr_put(nlh, RTA_GATEWAY, sizeof(struct in6_addr), &gw_in6);
+		} else {
+			gw_ip = ((struct sockaddr_in *)gw->ai_addr)->sin_addr.s_addr;
+			mnl_attr_put_u32(nlh, RTA_GATEWAY, gw_ip);
+		}
+	}
+
+	nl = mnl_socket_open(NETLINK_ROUTE);
+	if (nl == NULL)
+		tst_brk(TBROK, "mnl_socket_open failed (errno=%d): %s", errno,
+			strerror(errno));
+
+	if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0)
+		tst_brk(TBROK, "mnl_socket_bind failed (errno=%d): %s", errno,
+			strerror(errno));
+
+	portid = mnl_socket_get_portid(nl);
+
+	if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0)
+		tst_brk(TBROK, "mnl_socket_sendto failed (errno=%d): %s", errno,
+			strerror(errno));
+
+	ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
+	if (ret < 0)
+		tst_brk(TBROK, "mnl_socket_recvfrom failed (ret=%d, errno=%d): %s",
+			ret, errno, strerror(errno));
+
+	ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL);
+	if (ret < 0)
+		tst_brk(TBROK, "mnl_cb_run failed (ret=%d, errno=%d): %s", ret,
+			errno, strerror(errno));
+
+	mnl_socket_close(nl);
+}
+
+static void send_udp(struct addrinfo *rhost_addrinfo)
+{
+	const char *msg = "foo";
+
+	SAFE_SENDTO(1, fd, msg, sizeof(msg), MSG_CONFIRM,
+		rhost_addrinfo->ai_addr, rhost_addrinfo->ai_addrlen);
+}
+
+static void run(void)
+{
+	int i;
+
+	tst_res(TINFO, "adding and deleting route %d times", num_loops);
+
+	struct ip_addr *p_dst = dst, *p_gw = gw, *p_rhost = rhost;
+	struct iface *p_iface = iface;
+
+	for (i = 0; i < num_loops; i++) {
+		if (iface_len > 1)
+			tst_res(TINFO, "testing gw: %s, iface: %s",
+					p_gw->ip_str, p_iface->iface);
+		else if (gw_len > 1)
+			tst_res(TINFO, "testing gw: %s", p_gw->ip_str);
+		else
+			tst_res(TINFO, "testing dst: %s/%d", p_dst->ip_str, prefix);
+
+		rtnl_route(p_iface->index, p_dst->ip, gw ? p_gw->ip : NULL,
+			   prefix, RTM_NEWROUTE);
+		send_udp(p_rhost->ip);
+		rtnl_route(p_iface->index, p_dst->ip, gw ? p_gw->ip : NULL,
+			   prefix, RTM_DELROUTE);
+
+		if (gw)
+			p_gw = p_gw->next ?: gw;
+		p_dst = p_dst->next ?: dst;
+		p_iface = p_iface->next ?: iface;
+		p_rhost = p_rhost->next ?: rhost;
+	}
+
+	tst_res(TPASS, "routes created and deleted");
+}
+
+static struct tst_option options[] = {
+	{"6", &ipv6_opt, "-6       Use IPv6 (default is IPv4)"},
+	{"c:", &c_opt, "         Num loops (mandatory)"},
+	{"d:", &d_opt, "-d iface Interface to work on (mandatory)"},
+	{"g:", &g_opt, "-g x     Gateway IP"},
+	{"p:", &p_opt, "-p port  Rhost port (mandatory)"},
+	{"r:", &r_opt, "-r x     Rhost IP (mandatory)\n\n-g, -r IP parameter can contain more IP, separated by "
+		CHR2STR(IP_ADDR_DELIM)},
+	{NULL, NULL, NULL}
+};
+static struct tst_test test = {
+	.test_all = run,
+	.needs_root = 1,
+	.setup = setup,
+	.cleanup = cleanup,
+	.options = options,
+};
+#else
+	TST_TEST_TCONF("libmnl library and headers are required");
+#endif /* HAVE_LIBMNL */
diff --git a/testcases/network/stress/route/route-lib.sh b/testcases/network/stress/route/route-lib.sh
index 4afbe6323..45fa7f3c9 100644
--- a/testcases/network/stress/route/route-lib.sh
+++ b/testcases/network/stress/route/route-lib.sh
@@ -3,15 +3,113 @@ 
 # Copyright (c) 2019 Petr Vorel <pvorel@suse.cz>
 
 TST_NEEDS_ROOT=1
-TST_SETUP="setup"
-TST_CLEANUP="route_cleanup"
 TST_NEEDS_CMDS="ip"
-TST_CNT=$NS_TIMES
 
 . tst_net.sh
 
+ROUTE_RHOST_PORT=${ROUTE_RHOST_PORT:-65535}
+ROUTE_MAX_IP=${ROUTE_MAX_IP:-5}
+
+IP_ADDR_DELIM=','
+
+add_macvlan()
+{
+	local action="add"
+	local OPTIND
+	while getopts d opt; do
+		case "$opt" in
+		d) action="del";;
+		esac
+	done
+	shift $((OPTIND - 1))
+
+	local iface="$1"
+	local type="${2:-lhost}"
+
+	cmd="ip link $action $iface link $(tst_iface $type) type macvlan mode bridge"
+	if [ $type = "lhost" ]; then
+		ROD $cmd
+		[ "$action" = "add" ] || return
+		LHOST_IFACES="$LHOST_IFACES $iface"
+	else
+		tst_rhost_run -s -c "$cmd"
+		[ "$action" = "add" ] || return
+		RHOST_IFACES="$RHOST_IFACES $iface"
+	fi
+	tst_init_iface $type 1
+}
+
+check_max_ip()
+{
+	local max_ip_limit=254
+	[ "$TST_IPV6" ] && max_ip_limit=65534
+
+	tst_is_int "$ROUTE_MAX_IP" || tst_brk TBROK "\$ROUTE_MAX_IP not int ($ROUTE_MAX_IP)"
+	[ $ROUTE_MAX_IP -gt $max_ip_limit ] && ROUTE_MAX_IP=$max_ip_limit
+	[ $ROUTE_MAX_IP -gt $NS_TIMES ] && ROUTE_MAX_IP=$NS_TIMES
+}
+
+cleanup_if()
+{
+	[ "$new_liface" ] && add_macvlan -d $new_liface
+	[ "$new_riface" ] && add_macvlan -d $new_riface rhost
+	route_cleanup
+}
+
 route_cleanup()
 {
 	tst_restore_ipaddr
 	tst_restore_ipaddr rhost
 }
+
+setup_gw()
+{
+	tst_res TINFO "change IPv$TST_IPVER route gateway $NS_TIMES times"
+
+	rt="$(tst_ipaddr_un -p 0 0)"
+	lhost="$(tst_ipaddr_un 1 1)"
+	rhost="$(tst_ipaddr_un 0 1)"
+	tst_add_ipaddr -s -q -a $lhost
+	tst_add_ipaddr -s -q -a $rhost rhost
+}
+
+setup_if()
+{
+	tst_res TINFO "change IPv$TST_IPVER route interface $NS_TIMES times"
+
+	rt="$(tst_ipaddr_un -p 0)"
+	rhost="$(tst_ipaddr_un 0 1)"
+	tst_add_ipaddr -s -q -a $rhost rhost
+
+	if [ $(tst_get_ifaces_cnt) -lt 2 ]; then
+		new_liface="ltp_mv2"
+		tst_res TINFO "2 or more local ifaces required, adding '$new_liface'"
+		add_macvlan $new_liface
+	fi
+
+	if [ $(tst_get_ifaces_cnt rhost) -lt 2 ]; then
+		new_riface="ltp_mv1"
+		tst_res TINFO "2 or more remote ifaces required, adding '$new_riface'"
+		add_macvlan $new_riface rhost
+	fi
+}
+
+test_netlink()
+{
+	local ret=0
+	local cmd ip_flag
+	[ "$TST_IPV6" ] && ip_flag="-6"
+
+	cmd="route-change-netlink -c $NS_TIMES $ip_flag -p $ROUTE_RHOST_PORT $ROUTE_CHANGE_NETLINK_PARAMS"
+	tst_res TINFO "running $cmd"
+	$cmd || ret=$?
+	if [ "$ret" -ne 0 ]; then
+		[ $((ret & 3)) -ne 0 ] && \
+			tst_brk TFAIL "route-change-netlink failed"
+		[ $((ret & 32)) -ne 0 ] && \
+			tst_brk TCONF "not supported configuration"
+		[ $((ret & 4)) -ne 0 ] && \
+			tst_res TWARN "route-change-netlink has warnings"
+	fi
+	tst_res TPASS "route-change-netlink passed"
+}
diff --git a/travis/debian.cross-compile.sh b/travis/debian.cross-compile.sh
index 67307d1e1..08c3805aa 100755
--- a/travis/debian.cross-compile.sh
+++ b/travis/debian.cross-compile.sh
@@ -20,4 +20,5 @@  apt update
 apt install -y --no-install-recommends \
 	gcc-${gcc_arch}-linux-gnu \
 	libc6-dev-${ARCH}-cross \
+	libmnl-dev:$ARCH \
 	libtirpc-dev:$ARCH
diff --git a/travis/debian.sh b/travis/debian.sh
index 97fa91a0d..b759a9576 100755
--- a/travis/debian.sh
+++ b/travis/debian.sh
@@ -28,6 +28,7 @@  apt install -y --no-install-recommends \
 	libkeyutils-dev \
 	libkeyutils1 \
 	libmm-dev \
+	libmnl-dev \
 	libnuma-dev \
 	libnuma1 \
 	libselinux1-dev \
diff --git a/travis/fedora.sh b/travis/fedora.sh
index d68e7573e..990a84daf 100755
--- a/travis/fedora.sh
+++ b/travis/fedora.sh
@@ -13,3 +13,6 @@  yum -y install \
 	libtirpc-devel \
 	pkg-config \
 	redhat-lsb-core
+
+# CentOS 8 doesn't have libmnl-devel
+yum -y install libmnl-devel || yum -y install libmnl
diff --git a/travis/tumbleweed.sh b/travis/tumbleweed.sh
index 4dd71fd13..8143fc885 100755
--- a/travis/tumbleweed.sh
+++ b/travis/tumbleweed.sh
@@ -15,6 +15,7 @@  zypper --non-interactive install --no-recommends \
 	libacl-devel \
 	libaio-devel \
 	libcap-devel \
+	libmnl-devel \
 	libnuma-devel \
 	libopenssl-devel \
 	libselinux-devel \