diff --git a/Knot.files b/Knot.files
index 30a8344f15f5aaddbf2e62c23972b5caa41f104d..70c435eb2123f2eeae149fb6edd16c9c61931f3a 100644
--- a/Knot.files
+++ b/Knot.files
@@ -73,6 +73,8 @@ src/contrib/openbsd/strlcat.c
 src/contrib/openbsd/strlcat.h
 src/contrib/openbsd/strlcpy.c
 src/contrib/openbsd/strlcpy.h
+src/contrib/popenve.c
+src/contrib/popenve.h
 src/contrib/qp-trie/trie.c
 src/contrib/qp-trie/trie.h
 src/contrib/semaphore.c
diff --git a/src/contrib/Makefile.inc b/src/contrib/Makefile.inc
index 7addce2c5a92a6221ab6f5d7cd4f8d77b745d535..5b0079eca9c0dd4e29148fb020d3fa75dd14541f 100644
--- a/src/contrib/Makefile.inc
+++ b/src/contrib/Makefile.inc
@@ -43,6 +43,8 @@ libcontrib_la_SOURCES = \
 	contrib/mempattern.h			\
 	contrib/net.c				\
 	contrib/net.h				\
+	contrib/popenve.c			\
+	contrib/popenve.h			\
 	contrib/qp-trie/trie.c			\
 	contrib/qp-trie/trie.h			\
 	contrib/semaphore.c			\
diff --git a/src/contrib/popenve.c b/src/contrib/popenve.c
new file mode 100644
index 0000000000000000000000000000000000000000..366e049c3c14a37ccb5e992b1c44dc1c9decc5c2
--- /dev/null
+++ b/src/contrib/popenve.c
@@ -0,0 +1,83 @@
+/*  Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "popenve.h"
+
+int kpopenve(const char *binfile, char *const args[], char *const env[])
+{
+        int pipefds[2];
+        if (pipe(pipefds) < 0) {
+                return -errno;
+        }
+        if (fcntl(pipefds[0], F_SETFD, FD_CLOEXEC) < 0) {
+                int fcntlerrno = errno;
+                close(pipefds[0]);
+                close(pipefds[1]);
+                return -fcntlerrno;
+        }
+
+        pid_t forkpid = fork();
+        if (forkpid < 0) {
+                int forkerrno = errno;
+                close(pipefds[0]);
+                close(pipefds[1]);
+                return -forkerrno;
+        }
+
+        if (forkpid == 0) {
+dup_stdout:
+		if (dup2(pipefds[1], STDOUT_FILENO) < 0) {
+			if (errno == EINTR) {
+				goto dup_stdout;
+			}
+			perror("dup_stdout");
+			close(pipefds[0]);
+			close(pipefds[1]);
+			exit(98);
+		}
+                close(pipefds[1]);
+
+                execve(binfile, args, env);
+                perror("execve");
+                exit(99);
+        }
+
+        close(pipefds[1]);
+        return pipefds[0];
+}
+
+FILE *kpopenve2(const char *binfile, char *const args[], char *const env[])
+{
+	int p = kpopenve(binfile, args, env);
+	if (p < 0) {
+		errno = -p;
+		return NULL;
+	}
+
+	FILE *res = fdopen(p, "r");
+	if (res == NULL) {
+		int fdoerrno = errno;
+		close(p);
+		errno = fdoerrno;
+	}
+	return res;
+}
diff --git a/src/contrib/popenve.h b/src/contrib/popenve.h
new file mode 100644
index 0000000000000000000000000000000000000000..563c9db019b911ff51631b192a2bd8481c30ea4a
--- /dev/null
+++ b/src/contrib/popenve.h
@@ -0,0 +1,46 @@
+/*  Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <stdio.h>
+
+/*!
+ * \brief Hybrid of popen() and execve().
+ *
+ * This function is a safer altervative to popen(), it is the same to
+ * popen() as execve() is to system().
+ *
+ * \param binfile   Executable file to be executed.
+ * \param args      NULL-terminated arguments; first shall be the prog name!
+ * \param env       NULL-terminated environment variables "key=value"
+ *
+ * \retval < 0   Error occured, set to -errno.
+ * \return > 0   File descriptor of the pipe reading end.
+ */
+int kpopenve(const char *binfile, char *const args[], char *const env[]);
+
+/*!
+ * \brief Variant of kpopenve() returning FILE*
+ *
+ * \param binfile   Executable file to be executed.
+ * \param args      NULL-terminated arguments; first shall be the prog name!
+ * \param env       NULL-terminated environment variables "key=value"
+ *
+ * \retval NULL   Error occured, see errno.
+ * \return Pointer to open file descriptor.
+ */
+FILE *kpopenve2(const char *binfile, char *const args[], char *const env[]);
diff --git a/src/utils/knot-xdp-gun/main.c b/src/utils/knot-xdp-gun/main.c
index 74a6138f5bc0534ed731364db465e33821a06bae..8170ebf11af7f1ba8c4bef90ff94bb2b5c979623 100644
--- a/src/utils/knot-xdp-gun/main.c
+++ b/src/utils/knot-xdp-gun/main.c
@@ -37,6 +37,7 @@
 
 #include "libknot/libknot.h"
 #include "contrib/openbsd/strlcpy.h"
