[RFC,3/3] if_proxy: add example, test and documentation
diff mbox series

Message ID 20200114142517.29522-4-aostruszka@marvell.com
State Superseded, archived
Delegated to: Thomas Monjalon
Headers show
Series
  • introduce IF proxy library
Related show

Checks

Context Check Description
ci/Intel-compilation success Compilation OK
ci/checkpatch success coding style OK

Commit Message

Andrzej Ostruszka [C] Jan. 14, 2020, 2:25 p.m. UTC
This commit adds a test, documentation and a small example.

The example just creates one proxy port and binds all ports available to
it.  Then you can play around with changing of network configuration of
this proxy port and you should observe notifications from the
appropriate callbacks.  Below is an exemplary output (with some parts
elided and some comments added) - 'dtap0' is the name of the proxy
interface.

sudo ./if_proxy -w 00:03.0 -w 00:04.0
...
Press ^C to quit
        route add -> 10.0.0.0/16
        route add -> 192.168.123.0/24
...
        route6 add -> ::1/128
        route6 add -> fe80::/64
        route6 add -> fe80::ee05:deaf:6827:b435/128
...
[[ output on: ip link set dtap0 mtu 1600 ]]
        mtu change for port 0 -> 1600
        mtu change for port 1 -> 1600
[[ output on: ip link set dtap0 up ]]
        port 0 going up
        port 1 going up
        route6 add -> ff00::/8
        route6 add -> fe80::/64
        address6 add for port 0 -> fe80::2436:17ff:fefd:94ed
        address6 add for port 1 -> fe80::2436:17ff:fefd:94ed
        route6 add -> fe80::2436:17ff:fefd:94ed/128

Signed-off-by: Andrzej Ostruszka <aostruszka@marvell.com>
---
 app/test/Makefile                      |   5 +
 app/test/meson.build                   |   1 +
 app/test/test_if_proxy.c               | 431 +++++++++++++++++++++++++
 doc/guides/prog_guide/if_proxy_lib.rst | 103 ++++++
 doc/guides/prog_guide/index.rst        |   1 +
 examples/Makefile                      |   1 +
 examples/if_proxy/Makefile             |  58 ++++
 examples/if_proxy/main.c               | 203 ++++++++++++
 examples/if_proxy/meson.build          |  12 +
 examples/meson.build                   |   2 +-
 10 files changed, 816 insertions(+), 1 deletion(-)
 create mode 100644 app/test/test_if_proxy.c
 create mode 100644 doc/guides/prog_guide/if_proxy_lib.rst
 create mode 100644 examples/if_proxy/Makefile
 create mode 100644 examples/if_proxy/main.c
 create mode 100644 examples/if_proxy/meson.build

Patch
diff mbox series

diff --git a/app/test/Makefile b/app/test/Makefile
index 57930c00b..f621978d7 100644
--- a/app/test/Makefile
+++ b/app/test/Makefile
@@ -230,6 +230,11 @@  SRCS-$(CONFIG_RTE_LIBRTE_BPF) += test_bpf.c
 
 SRCS-$(CONFIG_RTE_LIBRTE_RCU) += test_rcu_qsbr.c test_rcu_qsbr_perf.c
 
+ifeq ($(CONFIG_RTE_LIBRTE_IF_PROXY),y)
+SRCS-y += test_if_proxy.c
+LDLIBS += -lrte_if_proxy
+endif
+
 SRCS-$(CONFIG_RTE_LIBRTE_IPSEC) += test_ipsec.c
 SRCS-$(CONFIG_RTE_LIBRTE_IPSEC) += test_ipsec_sad.c
 ifeq ($(CONFIG_RTE_LIBRTE_IPSEC),y)
