diff mbox series

net/netcat: add netcat over tcp support

Message ID 20250101033326.439585-1-mikhail.kshevetskiy@iopsys.eu
State New
Delegated to: Ramon Fried
Headers show
Series net/netcat: add netcat over tcp support | expand

Commit Message

Mikhail Kshevetskiy Jan. 1, 2025, 3:33 a.m. UTC
This patch adds downloading/uploading of data with netcat utility.
Client/server modes are supported both.

This patch is also an example of using new tcp api for legacy stack.

How to test:
============
  netcat-openbsd=1.219-1 from debian were used for a tests

  a) Load data from remote host.
       * U-Boot listen on tcp port 3456
       * PC connects

     u-boot: netcat load ${loadaddr} 3456
     PC:     netcat -q1 ${UBOOT_IP} 3456 < image.itb

  b) Load data from remote host.
       * PC listen on tcp port 3456
       * U-Boot connects

     PC:     netcat -q1 -l -p 3456 < image.itb
     u-boot: netcat load ${loadaddr} ${PC_IP}:3456

  c) Save data to remote host
       * U-Boot listen on tcp port 3456
       * PC connects

     u-boot: netcat save ${loadaddr} ${data_size_in_hex} 3456
     PC:     netcat -w1 ${UBOOT_IP} 3456 >image.itb

  d) Save data to remote host
       * PC listen on tcp port 3456
       * U-Boot connects

     PC:     netcat -w1 -l -p 3456 >image.itb
     u-boot: netcat save ${loadaddr} ${data_size_in_hex} ${PC_IP}:3456

Signed-off-by: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
Reviewed-by: Simon Glass <sjg@chromium.org>
---
 cmd/Kconfig          |  10 +++
 cmd/net.c            |  35 ++++++--
 include/net-legacy.h |   2 +-
 include/net/netcat.h |  20 +++++
 net/Makefile         |   1 +
 net/net.c            |   9 ++
 net/netcat.c         | 194 +++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 265 insertions(+), 6 deletions(-)
 create mode 100644 include/net/netcat.h
 create mode 100644 net/netcat.c

Comments

Heinrich Schuchardt Jan. 1, 2025, 9:44 a.m. UTC | #1
On 1/1/25 04:33, Mikhail Kshevetskiy wrote:
> This patch adds downloading/uploading of data with netcat utility.
> Client/server modes are supported both.
>
> This patch is also an example of using new tcp api for legacy stack.
>
> How to test:
> ============
>    netcat-openbsd=1.219-1 from debian were used for a tests
>
>    a) Load data from remote host.
>         * U-Boot listen on tcp port 3456
>         * PC connects
>
>       u-boot: netcat load ${loadaddr} 3456
>       PC:     netcat -q1 ${UBOOT_IP} 3456 < image.itb
>
>    b) Load data from remote host.
>         * PC listen on tcp port 3456
>         * U-Boot connects
>
>       PC:     netcat -q1 -l -p 3456 < image.itb
>       u-boot: netcat load ${loadaddr} ${PC_IP}:3456
>
>    c) Save data to remote host
>         * U-Boot listen on tcp port 3456
>         * PC connects
>
>       u-boot: netcat save ${loadaddr} ${data_size_in_hex} 3456
>       PC:     netcat -w1 ${UBOOT_IP} 3456 >image.itb
>
>    d) Save data to remote host
>         * PC listen on tcp port 3456
>         * U-Boot connects
>
>       PC:     netcat -w1 -l -p 3456 >image.itb
>       u-boot: netcat save ${loadaddr} ${data_size_in_hex} ${PC_IP}:3456
>
> Signed-off-by: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
> Reviewed-by: Simon Glass <sjg@chromium.org>
> ---
>   cmd/Kconfig          |  10 +++
>   cmd/net.c            |  35 ++++++--
>   include/net-legacy.h |   2 +-
>   include/net/netcat.h |  20 +++++
>   net/Makefile         |   1 +
>   net/net.c            |   9 ++
>   net/netcat.c         | 194 +++++++++++++++++++++++++++++++++++++++++++
>   7 files changed, 265 insertions(+), 6 deletions(-)
>   create mode 100644 include/net/netcat.h
>   create mode 100644 net/netcat.c
>
> diff --git a/cmd/Kconfig b/cmd/Kconfig
> index 93efeaec6f4..e37e50a18b5 100644
> --- a/cmd/Kconfig
> +++ b/cmd/Kconfig
> @@ -2091,6 +2091,16 @@ config CMD_DNS
>   	help
>   	  Lookup the IP of a hostname
>
> +config CMD_NETCAT
> +	bool "netcat"
> +	select PROT_TCP