+#include "contrib/popenve.h"
 #include "utils/common/params.h"
 
 #include "load_queries.h"
@@ -342,34 +343,41 @@ static bool str2mac(const char *str, uint8_t mac[])
 	return true;
 }
 
-static int ip_route_get(const char *ip_str, const char *what, char **res)
+static FILE *popen_ip(char *arg1, char *arg2, char *arg3)
 {
-	char cmd[50 + strlen(ip_str) + strlen(what)];
-	(void)snprintf(cmd, sizeof(cmd), "ip route get %s | grep -o ' %s [^ ]* '", ip_str, what);
+	char *args[5] = { "ip", arg1, arg2, arg3, NULL };
+	char *env[] = { NULL };
+	return kpopenve2("/sbin/ip", args, env);
+}
 
+static int ip_route_get(const char *ip_str, const char *what, char **res)
+{
 	errno = 0;
-	FILE *p = popen(cmd, "r");
+	FILE *p = popen_ip("route", "get", (char *)ip_str); // hope ip_str gets not broken
 	if (p == NULL) {
 		return (errno != 0) ? knot_map_errno() : KNOT_ENOMEM;
 	}
 
-	char check[16] = { 0 }, got[256] = { 0 };
-	if (fscanf(p, "%15s%255s", check, got) != 2 ||
-	    strcmp(check, what) != 0) {
-		int ret = feof(p) ? KNOT_ENOENT : KNOT_EMALF;
-		pclose(p);
-		return ret;
+	char buf[256] = { 0 };
+	bool hit = false;
+	while (fscanf(p, "%255s", buf) == 1) {
+		if (hit) {
+			*res = strdup(buf);
+			fclose(p);
+			return *res == NULL ? KNOT_ENOMEM : KNOT_EOK;
+		}
+		if (strcmp(buf, what) == 0) {
+			hit = true;
+		}
 	}
-	pclose(p);
-
-	*res = strdup(got);
-	return *res == NULL ? KNOT_ENOMEM : KNOT_EOK;
+	fclose(p);
+	return KNOT_ENOENT;
 }
 
 static int remoteIP2MAC(const char *ip_str, bool ipv6, char devname[], uint8_t remote_mac[])
 {
 	errno = 0;
-	FILE *p = popen(ipv6 ? "ip -6 neigh" : "arp -ne", "r");
+	FILE *p = popen_ip(ipv6 ? "-6" : "-4", "neigh", NULL);
 	if (p == NULL) {
 		return (errno != 0) ? knot_map_errno() : KNOT_ENOMEM;
 	}
@@ -384,14 +392,14 @@ static int remoteIP2MAC(const char *ip_str, bool ipv6, char devname[], uint8_t r
 		if (strcmp(fields[0], ip_str) != 0) {
 			continue;
 		}
-		if (!str2mac(fields[ipv6 ? 4 : 2], remote_mac)) {
+		if (!str2mac(fields[4], remote_mac)) {
 			ret = KNOT_EMALF;
 		} else {
-			strlcpy(devname, fields[ipv6 ? 2 : 4], IFNAMSIZ);
+			strlcpy(devname, fields[2], IFNAMSIZ);
 			ret = KNOT_EOK;
 		}
 	}
-	pclose(p);
+	fclose(p);
 	return ret;
 }