diff --git a/app/test/meson.build b/app/test/meson.build
index fb49d804b..2a3b5fef2 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -61,6 +61,7 @@  test_sources = files('commands.c',
 	'test_hash_perf.c',
 	'test_hash_readwrite_lf.c',
 	'test_interrupts.c',
+	'test_if_proxy.c',
 	'test_ipsec.c',
 	'test_ipsec_sad.c',
 	'test_kni.c',
diff --git a/app/test/test_if_proxy.c b/app/test/test_if_proxy.c
new file mode 100644
index 000000000..0ecfb79b4
--- /dev/null
+++ b/app/test/test_if_proxy.c
@@ -0,0 +1,431 @@ 
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2020 Marvell International Ltd.
+ */
+
+#include "test.h"
+
+#include <rte_ethdev.h>
+#include <rte_if_proxy.h>
+
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+#include <net/if.h>
+#include <arpa/inet.h>
+#include <pthread.h>
+#include <time.h>
+
+static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
+
+enum net_op {
+	INITIALIZED	= 1U << 0,
+	LOOP_ROUTE	= 1U << 1,
+	LOOP6_ROUTE	= 1U << 2,
+	LINK_CHANGED	= 1U << 3,
+	MAC_CHANGED	= 1U << 4,
+	MTU_CHANGED	= 1U << 5,
+	ADDR_ADD	= 1U << 6,
+	ADDR_DEL	= 1U << 7,
+	ROUTE_ADD	= 1U << 8,
+	ROUTE_DEL	= 1U << 9,
+	ADDR6_ADD	= 1U << 10,
+	ADDR6_DEL	= 1U << 11,
+	ROUTE6_ADD	= 1U << 12,
+	ROUTE6_DEL	= 1U << 13,
+};
+
+static unsigned int state;
+
+static struct {
+	struct rte_ether_addr mac_addr;
+	uint16_t port_id, mtu;
+	struct in_addr ipv4, route4;
+	struct in6_addr ipv6, route6;
+	uint16_t depth4, depth6;
+	int is_up;
+} net_cfg;
+
+static
+int unlock_notify(unsigned int op)
+{
+	/* the mutex is expected to be locked on entry */
+	RTE_VERIFY(pthread_mutex_trylock(&mutex) == EBUSY);
+	state |= op;
+
+	pthread_mutex_unlock(&mutex);
+	return pthread_cond_signal(&cond);
+}
+
+static
+int wait_for(unsigned int op_mask, unsigned int sec)
+{
+	struct timespec time;
+	int ec = pthread_mutex_trylock(&mutex);
+
+	/* the mutex is expected to be locked on entry */
+	RTE_VERIFY(ec == EBUSY);
+
+	ec = 0;
+	clock_gettime(CLOCK_REALTIME, &time);
+	time.tv_sec += sec;
+
+	while ((state & op_mask) != op_mask && ec == 0)
+		ec = pthread_cond_timedwait(&cond, &mutex, &time);
+
+	return ec;
+}
+
+static
+int expect(unsigned int op_mask, const char *fmt, ...)
+#if __GNUC__
+	__attribute__((format(printf, 2, 3)));
+#endif
+
+static
+int expect(unsigned int op_mask, const char *fmt, ...)
+{
+	char cmd[128];
+	va_list args;
+	int ret;
+
+	state &= ~op_mask;
+	va_start(args, fmt);
+	vsnprintf(cmd, sizeof(cmd), fmt, args);
+	va_end(args);
+	ret = system(cmd);
+	if (ret == 0)
+		/* IPv6 address notifications seem to need that long delay. */
+		return wait_for(op_mask, 2);
+	return ret;
+}
+
+static
+void mac_change(uint16_t port_id, const struct rte_ether_addr *mac)
+{
+	pthread_mutex_lock(&mutex);
+	RTE_VERIFY(port_id == net_cfg.port_id);
+	if (memcmp(mac->addr_bytes, net_cfg.mac_addr.addr_bytes,
+		   RTE_ETHER_ADDR_LEN) == 0) {
+		unlock_notify(MAC_CHANGED);
+		return;
+	}
+	pthread_mutex_unlock(&mutex);
+}
+
+static
+void mtu_change(uint16_t port_id, uint16_t mtu)
+{
+	pthread_mutex_lock(&mutex);
+	RTE_VERIFY(port_id == net_cfg.port_id);
+	if (net_cfg.mtu == mtu) {
+		unlock_notify(MTU_CHANGED);
+		return;
+	}
+	pthread_mutex_unlock(&mutex);
+}
+
+static
+void link_change(uint16_t port_id, int is_up)
+{
+	pthread_mutex_lock(&mutex);
+	RTE_VERIFY(port_id == net_cfg.port_id);
+	if (net_cfg.is_up == is_up) {
+		unlock_notify(LINK_CHANGED);
+		return;
+	}
+	pthread_mutex_unlock(&mutex);
+}
+
+static
+void addr_add(uint16_t port_id, uint32_t ip)
+{
+	pthread_mutex_lock(&mutex);
+	RTE_VERIFY(port_id == net_cfg.port_id);
+	if (net_cfg.ipv4.s_addr == ip) {
+		unlock_notify(ADDR_ADD);
+		return;
+	}
+	pthread_mutex_unlock(&mutex);
+}
+
+static
+void addr_del(uint16_t port_id, uint32_t ip)
+{
+	pthread_mutex_lock(&mutex);
+	RTE_VERIFY(port_id == net_cfg.port_id);
+	if (net_cfg.ipv4.s_addr == ip) {
+		unlock_notify(ADDR_DEL);
+		return;
+	}
+	pthread_mutex_unlock(&mutex);
+}
+
+static
+void addr6_add(uint16_t port_id, const uint8_t *ip)
+{
+	pthread_mutex_lock(&mutex);
+	RTE_VERIFY(port_id == net_cfg.port_id);
+	if (memcmp(ip, net_cfg.ipv6.s6_addr, 16) == 0) {
+		unlock_notify(ADDR6_ADD);
+		return;
+	}
+	pthread_mutex_unlock(&mutex);
+}
+
+static
+void addr6_del(uint16_t port_id __rte_unused, const uint8_t *ip)
+{
+	pthread_mutex_lock(&mutex);
+	RTE_VERIFY(port_id == net_cfg.port_id);
+	if (memcmp(ip, net_cfg.ipv6.s6_addr, 16) == 0) {
+		unlock_notify(ADDR6_DEL);
+		return;
+	}
+	pthread_mutex_unlock(&mutex);
+}
+
+static
+void route_add(uint32_t ip, uint8_t depth)
+{
+	pthread_mutex_lock(&mutex);
+	/* Since we are checking if during initialization we get some routing
+	 * info we need to notify either when we are not initialized or when
+	 * the exact route matches.
+	 */
+	if (!(state & INITIALIZED) ||
+	    (net_cfg.depth4 == depth && net_cfg.route4.s_addr == ip)) {
+		unlock_notify(ROUTE_ADD);
+		return;
+	}
+	pthread_mutex_unlock(&mutex);
+}
+
+static
+void route_del(uint32_t ip, uint8_t depth)
+{
+	pthread_mutex_lock(&mutex);
+	if (net_cfg.depth4 == depth && net_cfg.route4.s_addr == ip) {
+		unlock_notify(ROUTE_DEL);
+		return;
+	}
+	pthread_mutex_unlock(&mutex);
+}
+
+static
+void route6_add(const uint8_t *ip, uint8_t depth)
+{
+	pthread_mutex_lock(&mutex);
+	/* Since we are checking if during initialization we get some routing
+	 * info we need to notify either when we are not initialized or when
+	 * the exact route matches.
+	 */
+	if (!(state & INITIALIZED) ||
+	    (net_cfg.depth6 == depth &&
+	     /* don't check for trailing zeros */
+	     memcmp(ip, net_cfg.route6.s6_addr, depth/8) == 0)) {
+		unlock_notify(ROUTE6_ADD);
+		return;
+	}
+	pthread_mutex_unlock(&mutex);
+}
+
+static
+void route6_del(const uint8_t *ip, uint8_t depth)
+{
+	pthread_mutex_lock(&mutex);
+	if (net_cfg.depth6 == depth &&
+	    /* don't check for trailing zeros */
+	    memcmp(ip, net_cfg.route6.s6_addr, depth/8) == 0) {
+		unlock_notify(ROUTE6_DEL);
+		return;
+	}
+	pthread_mutex_unlock(&mutex);
+}
+
+static
+void cfg_finished(void)
+{
+	pthread_mutex_lock(&mutex);
+	unlock_notify(INITIALIZED);
+}
+
+static
+struct rte_ifpx_callbacks cbs = {
+	.mac_change = mac_change,
+	.mtu_change = mtu_change,
+	.link_change = link_change,
+	.addr_add = addr_add,
+	.addr_del = addr_del,
+	.addr6_add = addr6_add,
+	.addr6_del = addr6_del,
+	.route_add = route_add,
+	.route_del = route_del,
+	.route6_add = route6_add,
+	.route6_del = route6_del,
+	/* lib specific callback */
+	.cfg_finished = cfg_finished,
+};
+
+static int
+test_if_proxy(void)
+{
+	int ec;
+	char buf[INET6_ADDRSTRLEN];
+	const struct rte_ifpx_info *pinfo;
+
+	state = 0;
+	memset(&net_cfg, 0, sizeof(net_cfg));
+	/* Since we are not going to test RX/TX we can just create proxy and
+	 * bind it to itself to test just notification functionality.
+	 */
+	net_cfg.port_id = rte_ifpx_create(RTE_IFPX_DEFAULT);
+	RTE_VERIFY(net_cfg.port_id != RTE_MAX_ETHPORTS);
+	rte_ifpx_port_bind(net_cfg.port_id, net_cfg.port_id);
+	rte_ifpx_callbacks_register(&cbs);
+	rte_ifpx_listen();
+
+	pthread_mutex_lock(&mutex);
+	/* During initialization we should observe IPv4/6 loopback routes. */
+	net_cfg.route4.s_addr = RTE_IPV4(127, 0, 0, 1);
+	net_cfg.depth4 = 32;
+	memcpy(net_cfg.route6.s6_addr, in6addr_loopback.s6_addr, 16);
+	net_cfg.depth6 = 128;
+	ec = wait_for(INITIALIZED | ROUTE_ADD | ROUTE6_ADD, 2);
+	if (ec != 0) {
+		printf("Failed to obtain network configuration\n");
+		goto exit;
+	}
+	pinfo = rte_ifpx_info_get(net_cfg.port_id);
+	RTE_VERIFY(pinfo);
+
+	/* Make sure the link is down. */
+	net_cfg.is_up = 0;
+	ec = expect(LINK_CHANGED, "ip link set dev %s down", pinfo->if_name);
+	RTE_VERIFY(ec == ETIMEDOUT || ec == 0);
+
+	/* Test link up notification. */
+	net_cfg.is_up = 1;
+	ec = expect(LINK_CHANGED, "ip link set dev %s up", pinfo->if_name);
+	if (ec != 0) {
+		printf("Failed to notify about link going up\n");
+		goto exit;
+	}
+
+	/* Test for MAC changes notification. */
+	rte_eth_random_addr(net_cfg.mac_addr.addr_bytes);
+	rte_ether_format_addr(buf, sizeof(buf), &net_cfg.mac_addr);
+	ec = expect(MAC_CHANGED, "ip link set dev %s address %s",
+		    pinfo->if_name, buf);
+	if (ec != 0) {
+		printf("Missing/wrong notification about mac change\n");
+		goto exit;
+	}
+
+	/* Test for MTU changes notification. */
+	net_cfg.mtu = pinfo->mtu + 100;
+	ec = expect(MTU_CHANGED, "ip link set dev %s mtu %d",
+		    pinfo->if_name, net_cfg.mtu);
+	if (ec != 0) {
+		printf("Missing/wrong notification about mtu change\n");
+		goto exit;
+	}
+
+	/* Test for adding of IPv4 address - using address from TEST-2 pool.
+	 * This test is specific to linux netlink behaviour - after adding
+	 * address we get both notification about address being added and new
+	 * route.  So I check both.
+	 */
+	net_cfg.ipv4.s_addr = RTE_IPV4(198, 51, 100, 14);
+	net_cfg.route4.s_addr = net_cfg.ipv4.s_addr;
+	net_cfg.depth4 = 32;
+	ec = expect(ADDR_ADD | ROUTE_ADD, "ip addr add 198.51.100.14 dev %s",
+		    pinfo->if_name);
+	if (ec != 0) {
+		printf("Missing/wrong notifications about IPv4 address add\n");
+		goto exit;
+	}
+
+	/* Test for IPv4 address removal.  See comment above for 'addr add'. */
+	ec = expect(ADDR_DEL | ROUTE_DEL, "ip addr del 198.51.100.14/32 dev %s",
+		    pinfo->if_name);
+	if (ec != 0) {
+		printf("Missing/wrong notifications about IPv4 address del\n");
+		goto exit;
+	}
+
+	/* Test for adding IPv4 route. */
+	net_cfg.route4.s_addr = RTE_IPV4(198, 51, 100, 0);
+	net_cfg.depth4 = 24;
+	ec = expect(ROUTE_ADD, "ip route add 198.51.100.0/24 dev %s",
+		    pinfo->if_name);
+	if (ec != 0) {
+		printf("Missing/wrong notifications about IPv4 route add\n");
+		goto exit;
+	}
+
+	/* Test for IPv4 route removal. */
+	ec = expect(ROUTE_DEL, "ip route del 198.51.100.0/24 dev %s",
+		    pinfo->if_name);
+	if (ec != 0) {
+		printf("Missing/wrong notifications about IPv4 route del\n");
+		goto exit;
+	}
+
+	/* Now the same for IPv6 - with address from "documentation pool". */
+	inet_pton(AF_INET6, "2001:db8::dead:beef", net_cfg.ipv6.s6_addr);
+	/* This is specific to linux netlink behaviour - after adding address
+	 * we get both notification about address being added and new route.
+	 * So I wait for both.
+	 */
+	memcpy(net_cfg.route6.s6_addr, net_cfg.ipv6.s6_addr, 16);
+	net_cfg.depth6 = 128;
+	ec = expect(ADDR6_ADD | ROUTE6_ADD,
+		    "ip addr add 2001:db8::dead:beef dev %s",
+		    pinfo->if_name);
+	if (ec != 0) {
+		printf("Missing/wrong notifications about IPv6 address add\n");
+		goto exit;
+	}
+
+	/* See comment above for 'addr6 add'. */
+	ec = expect(ADDR6_DEL | ROUTE6_DEL,
+		    "ip addr del 2001:db8::dead:beef/128 dev %s",
+		    pinfo->if_name);
+	if (ec != 0) {
+		printf("Missing/wrong notifications about IPv6 address del\n");
+		goto exit;
+	}
+
+	net_cfg.depth6 = 96;
+	ec = expect(ROUTE6_ADD, "ip route add 2001:db8::dead:0/96 dev %s",
+		    pinfo->if_name);
+	if (ec != 0) {
+		printf("Missing/wrong notifications about IPv6 route add\n");
+		goto exit;
+	}
+
+	ec = expect(ROUTE6_DEL, "ip route del 2001:db8::dead:0/96 dev %s",
+		    pinfo->if_name);
+	if (ec != 0) {
+		printf("Missing/wrong notifications about IPv6 route del\n");
+		goto exit;
+	}
+
+	/* Finally put link down and test for notification. */
+	net_cfg.is_up = 0;
+	ec = expect(LINK_CHANGED, "ip link set dev %s down", pinfo->if_name);
+	if (ec != 0) {
+		printf("Failed to notify about link going down\n");
+		goto exit;
+	}
+
+exit:
+	pthread_mutex_unlock(&mutex);
+	rte_ifpx_destroy(net_cfg.port_id);
+	rte_ifpx_close();
+
+	return ec;
+}
+
+REGISTER_TEST_COMMAND(if_proxy_autotest, test_if_proxy)
diff --git a/doc/guides/prog_guide/if_proxy_lib.rst b/doc/guides/prog_guide/if_proxy_lib.rst
new file mode 100644
index 000000000..dc1202cdf
--- /dev/null
+++ b/doc/guides/prog_guide/if_proxy_lib.rst
@@ -0,0 +1,103 @@ 
+..  SPDX-License-Identifier: BSD-3-Clause
+    Copyright(C) 2019 Marvell International Ltd.
+
+.. _IF_Proxy_Library:
+
+IF Proxy Library
+================
+
+When a network interface is assigned to DPDK it usually disappears from
+the system.
+This way user looses ability to configure it via typical configuration
+tools and is left basically with two options:
+
+  - configure it via command line arguments,
+
+  - add support for live configuration via some IPC mechanism.
+
+The first option is static and the second one requires some work to add
+communication loop (e.g. separate thread listening/communicating on
+a socket).
+
+This library adds a possibility to configure DPDK ports by using normal
+configuration utilities (e.g. from iproute2 suite).
+It requires user to configure additional DPDK ports that are visible to
+the system (such as Tap or KNI - actually any port that has valid
+'if_index' in 'struct rte_eth_dev_info' will do) and designate them as
+a port representor (a proxy) in the system.
+
+Let's see typical intended usage by an example.
+Suppose that you have application that handles traffic on two ports (in
+the white list below).
+
+    ./app -w 00:14.0 -w 00:16.0 --vdev=net_tap0 --vdev=net_tap1
+
+So in addition you configure two proxy ports and in the application code
+you bind them to the "main" ports:
+
+    rte_if_proxy_port_bind(port0, proxy0);
+    rte_if_proxy_port_bind(port1, proxy1);
+
+This binding is a logical one - there is no automatic packet forwarding
+configured.
+This is because library cannot tell upfront what portion of the traffic
+received on ports 0/1 should be redirected to the system via proxies and
+also it does not know how the application is structured (what packet
+processing engines it uses).
+Therefore it is application writer responsibility to include proxy ports
+into its packet processing and forward appropriate packets between
+proxies and ports.
+What the library actually does is that it gets network configuration
+from the system and listens to its changes.
+This information is then matched against 'if_index' of the configured
+proxies (when applicable - routing information is global) and passed to
+the application via set of callbacks that user has to register:
+
+    rte_if_proxy_callbacks_register(&cbs);
+
+Here 'cbs' is a 'struct rte_if_proxy_callbacks' which has following
+members:
+
+    void  (*mac_change)(uint16_t port_id, const struct rte_ether_addr *mac);
+    void  (*mtu_change)(uint16_t port_id, uint16_t mtu);
+    void (*link_change)(uint16_t port_id, int is_up);
+    /* IPv4 addresses are in host order */
+    void    (*addr_add)(uint16_t port_id, uint32_t ip);
+    void    (*addr_del)(uint16_t port_id, uint32_t ip);
+    void   (*addr6_add)(uint16_t port_id, const uint8_t *ip);
+    void   (*addr6_del)(uint16_t port_id, const uint8_t *ip);
+    void   (*route_add)(uint32_t ip, uint8_t depth);
+    void   (*route_del)(uint32_t ip, uint8_t depth);
+    void  (*route6_add)(const uint8_t *ip, uint8_t depth);
+    void  (*route6_del)(const uint8_t *ip, uint8_t depth);
+    /* lib specific callback - called when initial network configuration
+     * query is finished */
+    void (*cfg_finished)(void);
+
+So for example when the user issues command:
+
+    ip link set dev dtap0 mtu 1600
+
+then library will call `mtu_change()` callback with port_id equal to
+'port0' (id of the port bound to this proxy) and 'mtu' equal to 1600
+('dtap0' is the default interface name for 'net_tap0').
+Application can simply use `rte_eth_dev_set_mtu()` as this callback.
+The same way `rte_eth_dev_default_mac_addr_set()` can be used for
+`mac_change()` and `rte_eth_dev_set_link_up/down()` can be used inside
+the callback that does dispatch based on 'is_up' argument.
+
+Please note however that the context in which these callbacks are called
+is most probably different from the one in which packets are handled and
+it is application writer responsibility to use proper synchronization
+mechanisms - if they are needed.
+
+If the application supports IP protocol stack then it can utilize
+callbacks for adding/removing of addresses to the proxies and also
+routing information (note that routing info is not associated with any
+port).
+E.g. application can feed some LPM tables with these addresses and upon
+reception of a packet on some port match this packet against those
+tables to figure out what to do with this packet.
+If the decision is to pass it to the system then it can simply forward
+them to the proxy corresponding to the port on which packet has been
+received by using standard PMD TX interface.
diff --git a/doc/guides/prog_guide/index.rst b/doc/guides/prog_guide/index.rst
index dc4851c57..0a1541f34 100644
--- a/doc/guides/prog_guide/index.rst
+++ b/doc/guides/prog_guide/index.rst
@@ -57,6 +57,7 @@  Programmer's Guide
     metrics_lib
     bpf_lib
     ipsec_lib
+    if_proxy_lib
     source_org
     dev_kit_build_system
     dev_kit_root_make_help
diff --git a/examples/Makefile b/examples/Makefile
index feff79784..5aa9ab431 100644
--- a/examples/Makefile
+++ b/examples/Makefile
@@ -81,6 +81,7 @@  else
 $(info vm_power_manager requires libvirt >= 0.9.3)
 endif
 endif
+DIRS-$(CONFIG_RTE_LIBRTE_IF_PROXY) += if_proxy
 
 DIRS-y += eventdev_pipeline
 
diff --git a/examples/if_proxy/Makefile b/examples/if_proxy/Makefile
new file mode 100644
index 000000000..dd0515fa4
--- /dev/null
+++ b/examples/if_proxy/Makefile
@@ -0,0 +1,58 @@ 
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2019 Marvell International Ltd.
+
+# binary name
+APP = if_proxy
+
+# all source are stored in SRCS-y
+SRCS-y := main.c
+
+# Build using pkg-config variables if possible
+ifeq ($(shell pkg-config --exists libdpdk && echo 0),0)
+
+all: shared
+.PHONY: shared static
+shared: build/$(APP)-shared
+	ln -sf $(APP)-shared build/$(APP)
+static: build/$(APP)-static
+	ln -sf $(APP)-static build/$(APP)
+
+PKGCONF=pkg-config --define-prefix
+
+PC_FILE := $(shell $(PKGCONF) --path libdpdk)
+CFLAGS += -O3 $(shell $(PKGCONF) --cflags libdpdk)
+LDFLAGS_SHARED = $(shell $(PKGCONF) --libs libdpdk)
+LDFLAGS_STATIC = -Wl,-Bstatic $(shell $(PKGCONF) --static --libs libdpdk)
+
+build/$(APP)-shared: $(SRCS-y) Makefile $(PC_FILE) | build
+	$(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_SHARED)
+
+build/$(APP)-static: $(SRCS-y) Makefile $(PC_FILE) | build
+	$(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_STATIC)
+
+build:
+	@mkdir -p $@
+
+.PHONY: clean
+clean:
+	rm -f build/$(APP) build/$(APP)-static build/$(APP)-shared
+	test -d build && rmdir -p build || true
+
+else # Build using legacy build system
+
+ifeq ($(RTE_SDK),)
+$(error "Please define RTE_SDK environment variable")
+endif
+
+# Default target, detect a build directory, by looking for a path with a .config
+RTE_TARGET ?= $(notdir $(abspath $(dir $(firstword $(wildcard $(RTE_SDK)/*/.config)))))
+
+include $(RTE_SDK)/mk/rte.vars.mk
+
+CFLAGS += -O3
+CFLAGS += -DALLOW_EXPERIMENTAL_API
+CFLAGS += $(WERROR_FLAGS)
+LDLIBS += -lrte_if_proxy -lrte_ethdev -lrte_eal
+
+include $(RTE_SDK)/mk/rte.extapp.mk
+endif
diff --git a/examples/if_proxy/main.c b/examples/if_proxy/main.c
new file mode 100644
index 000000000..2195fb490
--- /dev/null
+++ b/examples/if_proxy/main.c
@@ -0,0 +1,203 @@ 
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2020 Marvell International Ltd.
+ */
+
+#include <rte_if_proxy.h>
+
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+#include <arpa/inet.h>
+
+static
+char buf[INET6_ADDRSTRLEN];
+
+static
+uint16_t proxy_id = RTE_MAX_ETHPORTS;
+
+static
+void mac_change(uint16_t port_id, const struct rte_ether_addr *mac)
+{
+	char buf[3*RTE_ETHER_ADDR_LEN];
+
+	rte_ether_format_addr(buf, sizeof(buf), mac);
+	printf("\tmac change for port %u -> %s\n", port_id, buf);
+}
+
+static
+void mtu_change(uint16_t port_id, uint16_t mtu)
+{
+	printf("\tmtu change for port %u -> %u\n", port_id, mtu);
+}
+
+static
+void link_change(uint16_t port_id, int is_up)
+{
+	printf("\tport %u going %s\n", port_id, is_up ? "up" : "down");
+}
+
+static
+void addr_add(uint16_t port_id, uint32_t ip)
+{
+	struct in_addr a = { .s_addr = htonl(ip) };
+
+	printf("\taddress add for port %u -> %s\n", port_id,
+	       inet_ntop(AF_INET, &a, buf, sizeof(buf)));
+}
+
+static
+void addr_del(uint16_t port_id, uint32_t ip)
+{
+	struct in_addr a = { .s_addr = htonl(ip) };
+
+	printf("\taddress del for port %u -> %s\n", port_id,
+	       inet_ntop(AF_INET, &a, buf, sizeof(buf)));
+}
+
+static
+void addr6_add(uint16_t port_id, const uint8_t *ip)
+{
+	struct in6_addr a;
+
+	memcpy(a.s6_addr, ip, 16);
+	printf("\taddress6 add for port %u -> %s\n", port_id,
+	       inet_ntop(AF_INET6, &a, buf, sizeof(buf)));
+}
+
+static
+void addr6_del(uint16_t port_id, const uint8_t *ip)
+{
+	struct in6_addr a;
+
+	memcpy(a.s6_addr, ip, 16);
+	printf("\taddress6 del for port %u -> %s\n", port_id,
+	       inet_ntop(AF_INET6, &a, buf, sizeof(buf)));
+}
+
+static
+void route_add(uint32_t ip, uint8_t depth)
+{
+	struct in_addr a = { .s_addr = htonl(ip) };
+
+	printf("\troute add -> %s/%u\n",
+	       inet_ntop(AF_INET, &a, buf, sizeof(buf)), depth);
+}
+
+static
+void route_del(uint32_t ip, uint8_t depth)
+{
+	struct in_addr a = { .s_addr = htonl(ip) };
+
+	printf("\troute del -> %s/%u\n",
+	       inet_ntop(AF_INET, &a, buf, sizeof(buf)), depth);
+}
+
+static
+void route6_add(const uint8_t *ip, uint8_t depth)
+{
+	struct in6_addr a;
+
+	memcpy(a.s6_addr, ip, 16);
+	printf("\troute6 add -> %s/%u\n",
+	       inet_ntop(AF_INET6, &a, buf, sizeof(buf)), depth);
+}
+
+static
+void route6_del(const uint8_t *ip, uint8_t depth)
+{
+	struct in6_addr a;
+
+	memcpy(a.s6_addr, ip, 16);
+	printf("\troute6 del -> %s/%u\n",
+	       inet_ntop(AF_INET6, &a, buf, sizeof(buf)), depth);
+}
+
+struct rte_ifpx_callbacks cbs = {
+	.mac_change = mac_change,
+	.mtu_change = mtu_change,
+	.link_change = link_change,
+	.addr_add = addr_add,
+	.addr_del = addr_del,
+	.addr6_add = addr6_add,
+	.addr6_del = addr6_del,
+	.route_add = route_add,
+	.route_del = route_del,
+	.route6_add = route6_add,
+	.route6_del = route6_del,
+};
+
+static
+void proxy_bind_change(int sig)
+{
+	uint16_t port;
+	if (sig == SIGUSR1)
+		port = 0;
+	else if (sig == SIGUSR2)
+		port = 1;
+	else
+		return;
+
+	if (port >= rte_eth_dev_count_avail()) {
+		printf("\tNot enough ports allocated!\n");
+		return;
+	}
+
+	if (rte_ifpx_proxy_get(port) == RTE_MAX_ETHPORTS) {
+		printf("\tbinding port %d to proxy\n", port);
+		rte_ifpx_port_bind(port, proxy_id);
+	} else {
+		printf("\tunbinding port %d\n", port);
+		rte_ifpx_port_unbind(port);
+	}
+}
+
+int
+main(int argc, char **argv)
+{
+	int i, sig, nb_ports;
+	sigset_t set;
+
+	/* init EAL */
+	i = rte_eal_init(argc, argv);
+	if (i < 0)
+		rte_exit(EXIT_FAILURE, "Invalid EAL arguments\n");
+	argc -= i;
+	argv += i;
+
+	nb_ports = rte_eth_dev_count_avail();
+	if (nb_ports == 0)
+		rte_exit(EXIT_FAILURE, "No Ethernet ports - bye\n");
+
+	proxy_id = rte_ifpx_create(RTE_IFPX_DEFAULT);
+	if (proxy_id >= RTE_MAX_ETHPORTS) {
+		printf("Failed to create default proxy\n");
+		return -1;
+	}
+	/* Bind all ports to the same proxy. */
+	for (i = 0; i < nb_ports; ++i)
+		rte_ifpx_port_bind(i, proxy_id);
+	rte_ifpx_callbacks_register(&cbs);
+	rte_ifpx_listen();
+
+	/* Since we do not process packets - only listen to net events - we only
+	 * wait for signal either to quit or to change proxy binding.
+	 */
+	signal(SIGUSR1, proxy_bind_change);
+	signal(SIGUSR2, proxy_bind_change);
+
+	sigemptyset(&set);
+	sigaddset(&set, SIGINT);
+	sigprocmask(SIG_BLOCK, &set, NULL);
+	printf("Press ^C to quit\n");
+	do {
+		i = sigwait(&set, &sig);
+	} while (i != 0 && sig != SIGINT);
+
+	RTE_ETH_FOREACH_DEV(i) {
+		printf("\nClosing port %d...\n", i);
+		rte_eth_dev_close(i);
+	}
+	printf("Bye\n");
+
+	return 0;
+}
diff --git a/examples/if_proxy/meson.build b/examples/if_proxy/meson.build
new file mode 100644
index 000000000..5f5826a90
--- /dev/null
+++ b/examples/if_proxy/meson.build
@@ -0,0 +1,12 @@ 
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2019 Marvell International Ltd.
+
+# meson file, for building this example as part of a main DPDK build.
+#
+# To build this example as a standalone application with an already-installed
+# DPDK instance, use 'make'
+
+allow_experimental_apis = true
+sources = files(
+	'main.c'
+)
diff --git a/examples/meson.build b/examples/meson.build
index 1f2b6f516..468ef8a90 100644
--- a/examples/meson.build
+++ b/examples/meson.build
@@ -16,7 +16,7 @@  all_examples = [
 	'eventdev_pipeline',
 	'fips_validation', 'flow_classify',
 	'flow_filtering', 'helloworld',
-	'ioat',
+	'if_proxy', 'ioat',
 	'ip_fragmentation', 'ip_pipeline',
 	'ip_reassembly', 'ipsec-secgw',
 	'ipv4_multicast', 'kni',