diff --git a/lib/Kconfig b/lib/Kconfig
index 19649517a3..915402e843 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -258,7 +258,7 @@ config REGEX
 choice
 	prompt "Pseudo-random library support type"
 	depends on NET_RANDOM_ETHADDR || RANDOM_UUID || CMD_UUID || \
-		   RNG_SANDBOX || UT_LIB && AES || FAT_WRITE
+		   RNG_SANDBOX || UT_LIB && AES || FAT_WRITE || LWIP
 	default LIB_RAND
 	help
 	  Select the library to provide pseudo-random number generator
diff --git a/net/eth-uclass.c b/net/eth-uclass.c
index 3d0ec91dfa..f57da423f5 100644
--- a/net/eth-uclass.c
+++ b/net/eth-uclass.c
@@ -21,6 +21,7 @@
 #include <net/pcap.h>
 #include "eth_internal.h"
 #include <eth_phy.h>
+#include <net/ulwip.h>
 
 DECLARE_GLOBAL_DATA_PTR;
 
@@ -32,6 +33,7 @@ DECLARE_GLOBAL_DATA_PTR;
 struct eth_device_priv {
 	enum eth_state_t state;
 	bool running;
+	ulwip ulwip;
 };
 
 /**
@@ -347,6 +349,13 @@ int eth_init(void)
 	return ret;
 }
 
+struct ulwip *eth_lwip_priv(struct udevice *current)
+{
+	struct eth_device_priv *priv = dev_get_uclass_priv(current);
+
+	return &priv->ulwip;
+}
+
 void eth_halt(void)
 {
 	struct udevice *current;
@@ -420,8 +429,13 @@ int eth_rx(void)
 	for (i = 0; i < ETH_PACKETS_BATCH_RECV; i++) {
 		ret = eth_get_ops(current)->recv(current, flags, &packet);
 		flags = 0;
-		if (ret > 0)
-			net_process_received_packet(packet, ret);
+		if (ret > 0) {
+			if (ulwip_active())
+				ulwip_poll(packet, ret);
+			else
+				net_process_received_packet(packet, ret);
+		}
+
 		if (ret >= 0 && eth_get_ops(current)->free_pkt)
 			eth_get_ops(current)->free_pkt(current, packet, ret);
 		if (ret <= 0)
@@ -555,6 +569,7 @@ static int eth_post_probe(struct udevice *dev)
 	struct eth_pdata *pdata = dev_get_plat(dev);
 	unsigned char env_enetaddr[ARP_HLEN];
 	char *source = "DT";
+	int ret;
 
 	priv->state = ETH_STATE_INIT;
 	priv->running = false;
@@ -587,18 +602,26 @@ static int eth_post_probe(struct udevice *dev)
 		/* Override the ROM MAC address */
 		memcpy(pdata->enetaddr, env_enetaddr, ARP_HLEN);
 	} else if (is_valid_ethaddr(pdata->enetaddr)) {
-		eth_env_set_enetaddr_by_index("eth", dev_seq(dev),
-					      pdata->enetaddr);
+		ret = eth_env_set_enetaddr_by_index("eth", dev_seq(dev),
+						    pdata->enetaddr);
+		if (ret) {
+			log_err("Error update env for eth%d\n", dev_seq(dev));
+			return -EINVAL;
+		}
 	} else if (is_zero_ethaddr(pdata->enetaddr) ||
 		   !is_valid_ethaddr(pdata->enetaddr)) {
 #ifdef CONFIG_NET_RANDOM_ETHADDR
 		net_random_ethaddr(pdata->enetaddr);
 		printf("\nWarning: %s (eth%d) using random MAC address - %pM\n",
 		       dev->name, dev_seq(dev), pdata->enetaddr);
-		eth_env_set_enetaddr_by_index("eth", dev_seq(dev),
-					      pdata->enetaddr);
+		ret = eth_env_set_enetaddr_by_index("eth", dev_seq(dev),
+						    pdata->enetaddr);
+		if (ret) {
+			log_err("Error update env for eth%d\n", dev_seq(dev));
+			return -EINVAL;
+		}
 #else
-		printf("\nError: %s No valid MAC address found.\n",
+		log_err("\nError: %s No valid MAC address found.\n",
 		       dev->name);
 		return -EINVAL;
 #endif
diff --git a/net/lwip/Kconfig b/net/lwip/Kconfig
index 295261a042..9135dee6b4 100644
--- a/net/lwip/Kconfig
+++ b/net/lwip/Kconfig
@@ -1,6 +1,7 @@
 menu "lwIP"
 config LWIP
 	bool "Support LWIP library"
+	select LIB_RAND
 	help
 	  Enable the lwIP library code with
 	  all dependencies (commands are implemented with lwIP
diff --git a/net/lwip/port/if.c b/net/lwip/port/if.c
new file mode 100644
index 0000000000..ed15f4a459
--- /dev/null
+++ b/net/lwip/port/if.c
@@ -0,0 +1,327 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * (C) Copyright 2023 Linaro Ltd. <maxim.uvarov@linaro.org>
+ */
+
+#include <common.h>
+#include <command.h>
+#include <net/eth.h>
+#include <dm/device.h>
+#include <dm/uclass-id.h>
+#include <dm/uclass.h>
+#include "lwip/debug.h"
+#include "lwip/arch.h"
+#include "netif/etharp.h"
+#include "lwip/stats.h"
+#include "lwip/def.h"
+#include "lwip/mem.h"
+#include "lwip/pbuf.h"
+#include "lwip/sys.h"
+#include "lwip/netif.h"
+#include "lwip/ethip6.h"
+#include "lwip/timeouts.h"
+
+#include "lwip/ip.h"
+
+/*
+ * MAC_ADDR_STRLEN: length of mac address string
+ */
+#define MAC_ADDR_STRLEN 18
+
+int ulwip_active(void)
+{
+	struct udevice *udev;
+	struct ulwip *ulwip;
+
+	udev = eth_get_dev();
+	if (!udev)
+		return 0;
+
+	ulwip = eth_lwip_priv(udev);
+	return ulwip->active;
+}
+
+int ulwip_in_loop(void)
+{
+	struct udevice *udev;
+	struct ulwip *ulwip;
+
+	udev = eth_get_dev();
+	if (!udev)
+		return 0;
+
+	sys_check_timeouts();
+
+	ulwip = eth_lwip_priv(udev);
+	return ulwip->loop;
+}
+
+void ulwip_loop_set(int loop)
+{
+	struct udevice *udev;
+	struct ulwip *ulwip;
+
+	udev = eth_get_dev();
+	if (!udev)
+		return;
+
+	ulwip = eth_lwip_priv(udev);
+	ulwip->loop = loop;
+}
+
+void ulwip_exit(int err)
+{
+	struct udevice *udev;
+	struct ulwip *ulwip;
+
+	udev = eth_get_dev();
+	if (!udev)
+		return;
+
+	ulwip = eth_lwip_priv(udev);
+	ulwip->loop = 0;
+	ulwip->err = err;
+}
+
+int ulwip_app_get_err(void)
+{
+	struct udevice *udev;
+	struct ulwip *ulwip;
+
+	udev = eth_get_dev();
+	if (!udev)
+		return 0;
+
+	ulwip = eth_lwip_priv(udev);
+	return ulwip->err;
+}
+
+typedef struct {
+} ulwip_if_t;
+
+static struct pbuf *low_level_input(uchar *data, int len)
+{
+	struct pbuf *p, *q;
+
+	/* We allocate a pbuf chain of pbufs from the pool. */
+	p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
+	if (!p) {
+		LINK_STATS_INC(link.memerr);
+		LINK_STATS_INC(link.drop);
+		return NULL;
+	}
+
+	for (q = p; q != NULL; q = q->next) {
+		memcpy(q->payload, data, q->len);
+		data += q->len;
+	}
+
+	LINK_STATS_INC(link.recv);
+
+	return p;
+}
+
+void ulwip_poll(uchar *in_packet, int len)
+{
+	struct pbuf *p;
+	int err;
+	struct netif *netif;
+	int eth_idx;
+
+	eth_idx = eth_get_dev_index();
+	if (eth_idx < 0) {
+		log_err("no eth idx\n");
+		return;
+	}
+
+	netif = netif_get_by_index(eth_idx + 1);
+	if (!netif) {
+		log_err("!netif\n");
+		return;
+	}
+
+	p = low_level_input(in_packet, len);
+	if (!p) {
+		log_err("no mem\n");
+		return;
+	}
+
+	/* ethernet_input always returns ERR_OK */
+	err = ethernet_input(p, netif);
+	if (err)
+		log_err("ip4_input err %d\n", err);
+}
+
+static int ethernetif_input(struct pbuf *p, struct netif *netif)
+{
+	return 0;
+}
+
+static err_t low_level_output(struct netif *netif, struct pbuf *p)
+{
+	int err;
+
+	/* switch dev to active state */
+	eth_init_state_only();
+
+	err = eth_send(p->payload, p->len);
+	if (err) {
+		log_err("eth_send error %d\n", err);
+		return ERR_ABRT;
+	}
+	return ERR_OK;
+}
+
+err_t ulwip_if_init(struct netif *netif)
+{
+	ulwip_if_t *uif;
+	struct ulwip *ulwip;
+
+	uif = malloc(sizeof(ulwip_if_t));
+	if (!uif) {
+		log_err("uif: out of memory\n");
+		return ERR_MEM;
+	}
+	netif->state = uif;
+
+#if defined(CONFIG_LWIP_LIB_DEBUG)
+	log_info("              MAC: %02X:%02X:%02X:%02X:%02X:%02X\n",
+		 netif->hwaddr[0], netif->hwaddr[1], netif->hwaddr[2],
+		 netif->hwaddr[3], netif->hwaddr[4], netif->hwaddr[5]);
+	log_info("             NAME: %s\n", netif->name);
+#endif
+#if LWIP_IPV4
+	netif->output = etharp_output;
+#endif
+#if LWIP_IPV6
+	netif->output_ip6 = ethip6_output;
+#endif
+
+	netif->linkoutput = low_level_output;
+	netif->mtu = 1500;
+	netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP;
+
+	ulwip = eth_lwip_priv(eth_get_dev());
+	ulwip->init_done = 1;
+
+	return ERR_OK;
+}
+
+int ulwip_init(void)
+{
+	ip4_addr_t ipaddr, netmask, gw;
+	struct netif *unetif;
+	struct ulwip *ulwip;
+	struct udevice *udev;
+	int ret;
+	unsigned char env_enetaddr[ARP_HLEN];
+	const struct udevice *dev;
+	struct uclass *uc;
+
+	eth_set_current();
+
+	udev = eth_get_dev();
+	if (!udev) {
+		log_err("no active eth device\n");
+		return ERR_IF;
+	}
+
+	ulwip = eth_lwip_priv(udev);
+	if (ulwip->init_done) {
+		log_info("init already done for %s\n", udev->name);
+		ret = eth_init();
+		if (ret)
+			return ERR_IF;
+		ulwip->active = 1;
+		return CMD_RET_SUCCESS;
+	}
+
+	eth_init_rings();
+
+	ret = eth_init();
+	if (ret) {
+		log_err("eth_init error %d\n", ret);
+		return ERR_IF;
+	}
+
+	uclass_id_foreach_dev(UCLASS_ETH, dev, uc) {
+		char ipstr[IP4ADDR_STRLEN_MAX];
+		char maskstr[IP4ADDR_STRLEN_MAX];
+		char gwstr[IP4ADDR_STRLEN_MAX];
+		char hwstr[MAC_ADDR_STRLEN];
+		char *env;
+
+		eth_env_get_enetaddr_by_index("eth", dev_seq(dev), env_enetaddr);
+		log_info("eth%d: %s %pM %s\n", dev_seq(dev), dev->name, env_enetaddr,
+			 udev == dev ? "active" : "");
+
+		unetif = malloc(sizeof(struct netif));
+		if (!unetif)
+			return ERR_MEM;
+		memset(unetif, 0, sizeof(struct netif));
+
+		ip4_addr_set_zero(&gw);
+		ip4_addr_set_zero(&ipaddr);
+		ip4_addr_set_zero(&netmask);
+
+		if (dev_seq(dev) == 0) {
+			snprintf(ipstr, IP4ADDR_STRLEN_MAX, "ipaddr");
+			snprintf(maskstr, IP4ADDR_STRLEN_MAX, "netmask");
+			snprintf(gwstr, IP4ADDR_STRLEN_MAX, "gw");
+		} else {
+			snprintf(ipstr, IP4ADDR_STRLEN_MAX, "ipaddr%d", dev_seq(dev));
+			snprintf(maskstr, IP4ADDR_STRLEN_MAX, "netmask%d", dev_seq(dev));
+			snprintf(gwstr, IP4ADDR_STRLEN_MAX, "gw%d", dev_seq(dev));
+		}
+		snprintf(hwstr, MAC_ADDR_STRLEN, "%pM",  env_enetaddr);
+		snprintf(unetif->name, 2, "%d", dev_seq(dev));
+
+		string_to_enetaddr(hwstr, unetif->hwaddr);
+		unetif->hwaddr_len = ETHARP_HWADDR_LEN;
+
+		env = env_get(ipstr);
+		if (env)
+			ipaddr_aton(env, &ipaddr);
+
+		env = env_get(maskstr);
+		if (env)
+			ipaddr_aton(env, &netmask);
+
+		if (IS_ENABLED(CONFIG_LWIP_LIB_DEBUG)) {
+			log_info("Starting lwIP\n ");
+			log_info("	netdev: %s\n", dev->name);
+			log_info("	    IP: %s\n", ip4addr_ntoa(&ipaddr));
+			log_info("          GW: %s\n", ip4addr_ntoa(&gw));
+			log_info("        mask: %s\n", ip4addr_ntoa(&netmask));
+		}
+
+#if LWIP_IPV6
+#define MAC_FROM_48_BIT 1
+		netif_create_ip6_linklocal_address(unetif, MAC_FROM_48_BIT);
+		log_info("             IPv6: %s\n", ip6addr_ntoa(netif_ip6_addr(unetif, 0)));
+#endif /* LWIP_IPV6 */
+
+		if (!netif_add(unetif, &ipaddr, &netmask, &gw,
+			       unetif, ulwip_if_init, ethernetif_input)) {
+			log_err("err: netif_add failed!\n");
+			free(unetif);
+			return ERR_IF;
+		}
+
+		netif_set_up(unetif);
+		netif_set_link_up(unetif);
+	}
+
+	if (IS_ENABLED(CONFIG_LWIP_LIB_DEBUG)) {
+		log_info("Initialized LWIP stack\n");
+	}
+
+	ulwip->active = 1;
+	return CMD_RET_SUCCESS;
+}
+
+/* placeholder, not used now */
+void ulwip_destroy(void)
+{
+}
diff --git a/net/lwip/port/include/arch/cc.h b/net/lwip/port/include/arch/cc.h
new file mode 100644
index 0000000000..74d0a0932a
--- /dev/null
+++ b/net/lwip/port/include/arch/cc.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * (C) Copyright 2023 Linaro Ltd. <maxim.uvarov@linaro.org>
+ */
+
+#ifndef LWIP_ARCH_CC_H
+#define LWIP_ARCH_CC_H
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <vsprintf.h>
+#include <rand.h>
+
+#define LWIP_ERRNO_INCLUDE <errno.h>
+
+#define LWIP_ERRNO_STDINCLUDE	1
+#define LWIP_NO_UNISTD_H 1
+#define LWIP_TIMEVAL_PRIVATE 1
+
+#define LWIP_RAND() ((u32_t)rand())
+
+/* different handling for unit test, normally not needed */
+#ifdef LWIP_NOASSERT_ON_ERROR
+#define LWIP_ERROR(message, expression, handler) do { if (!(expression)) { \
+						handler; }} while (0)
+#endif
+
+#define LWIP_DONT_PROVIDE_BYTEORDER_FUNCTIONS
+
+#define LWIP_PLATFORM_ASSERT(x) do {printf("Assertion \"%s\" failed at line %d in %s\n", \
+				    x, __LINE__, __FILE__); } while (0)
+
+#define atoi(str) (int)dectoul(str, NULL)
+#define lwip_strnstr(a, b)  strnstr(a, b)
+
+#define LWIP_ERR_T int
+#define LWIP_CONST_CAST(target_type, val) ((target_type)((uintptr_t)val))
+
+#if defined(CONFIG_SYS_BIG_ENDIAN)
+#define BYTE_ORDER BIG_ENDIAN
+#endif
+
+#endif /* LWIP_ARCH_CC_H */
diff --git a/net/lwip/port/include/arch/sys_arch.h b/net/lwip/port/include/arch/sys_arch.h
new file mode 100644
index 0000000000..87a3fb66d1
--- /dev/null
+++ b/net/lwip/port/include/arch/sys_arch.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * (C) Copyright 2023 Linaro Ltd. <maxim.uvarov@linaro.org>
+ */
+
+#ifndef LWIP_ARCH_SYS_ARCH_H
+#define LWIP_ARCH_SYS_ARCH_H
+
+#endif /* LWIP_ARCH_SYS_ARCH_H */
diff --git a/net/lwip/port/include/limits.h b/net/lwip/port/include/limits.h
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/net/lwip/port/sys-arch.c b/net/lwip/port/sys-arch.c
new file mode 100644
index 0000000000..68476d16e8
--- /dev/null
+++ b/net/lwip/port/sys-arch.c
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * (C) Copyright 2023 Linaro Ltd. <maxim.uvarov@linaro.org>
+ */
+
+#include <common.h>
+#include "lwip/opt.h"
+
+u32_t sys_now(void)
+{
+	return get_timer(0);
+}