Thank you for your patch which looks like a useful addition to U-Boot.

Here you make the netcat command depend on CMD_NET. This dependency
seems unnecessary at least for the lwIP case.

> +	help
> +	  netcat is a simple command to load/store kernel, or other files,
> +	  using well-known netcat (nc) utility. Unlike classic netcat utility
> +	  this command supports TCP-based data transfer only, thus no data
> +	  will be lost or reordered. Any netcat implementation should work,
> +	  but openbsd one was tested only.
> +
>   config CMD_MII
>   	bool "mii"
>   	imply CMD_MDIO
> diff --git a/cmd/net.c b/cmd/net.c
> index 79525f73a51..d7a5025f940 100644
> --- a/cmd/net.c
> +++ b/cmd/net.c
> @@ -208,6 +208,29 @@ U_BOOT_CMD(
>   );
>   #endif
>
> +#if defined(CONFIG_CMD_NETCAT)
> +static int do_netcat_load(struct cmd_tbl *cmdtp, int flag, int argc,
> +			  char *const argv[])
> +{
> +	return netboot_common(NETCAT_LOAD, cmdtp, argc, argv);
> +}
> +
> +static int do_netcat_save(struct cmd_tbl *cmdtp, int flag, int argc,
> +			  char *const argv[])
> +{
> +	return netboot_common(NETCAT_SAVE, cmdtp, argc, argv);
> +}
> +
> +U_BOOT_LONGHELP(netcat,
> +	"load [loadAddress] [[hostIPaddr:]port]\n"
> +	"save Address Size [[hostIPaddr:]port]");
> +
> +U_BOOT_CMD_WITH_SUBCMDS(netcat,
> +	"load/store data over plain TCP via netcat utility", netcat_help_text,
> +	U_BOOT_SUBCMD_MKENT(load, 3, 0, do_netcat_load),
> +	U_BOOT_SUBCMD_MKENT(save, 4, 0, do_netcat_save));
> +#endif
> +
>   static void netboot_update_env(void)
>   {
>   	char tmp[46];
> @@ -325,16 +348,17 @@ static int parse_args(enum proto_t proto, int argc, char *const argv[])
>
>   	switch (argc) {
>   	case 1:
> -		if (IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT)
> +		if ((IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT) ||
> +		    (IS_ENABLED(CONFIG_CMD_NETCAT) && proto == NETCAT_SAVE))
>   			return 1;
> -
>   		/* refresh bootfile name from env */
>   		copy_filename(net_boot_file_name, env_get("bootfile"),
>   			      sizeof(net_boot_file_name));
>   		break;
>
>   	case 2:
> -		if (IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT)
> +		if ((IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT) ||
> +		    (IS_ENABLED(CONFIG_CMD_NETCAT) && proto == NETCAT_SAVE))
>   			return 1;
>   		/*
>   		 * Only one arg - accept two forms:
> @@ -356,7 +380,8 @@ static int parse_args(enum proto_t proto, int argc, char *const argv[])
>   		break;
>
>   	case 3:
> -		if (IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT) {
> +		if ((IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT) ||
> +		    (IS_ENABLED(CONFIG_CMD_NETCAT) && proto == NETCAT_SAVE)) {
>   			if (parse_addr_size(argv))
>   				return 1;
>   		} else {
> @@ -367,7 +392,7 @@ static int parse_args(enum proto_t proto, int argc, char *const argv[])
>   		}
>   		break;
>
> -#ifdef CONFIG_CMD_TFTPPUT
> +#if defined(CONFIG_CMD_TFTPPUT) || defined(CONFIG_CMD_NETCAT)
>   	case 4:
>   		if (parse_addr_size(argv))
>   			return 1;
> diff --git a/include/net-legacy.h b/include/net-legacy.h
> index bc0f0cde9fe..84a4beb9ec1 100644
> --- a/include/net-legacy.h
> +++ b/include/net-legacy.h
> @@ -305,7 +305,7 @@ extern int		net_restart_wrap;	/* Tried all network devices */
>   enum proto_t {
>   	BOOTP, RARP, ARP, TFTPGET, DHCP, DHCP6, PING, PING6, DNS, NFS, CDP,
>   	NETCONS, SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT_UDP, FASTBOOT_TCP,
> -	WOL, UDP, NCSI, WGET, RS
> +	WOL, UDP, NCSI, WGET, NETCAT_LOAD, NETCAT_SAVE, RS
>   };
>
>   /* Indicates whether the file name was specified on the command line */
> diff --git a/include/net/netcat.h b/include/net/netcat.h
> new file mode 100644
> index 00000000000..d8e09aaaa55
> --- /dev/null
> +++ b/include/net/netcat.h
> @@ -0,0 +1,20 @@
> +/* SPDX-License-Identifier: BSD-2-Clause

According to doc/develop/sending_patches.rst all new code should use the
GPL-2.0-or-later license.

> + *
> + * netcat include file
> + * Copyright (C) 2024 IOPSYS Software Solutions AB
> + * Author: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
> + */
> +#ifndef __NET_NETCAT_TCP_H__
> +#define __NET_NETCAT_TCP_H__
> +
> +/**
> + * netcat_load_start() - begin netcat in loading mode
> + */
> +void netcat_load_start(void);
> +
> +/**
> + * netcat_save_start() - begin netcat in data saving mode
> + */
> +void netcat_save_start(void);
> +
> +#endif /* __NET_NETCAT_TCP_H__ */
> diff --git a/net/Makefile b/net/Makefile
> index 7c917b318c0..bc5f2f123be 100644
> --- a/net/Makefile
> +++ b/net/Makefile
> @@ -30,6 +30,7 @@ obj-$(CONFIG_CMD_WOL)  += wol.o
>   obj-$(CONFIG_PROT_UDP) += udp.o
>   obj-$(CONFIG_PROT_TCP) += tcp.o
>   obj-$(CONFIG_WGET) += wget.o
> +obj-$(CONFIG_CMD_NETCAT) += netcat.o
>
>   # Disable this warning as it is triggered by:
>   # sprintf(buf, index ? "foo%d" : "foo", index)
> diff --git a/net/net.c b/net/net.c
> index 1828f1cca36..595bffdc532 100644
> --- a/net/net.c
> +++ b/net/net.c
> @@ -103,6 +103,7 @@
>   #include <net/fastboot_udp.h>
>   #include <net/fastboot_tcp.h>
>   #include <net/ncsi.h>
> +#include <net/netcat.h>
>   #if defined(CONFIG_CMD_PCAP)
>   #include <net/pcap.h>
>   #endif
> @@ -572,6 +573,14 @@ restart:
>   			wget_start();
>   			break;
>   #endif
> +#if defined(CONFIG_CMD_NETCAT)
> +		case NETCAT_LOAD:
> +			netcat_load_start();
> +			break;
> +		case NETCAT_SAVE:
> +			netcat_save_start();
> +			break;
> +#endif
>   #if defined(CONFIG_CMD_CDP)
>   		case CDP:
>   			cdp_start();
> diff --git a/net/netcat.c b/net/netcat.c
> new file mode 100644
> index 00000000000..47dbebe5ae2
> --- /dev/null
> +++ b/net/netcat.c
> @@ -0,0 +1,194 @@
> +// SPDX-License-Identifier: GPL-2.0

According to doc/develop/sending_patches.rst all new code should use the
GPL-2.0-or-later license.

> +/*
> + * netcat support driver
> + * Copyright (C) 2024 IOPSYS Software Solutions AB
> + * Author: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
> + */
> +
> +#include <command.h>
> +#include <display_options.h>
> +#include <env.h>
> +#include <image.h>
> +#include <mapmem.h>
> +#include <net.h>
> +#include <net/tcp.h>
> +#include <net/netcat.h>
> +
> +#define HASHES_PER_LINE		65
> +#define MARKER_STEP		10
> +
> +static struct in_addr		server_ip;
> +static u16			server_port;
> +static u16			local_port;
> +static int			listen;
> +static int			reading;
> +static int			marker, marks_in_line;
> +static enum net_loop_state	netcat_loop_state;
> +

Please, describe all functions in Sphinx style.  Cf.
https://docs.kernel.org/doc-guide/kernel-doc.html#function-documentation

> +static void show_block_marker(int packets)
> +{
> +	for (; marker + MARKER_STEP <= packets; marker += MARKER_STEP) {
> +		marks_in_line++;
> +		putc('#');
> +		if (marks_in_line == HASHES_PER_LINE) {
> +			marks_in_line = 0;
> +			puts("\n");
> +		}
> +	}
> +}
> +
> +static void tcp_stream_on_closed(struct tcp_stream *tcp)
> +{
> +	if (tcp->status != TCP_ERR_OK)
> +		netcat_loop_state = NETLOOP_FAIL;
> +
> +	/*
> +	 * The status line will be shown after the download/upload
> +	 * progress (see show_block_marker() function). This progress
> +	 * generally have no final "\n", so jump to new line before
> +	 * output the status
> +	 */
> +	if (netcat_loop_state != NETLOOP_SUCCESS) {
> +		net_boot_file_size = 0;
> +		printf("\nnetcat: Transfer Fail, TCP status - %d\n", tcp->status);
> +	} else {
> +		env_set_hex("filesize", net_boot_file_size);
> +		printf("\nPackets %s %d, Transfer Successful\n",
> +		       reading ? "received" : "transmitted",
> +		       reading ? tcp->rx_packets : tcp->tx_packets);
> +	}
> +	net_set_state(netcat_loop_state);
> +}
> +
> +static void tcp_stream_on_rcv_nxt_update(struct tcp_stream *tcp, u32 rx_bytes)
> +{
> +	net_boot_file_size = rx_bytes;
> +	show_block_marker(tcp->rx_packets);
> +}
> +
> +static void tcp_stream_on_snd_una_update(struct tcp_stream *tcp, u32 tx_bytes)
> +{
> +	show_block_marker(tcp->tx_packets);
> +	if (tx_bytes == image_save_size)
> +		tcp_stream_close(tcp);
> +}
> +
> +static void tcp_stream_on_established(struct tcp_stream *tcp)
> +{
> +	netcat_loop_state = NETLOOP_SUCCESS;
> +}
> +
> +static int tcp_stream_rx(struct tcp_stream *tcp, u32 rx_offs, void *buf, int len)
> +{
> +	void *ptr;
> +
> +	ptr = map_sysmem(image_load_addr + rx_offs, len);
> +	memcpy(ptr, buf, len);
> +	unmap_sysmem(ptr);
> +
> +	return len;
> +}
> +
> +static int tcp_stream_tx(struct tcp_stream *tcp, u32 tx_offs, void *buf, int maxlen)
> +{
> +	void *ptr;
> +
> +	if (tx_offs + maxlen > image_save_size)
> +		maxlen = image_save_size - tx_offs;
> +	if (!maxlen)
> +		return 0;
> +
> +	ptr = map_sysmem(image_save_addr + tx_offs, maxlen);
> +	memcpy(buf, ptr, maxlen);
> +	unmap_sysmem(ptr);
> +
> +	return maxlen;
> +}
> +
> +/*
> + * This function will be called on new connections (incoming or outgoing)
> + * It MUST:
> + *   -- setup required tcp_stream callbacks (if connection will be accepted)
> + *   -- return a connection verdict:
> + *        0: discard connection
> + *        1: accept connection

Please, use a Sphinx style function description.

Best regards

Heinrich

> + */
> +static int tcp_stream_on_create(struct tcp_stream *tcp)
> +{
> +	if (listen) {
> +		/*
> +		 * We are listening for incoming connections.
> +		 * Accept connections to netcat listen port only.
> +		 */
> +		if (tcp->lport != local_port)
> +			return 0;
> +	} else {
> +		/*
> +		 * We are connecting to remote host.
> +		 * Check remote IP:port to make sure that this is our connection
> +		 */
> +		if (tcp->rhost.s_addr != server_ip.s_addr ||
> +		    tcp->rport != server_port)
> +			return 0;
> +	}
> +
> +	netcat_loop_state = NETLOOP_FAIL;
> +	net_boot_file_size = 0;
> +	marker = 0;
> +	marks_in_line = 0;
> +
> +	tcp->on_closed = tcp_stream_on_closed;
> +	tcp->on_established = tcp_stream_on_established;
> +	if (reading) {
> +		tcp->on_rcv_nxt_update = tcp_stream_on_rcv_nxt_update;
> +		tcp->rx = tcp_stream_rx;
> +	} else {
> +		tcp->on_snd_una_update = tcp_stream_on_snd_una_update;
> +		tcp->tx = tcp_stream_tx;
> +	}
> +
> +	return 1;
> +}
> +
> +static void netcat_start(void)
> +{
> +	struct tcp_stream *tcp;
> +
> +	memset(net_server_ethaddr, 0, 6);
> +	tcp_stream_set_on_create_handler(tcp_stream_on_create);
> +
> +	if (!strchr(net_boot_file_name, ':')) {
> +		listen = 1;
> +		printf("Listening on port %s...\n", net_boot_file_name);
> +
> +		local_port = dectoul(net_boot_file_name, NULL);
> +	} else {
> +		listen = 0;
> +		printf("Connecting to %s...\n", net_boot_file_name);
> +
> +		server_ip = string_to_ip(net_boot_file_name);
> +		server_port = dectoul(strchr(net_boot_file_name, ':') + 1, NULL);
> +
> +		tcp = tcp_stream_connect(server_ip, server_port);
> +		if (!tcp) {
> +			printf("No free tcp streams\n");
> +			net_set_state(NETLOOP_FAIL);
> +			return;
> +		}
> +		tcp_stream_put(tcp);
> +	}
> +}
> +
> +void netcat_load_start(void)
> +{
> +	reading = 1;
> +
> +	return netcat_start();
> +}
> +
> +void netcat_save_start(void)
> +{
> +	reading = 0;
> +
> +	return netcat_start();
> +}
diff mbox series

Patch

diff --git a/cmd/Kconfig b/cmd/Kconfig
index 93efeaec6f4..e37e50a18b5 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -2091,6 +2091,16 @@  config CMD_DNS
 	help
 	  Lookup the IP of a hostname
 
+config CMD_NETCAT
+	bool "netcat"
+	select PROT_TCP
+	help
+	  netcat is a simple command to load/store kernel, or other files,
+	  using well-known netcat (nc) utility. Unlike classic netcat utility
+	  this command supports TCP-based data transfer only, thus no data
+	  will be lost or reordered. Any netcat implementation should work,
+	  but openbsd one was tested only.
+
 config CMD_MII
 	bool "mii"
 	imply CMD_MDIO
diff --git a/cmd/net.c b/cmd/net.c
index 79525f73a51..d7a5025f940 100644
--- a/cmd/net.c
+++ b/cmd/net.c
@@ -208,6 +208,29 @@  U_BOOT_CMD(
 );
 #endif
 
+#if defined(CONFIG_CMD_NETCAT)
+static int do_netcat_load(struct cmd_tbl *cmdtp, int flag, int argc,
+			  char *const argv[])
+{
+	return netboot_common(NETCAT_LOAD, cmdtp, argc, argv);
+}
+
+static int do_netcat_save(struct cmd_tbl *cmdtp, int flag, int argc,
+			  char *const argv[])
+{
+	return netboot_common(NETCAT_SAVE, cmdtp, argc, argv);
+}
+
+U_BOOT_LONGHELP(netcat,
+	"load [loadAddress] [[hostIPaddr:]port]\n"
+	"save Address Size [[hostIPaddr:]port]");
+
+U_BOOT_CMD_WITH_SUBCMDS(netcat,
+	"load/store data over plain TCP via netcat utility", netcat_help_text,
+	U_BOOT_SUBCMD_MKENT(load, 3, 0, do_netcat_load),
+	U_BOOT_SUBCMD_MKENT(save, 4, 0, do_netcat_save));
+#endif
+
 static void netboot_update_env(void)
 {
 	char tmp[46];
@@ -325,16 +348,17 @@  static int parse_args(enum proto_t proto, int argc, char *const argv[])
 
 	switch (argc) {
 	case 1:
-		if (IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT)
+		if ((IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT) ||
+		    (IS_ENABLED(CONFIG_CMD_NETCAT) && proto == NETCAT_SAVE))
 			return 1;
-
 		/* refresh bootfile name from env */
 		copy_filename(net_boot_file_name, env_get("bootfile"),
 			      sizeof(net_boot_file_name));
 		break;
 
 	case 2:
-		if (IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT)
+		if ((IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT) ||
+		    (IS_ENABLED(CONFIG_CMD_NETCAT) && proto == NETCAT_SAVE))
 			return 1;
 		/*
 		 * Only one arg - accept two forms:
@@ -356,7 +380,8 @@  static int parse_args(enum proto_t proto, int argc, char *const argv[])
 		break;
 
 	case 3:
-		if (IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT) {
+		if ((IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT) ||
+		    (IS_ENABLED(CONFIG_CMD_NETCAT) && proto == NETCAT_SAVE)) {
 			if (parse_addr_size(argv))
 				return 1;
 		} else {
@@ -367,7 +392,7 @@  static int parse_args(enum proto_t proto, int argc, char *const argv[])
 		}
 		break;
 
-#ifdef CONFIG_CMD_TFTPPUT
+#if defined(CONFIG_CMD_TFTPPUT) || defined(CONFIG_CMD_NETCAT)
 	case 4:
 		if (parse_addr_size(argv))
 			return 1;
diff --git a/include/net-legacy.h b/include/net-legacy.h
index bc0f0cde9fe..84a4beb9ec1 100644
--- a/include/net-legacy.h
+++ b/include/net-legacy.h
@@ -305,7 +305,7 @@  extern int		net_restart_wrap;	/* Tried all network devices */
 enum proto_t {
 	BOOTP, RARP, ARP, TFTPGET, DHCP, DHCP6, PING, PING6, DNS, NFS, CDP,
 	NETCONS, SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT_UDP, FASTBOOT_TCP,
-	WOL, UDP, NCSI, WGET, RS
+	WOL, UDP, NCSI, WGET, NETCAT_LOAD, NETCAT_SAVE, RS
 };
 
 /* Indicates whether the file name was specified on the command line */
diff --git a/include/net/netcat.h b/include/net/netcat.h
new file mode 100644
index 00000000000..d8e09aaaa55
--- /dev/null
+++ b/include/net/netcat.h
@@ -0,0 +1,20 @@ 
+/* SPDX-License-Identifier: BSD-2-Clause
+ *
+ * netcat include file
+ * Copyright (C) 2024 IOPSYS Software Solutions AB
+ * Author: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+ */
+#ifndef __NET_NETCAT_TCP_H__
+#define __NET_NETCAT_TCP_H__
+
+/**
+ * netcat_load_start() - begin netcat in loading mode
+ */
+void netcat_load_start(void);
+
+/**
+ * netcat_save_start() - begin netcat in data saving mode
+ */
+void netcat_save_start(void);
+
+#endif /* __NET_NETCAT_TCP_H__ */
diff --git a/net/Makefile b/net/Makefile
index 7c917b318c0..bc5f2f123be 100644
--- a/net/Makefile
+++ b/net/Makefile
@@ -30,6 +30,7 @@  obj-$(CONFIG_CMD_WOL)  += wol.o
 obj-$(CONFIG_PROT_UDP) += udp.o
 obj-$(CONFIG_PROT_TCP) += tcp.o
 obj-$(CONFIG_WGET) += wget.o
+obj-$(CONFIG_CMD_NETCAT) += netcat.o
 
 # Disable this warning as it is triggered by:
 # sprintf(buf, index ? "foo%d" : "foo", index)
diff --git a/net/net.c b/net/net.c
index 1828f1cca36..595bffdc532 100644
--- a/net/net.c
+++ b/net/net.c
@@ -103,6 +103,7 @@ 
 #include <net/fastboot_udp.h>
 #include <net/fastboot_tcp.h>
 #include <net/ncsi.h>
+#include <net/netcat.h>
 #if defined(CONFIG_CMD_PCAP)
 #include <net/pcap.h>
 #endif
@@ -572,6 +573,14 @@  restart:
 			wget_start();
 			break;
 #endif
+#if defined(CONFIG_CMD_NETCAT)
+		case NETCAT_LOAD:
+			netcat_load_start();
+			break;
+		case NETCAT_SAVE:
+			netcat_save_start();
+			break;
+#endif
 #if defined(CONFIG_CMD_CDP)
 		case CDP:
 			cdp_start();
diff --git a/net/netcat.c b/net/netcat.c
new file mode 100644
index 00000000000..47dbebe5ae2
--- /dev/null
+++ b/net/netcat.c
@@ -0,0 +1,194 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * netcat support driver
+ * Copyright (C) 2024 IOPSYS Software Solutions AB
+ * Author: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+ */
+
+#include <command.h>
+#include <display_options.h>
+#include <env.h>
+#include <image.h>
+#include <mapmem.h>
+#include <net.h>
+#include <net/tcp.h>
+#include <net/netcat.h>
+
+#define HASHES_PER_LINE		65
+#define MARKER_STEP		10
+
+static struct in_addr		server_ip;
+static u16			server_port;
+static u16			local_port;
+static int			listen;
+static int			reading;
+static int			marker, marks_in_line;
+static enum net_loop_state	netcat_loop_state;
+
+static void show_block_marker(int packets)
+{
+	for (; marker + MARKER_STEP <= packets; marker += MARKER_STEP) {
+		marks_in_line++;
+		putc('#');
+		if (marks_in_line == HASHES_PER_LINE) {
+			marks_in_line = 0;
+			puts("\n");
+		}
+	}
+}
+
+static void tcp_stream_on_closed(struct tcp_stream *tcp)
+{
+	if (tcp->status != TCP_ERR_OK)
+		netcat_loop_state = NETLOOP_FAIL;
+
+	/*
+	 * The status line will be shown after the download/upload
+	 * progress (see show_block_marker() function). This progress
+	 * generally have no final "\n", so jump to new line before
+	 * output the status
+	 */
+	if (netcat_loop_state != NETLOOP_SUCCESS) {
+		net_boot_file_size = 0;
+		printf("\nnetcat: Transfer Fail, TCP status - %d\n", tcp->status);
+	} else {
+		env_set_hex("filesize", net_boot_file_size);
+		printf("\nPackets %s %d, Transfer Successful\n",
+		       reading ? "received" : "transmitted",
+		       reading ? tcp->rx_packets : tcp->tx_packets);
+	}
+	net_set_state(netcat_loop_state);
+}
+
+static void tcp_stream_on_rcv_nxt_update(struct tcp_stream *tcp, u32 rx_bytes)
+{
+	net_boot_file_size = rx_bytes;
+	show_block_marker(tcp->rx_packets);
+}
+
+static void tcp_stream_on_snd_una_update(struct tcp_stream *tcp, u32 tx_bytes)
+{
+	show_block_marker(tcp->tx_packets);
+	if (tx_bytes == image_save_size)
+		tcp_stream_close(tcp);
+}
+
+static void tcp_stream_on_established(struct tcp_stream *tcp)
+{
+	netcat_loop_state = NETLOOP_SUCCESS;
+}
+
+static int tcp_stream_rx(struct tcp_stream *tcp, u32 rx_offs, void *buf, int len)
+{
+	void *ptr;
+
+	ptr = map_sysmem(image_load_addr + rx_offs, len);
+	memcpy(ptr, buf, len);
+	unmap_sysmem(ptr);
+
+	return len;
+}
+
+static int tcp_stream_tx(struct tcp_stream *tcp, u32 tx_offs, void *buf, int maxlen)
+{
+	void *ptr;
+
+	if (tx_offs + maxlen > image_save_size)
+		maxlen = image_save_size - tx_offs;
+	if (!maxlen)
+		return 0;
+
+	ptr = map_sysmem(image_save_addr + tx_offs, maxlen);
+	memcpy(buf, ptr, maxlen);
+	unmap_sysmem(ptr);
+
+	return maxlen;
+}
+
+/*
+ * This function will be called on new connections (incoming or outgoing)
+ * It MUST:
+ *   -- setup required tcp_stream callbacks (if connection will be accepted)
+ *   -- return a connection verdict:
+ *        0: discard connection
+ *        1: accept connection
+ */
+static int tcp_stream_on_create(struct tcp_stream *tcp)
+{
+	if (listen) {
+		/*
+		 * We are listening for incoming connections.
+		 * Accept connections to netcat listen port only.
+		 */
+		if (tcp->lport != local_port)
+			return 0;
+	} else {
+		/*
+		 * We are connecting to remote host.
+		 * Check remote IP:port to make sure that this is our connection
+		 */
+		if (tcp->rhost.s_addr != server_ip.s_addr ||
+		    tcp->rport != server_port)
+			return 0;
+	}
+
+	netcat_loop_state = NETLOOP_FAIL;
+	net_boot_file_size = 0;
+	marker = 0;
+	marks_in_line = 0;
+
+	tcp->on_closed = tcp_stream_on_closed;
+	tcp->on_established = tcp_stream_on_established;
+	if (reading) {
+		tcp->on_rcv_nxt_update = tcp_stream_on_rcv_nxt_update;
+		tcp->rx = tcp_stream_rx;
+	} else {
+		tcp->on_snd_una_update = tcp_stream_on_snd_una_update;
+		tcp->tx = tcp_stream_tx;
+	}
+
+	return 1;
+}
+
+static void netcat_start(void)
+{
+	struct tcp_stream *tcp;
+
+	memset(net_server_ethaddr, 0, 6);
+	tcp_stream_set_on_create_handler(tcp_stream_on_create);
+
+	if (!strchr(net_boot_file_name, ':')) {
+		listen = 1;
+		printf("Listening on port %s...\n", net_boot_file_name);
+
+		local_port = dectoul(net_boot_file_name, NULL);
+	} else {
+		listen = 0;
+		printf("Connecting to %s...\n", net_boot_file_name);
+
+		server_ip = string_to_ip(net_boot_file_name);
+		server_port = dectoul(strchr(net_boot_file_name, ':') + 1, NULL);
+
+		tcp = tcp_stream_connect(server_ip, server_port);
+		if (!tcp) {
+			printf("No free tcp streams\n");
+			net_set_state(NETLOOP_FAIL);
+			return;
+		}
+		tcp_stream_put(tcp);
+	}
+}
+
+void netcat_load_start(void)
+{
+	reading = 1;
+
+	return netcat_start();
+}
+
+void netcat_save_start(void)
+{
+	reading = 0;
+
+	return netcat_start();
+}