diff --git a/net/Kconfig b/net/Kconfig
index 4215889127..34c1e43c87 100644
--- a/net/Kconfig
+++ b/net/Kconfig
@@ -5,9 +5,12 @@
 menuconfig NET
 	bool "Networking support"
 	default y
+	imply LWIP
 
 if NET
 
+source net/lwip/Kconfig
+
 config ARP_TIMEOUT
 	int "Milliseconds before trying ARP again"
 	default 5000
diff --git a/net/Makefile b/net/Makefile
index 64ab7ec740..bda6b383b5 100644
--- a/net/Makefile
+++ b/net/Makefile
@@ -33,6 +33,7 @@ obj-$(CONFIG_CMD_WOL)  += wol.o
 obj-$(CONFIG_PROT_UDP) += udp.o
 obj-$(CONFIG_PROT_TCP) += tcp.o
 obj-$(CONFIG_CMD_WGET) += wget.o
+obj-$(CONFIG_LWIP) += lwip/
 
 # Disable this warning as it is triggered by:
 # sprintf(buf, index ? "foo%d" : "foo", index)
diff --git a/net/lwip/Kconfig b/net/lwip/Kconfig
new file mode 100644
index 0000000000..295261a042
--- /dev/null
+++ b/net/lwip/Kconfig
@@ -0,0 +1,33 @@
+menu "lwIP"
+config LWIP
+	bool "Support LWIP library"
+	help
+	  Enable the lwIP library code with
+	  all dependencies (commands are implemented with lwIP
+	  library. This option is automatically enabled if CONFIG_NET=y.
+	  lwIP library (https://git.savannah.nongnu.org/git/lwip.git) provides
+	  network stack and application code for U-Boot commands.
+	  Please see doc/develop/net_lwip.rst for more details.
+
+menu "LWIP options"
+
+config LWIP_LIB_DEBUG
+	bool "enable debug"
+	default n
+	help
+	  Enable DEBUG information for IP stack.
+	  File net/lwip/lwipopts.h can be modified
+	  to enable specific debug message like
+	  raw packets or IP debug.
+
+config LWIP_LIB_NOASSERT
+	bool "disable asserts"
+	default y
+	help
+	  Disabling asserts reduces binary size by 16k.
+          lwIP assers are useful for testing, but for
+	  the production code we can fight for the size
+	  and reduce it with compile out asserts.
+endmenu
+
+endmenu
diff --git a/net/lwip/Makefile b/net/lwip/Makefile
new file mode 100644
index 0000000000..3fd5d34564
--- /dev/null
+++ b/net/lwip/Makefile
@@ -0,0 +1,64 @@
+# SPDX-License-Identifier: GPL-2.0+
+#
+# (C) Copyright 2023 Linaro Ltd. <maxim.uvarov@linaro.org>
+
+ccflags-y += -I$(srctree)/net/lwip/port/include
+ccflags-y += -I$(srctree)/net/lwip/lwip-external/src/include -I$(srctree)/net/lwip
+
+obj-$(CONFIG_NET) += lwip-external/src/core/init.o \
+	lwip-external/src/core/def.o \
+	lwip-external/src/core/dns.o \
+	lwip-external/src/core/inet_chksum.o \
+	lwip-external/src/core/ip.o \
+	lwip-external/src/core/mem.o \
+	lwip-external/src/core/memp.o \
+	lwip-external/src/core/netif.o \
+	lwip-external/src/core/pbuf.o \
+	lwip-external/src/core/raw.o \
+	lwip-external/src/core/stats.o \
+	lwip-external/src/core/sys.o \
+	lwip-external/src/core/altcp.o \
+	lwip-external/src/core/altcp_alloc.o \
+	lwip-external/src/core/altcp_tcp.o \
+	lwip-external/src/core/tcp.o \
+	lwip-external/src/core/tcp_in.o \
+	lwip-external/src/core/tcp_out.o \
+	lwip-external/src/core/timeouts.o \
+	lwip-external/src/core/udp.o
+
+# IPv4
+obj-$(CONFIG_NET) += lwip-external/src/core/ipv4/acd.o \
+        lwip-external/src/core/ipv4/autoip.o \
+        lwip-external/src/core/ipv4/dhcp.o \
+        lwip-external/src/core/ipv4/etharp.o \
+        lwip-external/src/core/ipv4/icmp.o \
+        lwip-external/src/core/ipv4/igmp.o \
+        lwip-external/src/core/ipv4/ip4_frag.o \
+        lwip-external/src/core/ipv4/ip4.o \
+        lwip-external/src/core/ipv4/ip4_addr.o
+# IPv6
+obj-$(CONFIG_NET) += lwip-external/src/core/ipv6/dhcp6.o \
+        lwip-external/src/core/ipv6/ethip6.o \
+        lwip-external/src/core/ipv6/icmp6.o \
+        lwip-external/src/core/ipv6/inet6.o \
+        lwip-external/src/core/ipv6/ip6.o \
+        lwip-external/src/core/ipv6/ip6_addr.o \
+        lwip-external/src/core/ipv6/ip6_frag.o \
+        lwip-external/src/core/ipv6/mld6.o \
+        lwip-external/src/core/ipv6/nd6.o
+# API
+obj-$(CONFIG_NET) += lwip-external/src/api/api_lib.o \
+	lwip-external/src/api/api_msg.o \
+	lwip-external/src/api/err.o \
+	lwip-external/src/api/if_api.o \
+	lwip-external/src/api/netbuf.o \
+	lwip-external/src/api/netdb.o \
+	lwip-external/src/api/netifapi.o \
+	lwip-external/src/api/sockets.o \
+	lwip-external/src/api/tcpip.o
+
+# Netdevs
+obj-$(CONFIG_NET) += lwip-external/src/netif/ethernet.o
+
+obj-$(CONFIG_NET) += port/if.o
+obj-$(CONFIG_NET) += port/sys-arch.o
diff --git a/net/lwip/lwip-external b/net/lwip/lwip-external
index 0a0452b2c3..5e3268cf3e 160000
--- a/net/lwip/lwip-external
+++ b/net/lwip/lwip-external
@@ -1 +1 @@
-Subproject commit 0a0452b2c39bdd91e252aef045c115f88f6ca773
+Subproject commit 5e3268cf3ea16ed4907a55e167fcf7e2b544fb20
diff --git a/net/net.c b/net/net.c
index 0fb2d25077..038ccd8933 100644
--- a/net/net.c
+++ b/net/net.c
@@ -125,6 +125,7 @@
 #endif
 #include "dhcpv6.h"
 #include "net_rand.h"
+#include <net/ulwip.h>
 
 /** BOOTP EXTENTIONS **/
 
@@ -393,7 +394,7 @@ static void net_cleanup_loop(void)
 	net_clear_handlers();
 }
 
-int net_init(void)
+void eth_init_rings(void)
 {
 	static int first_call = 1;
 
@@ -409,6 +410,17 @@ int net_init(void)
 			net_rx_packets[i] = net_tx_packet +
 				(i + 1) * PKTSIZE_ALIGN;
 		}
+		/* Only need to setup buffer pointers once. */
+		first_call = 0;
+	}
+}
+
+int net_init(void)
+{
+	static int first_call = 1;
+
+	eth_init_rings();
+	if (first_call) {
 		arp_init();
 		ndisc_init();
 		net_clear_handlers();
@@ -453,16 +465,18 @@ int net_loop(enum proto_t protocol)
 
 	bootstage_mark_name(BOOTSTAGE_ID_ETH_START, "eth_start");
 	net_init();
-	if (eth_is_on_demand_init()) {
-		eth_halt();
-		eth_set_current();
-		ret = eth_init();
-		if (ret < 0) {
+	if (!ulwip_active()) {
+		if (eth_is_on_demand_init()) {
 			eth_halt();
-			return ret;
+			eth_set_current();
+			ret = eth_init();
+			if (ret < 0) {
+				eth_halt();
+				return ret;
+			}
+		} else {
+			eth_init_state_only();
 		}
-	} else {
-		eth_init_state_only();
 	}
 
 restart:
@@ -633,7 +647,7 @@ restart:
 	 */
 	for (;;) {
 		schedule();
-		if (arp_timeout_check() > 0)
+		if (!ulwip_active() && (arp_timeout_check() > 0))
 			time_start = get_timer(0);
 
 		if (IS_ENABLED(CONFIG_IPV6)) {
@@ -649,6 +663,16 @@ restart:
 		 */
 		eth_rx();
 
+		if (ulwip_active()) {
+			net_set_state(NETLOOP_CONTINUE);
+			if (!ulwip_in_loop()) {
+				if (ulwip_app_get_err())
+					net_set_state(NETLOOP_FAIL);
+				else
+					net_set_state(NETLOOP_SUCCESS);
+				goto done;
+			}
+		}
 		/*
 		 *	Abort if ctrl-c was pressed.
 		 */
