[PATCH] net: sandbox: support raw Ethernet with lwIP

James Hilliard <[email protected]>
Newsgroups org.u-boot-project.lists.u-boot
Message-ID <[email protected]>
ETH_SANDBOX_RAW is restricted to NET_LEGACY despite operating through
the common Ethernet driver API.

Make the localhost shim stack-neutral: define the ARP layout locally,
populate lwIP's per-device ipaddr environment variable, and retain the
legacy address state when that stack is selected.

Linux loopback can expose UDP checksum-offload seeds through raw sockets
without the metadata needed to validate them. Normalize these to the valid
IPv4 no-checksum form. Only update the dummy UDP bind for UDP output so
lwIP-generated ICMP packets cannot disturb it.

This enables both real-interface and localhost raw Ethernet with lwIP.

Signed-off-by: James Hilliard <[email protected]>
---
 arch/sandbox/cpu/eth-raw-os.c | 83 +++++++++++++++++++++++++++++--------------
 drivers/net/Kconfig           |  2 +-
 drivers/net/sandbox-raw.c     | 49 +++++++++++++++++--------
 include/net-legacy.h          |  6 ++++
 include/net-lwip.h            |  5 +++
 5 files changed, 103 insertions(+), 42 deletions(-)

diff --git a/arch/sandbox/cpu/eth-raw-os.c b/arch/sandbox/cpu/eth-raw-os.c
index 39ea3b3f012..c9c3a561a76 100644
--- a/arch/sandbox/cpu/eth-raw-os.c
+++ b/arch/sandbox/cpu/eth-raw-os.c
@@ -208,7 +208,6 @@ int sandbox_eth_raw_os_send(void *packet, int length,
 			    struct eth_sandbox_raw_priv *priv)
 {
 	int retval;
-	struct udphdr *udph = packet + sizeof(struct iphdr);
 
 	if (priv->sd < 0 || !priv->device)
 		return -EINVAL;
@@ -226,38 +225,50 @@ int sandbox_eth_raw_os_send(void *packet, int length,
 	 * stack from sending that ICMP message claiming that the port has no
 	 * bound socket.
 	 */
-	if (priv->local && (priv->local_bind_sd == -1 ||
-			    priv->local_bind_udp_port != udph->source)) {
+	if (priv->local && length >= sizeof(struct iphdr)) {
 		struct iphdr *iph = packet;
-		struct sockaddr_in addr;
+		unsigned int iphdr_len = iph->ihl * 4;
+		struct udphdr *udph;
 
-		if (priv->local_bind_sd != -1)
-			os_close(priv->local_bind_sd);
+		if (iph->protocol != IPPROTO_UDP ||
+		    iphdr_len < sizeof(*iph) ||
+		    length < iphdr_len + sizeof(*udph))
+			goto send;
 
-		/* A normal UDP socket is required to bind */
-		priv->local_bind_sd = socket(AF_INET, SOCK_DGRAM, 0);
-		if (priv->local_bind_sd < 0) {
-			printf("Failed to open bind sd: %d %s\n", errno,
-			       strerror(errno));
-			return -errno;
+		udph = packet + iphdr_len;
+		if (priv->local_bind_sd == -1 ||
+		    priv->local_bind_udp_port != udph->source) {
+			struct sockaddr_in addr = {};
+
+			if (priv->local_bind_sd != -1)
+				os_close(priv->local_bind_sd);
+
+			/* A normal UDP socket is required to bind */
+			priv->local_bind_sd = socket(AF_INET, SOCK_DGRAM, 0);
+			if (priv->local_bind_sd < 0) {
+				printf("Failed to open bind sd: %d %s\n", errno,
+				       strerror(errno));
+				return -errno;
+			}
+			priv->local_bind_udp_port = udph->source;
+
+			/**
+			 * Bind the UDP port that we intend to use as our source port
+			 * so that the kernel will not send an ICMP port unreachable
+			 * message to the server
+			 */
+			addr.sin_family = AF_INET;
+			addr.sin_port = udph->source;
+			addr.sin_addr.s_addr = iph->saddr;
+			retval = bind(priv->local_bind_sd,
+				      (struct sockaddr *)&addr, sizeof(addr));
+			if (retval < 0)
+				printf("Failed to bind: %d %s\n", errno,
+				       strerror(errno));
 		}
-		priv->local_bind_udp_port = udph->source;
-
-		/**
-		 * Bind the UDP port that we intend to use as our source port
-		 * so that the kernel will not send an ICMP port unreachable
-		 * message to the server
-		 */
-		addr.sin_family = AF_INET;
-		addr.sin_port = udph->source;
-		addr.sin_addr.s_addr = iph->saddr;
-		retval = bind(priv->local_bind_sd, (struct sockaddr *)&addr,
-			      sizeof(addr));
-		if (retval < 0)
-			printf("Failed to bind: %d %s\n", errno,
-			       strerror(errno));
 	}
 
+send:
 	retval = sendto(priv->sd, packet, length, 0,
 			(struct sockaddr *)priv->device,
 			sizeof(struct sockaddr_ll));
@@ -283,6 +294,24 @@ int sandbox_eth_raw_os_recv(void *packet, int *length,
 			  (socklen_t *)&saddr_size);
 	*length = 0;
 	if (retval >= 0) {
+		if (priv->local && retval >= sizeof(struct iphdr)) {
+			struct iphdr *iph = packet;
+			unsigned int iphdr_len = iph->ihl * 4;
+
+			if (iph->protocol == IPPROTO_UDP &&
+			    iphdr_len >= sizeof(*iph) &&
+			    retval >= iphdr_len + sizeof(struct udphdr)) {
+				struct udphdr *udph = packet + iphdr_len;
+
+				/*
+				 * Loopback packets can retain a checksum-offload seed
+				 * which a raw socket cannot validate without skb metadata.
+				 * An IPv4 UDP checksum of zero explicitly disables the
+				 * checksum, so present that portable form to U-Boot.
+				 */
+				udph->check = 0;
+			}
+		}
 		*length = retval;
 		return 0;
 	}
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index faa08e9ebba..c057504cf66 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -366,7 +366,7 @@ config ETH_SANDBOX
 
 config ETH_SANDBOX_RAW
 	depends on SANDBOX
-	depends on NET_LEGACY
+	depends on NET
 	default y
 	bool "Sandbox: Bridge to Linux Raw Sockets"
 	help
diff --git a/drivers/net/sandbox-raw.c b/drivers/net/sandbox-raw.c
index c3d40f0b59e..c57893c48ce 100644
--- a/drivers/net/sandbox-raw.c
+++ b/drivers/net/sandbox-raw.c
@@ -13,6 +13,21 @@
 #include <malloc.h>
 #include <net.h>
 
+#define ARP_PROTOCOL_ADDR_LEN	4
+#define ARP_OP_REPLY		2
+
+struct sb_arp_hdr {
+	u16 hrd;
+	u16 pro;
+	u8 hln;
+	u8 pln;
+	u16 op;
+	u8 sha[ARP_HLEN];
+	struct in_addr spa;
+	u8 tha[ARP_HLEN];
+	struct in_addr tpa;
+} __packed;
+
 static int reply_arp;
 static struct in_addr arp_ip;
 
@@ -20,6 +35,8 @@ static int sb_eth_raw_start(struct udevice *dev)
 {
 	struct eth_sandbox_raw_priv *priv = dev_get_priv(dev);
 	struct eth_pdata *pdata = dev_get_plat(dev);
+	char ipaddr[sizeof("ipaddr99")];
+	int idx = dev_seq(dev);
 	int ret;
 
 	debug("eth_sandbox_raw: Start\n");
@@ -27,9 +44,13 @@ static int sb_eth_raw_start(struct udevice *dev)
 	ret = sandbox_eth_raw_os_start(priv, pdata->enetaddr);
 	if (priv->local) {
 		env_set("ipaddr", "127.0.0.1");
+		if (IS_ENABLED(CONFIG_NET_LWIP) && idx > 0 && idx <= 99) {
+			snprintf(ipaddr, sizeof(ipaddr), "ipaddr%d", idx);
+			env_set(ipaddr, "127.0.0.1");
+		}
 		env_set("serverip", "127.0.0.1");
 		net_ip = string_to_ip("127.0.0.1");
-		net_server_ip = net_ip;
+		net_set_server_ip(net_ip);
 	}
 	return ret;
 }
@@ -44,13 +65,13 @@ static int sb_eth_raw_send(struct udevice *dev, void *packet, int length)
 		struct ethernet_hdr *eth = packet;
 
 		if (ntohs(eth->et_protlen) == PROT_ARP) {
-			struct arp_hdr *arp = packet + ETHER_HDR_SIZE;
+			struct sb_arp_hdr *arp = packet + ETHER_HDR_SIZE;
 
 			/**
 			 * localhost works on a higher-level API in Linux than
 			 * ARP packets, so fake it
 			 */
-			arp_ip = net_read_ip(&arp->ar_tpa);
+			memcpy(&arp_ip, &arp->tpa, sizeof(arp_ip));
 			reply_arp = 1;
 			return 0;
 		}
@@ -68,7 +89,7 @@ static int sb_eth_raw_recv(struct udevice *dev, int flags, uchar **packetp)
 	int length;
 
 	if (reply_arp) {
-		struct arp_hdr *arp = (void *)net_rx_packets[0] +
+		struct sb_arp_hdr *arp = (void *)net_rx_packets[0] +
 			ETHER_HDR_SIZE;
 
 		/*
@@ -80,18 +101,18 @@ static int sb_eth_raw_recv(struct udevice *dev, int flags, uchar **packetp)
 		 * to get a response. For this reason we fake the response to
 		 * make the u-boot network stack happy.
 		 */
-		arp->ar_hrd = htons(ARP_ETHER);
-		arp->ar_pro = htons(PROT_IP);
-		arp->ar_hln = ARP_HLEN;
-		arp->ar_pln = ARP_PLEN;
-		arp->ar_op = htons(ARPOP_REPLY);
+		arp->hrd = htons(ARP_ETHER);
+		arp->pro = htons(PROT_IP);
+		arp->hln = ARP_HLEN;
+		arp->pln = ARP_PROTOCOL_ADDR_LEN;
+		arp->op = htons(ARP_OP_REPLY);
 		/* Any non-zero MAC address will work */
-		memset(&arp->ar_sha, 0x01, ARP_HLEN);
+		memset(arp->sha, 0x01, ARP_HLEN);
 		/* Use whatever IP we were looking for (always 127.0.0.1?) */
-		net_write_ip(&arp->ar_spa, arp_ip);
-		memcpy(&arp->ar_tha, pdata->enetaddr, ARP_HLEN);
-		net_write_ip(&arp->ar_tpa, net_ip);
-		length = ARP_HDR_SIZE;
+		memcpy(&arp->spa, &arp_ip, sizeof(arp->spa));
+		memcpy(arp->tha, pdata->enetaddr, ARP_HLEN);
+		memcpy(&arp->tpa, &net_ip, sizeof(arp->tpa));
+		length = sizeof(*arp);
 	} else {
 		/* If local, the Ethernet header won't be included; skip it */
 		uchar *pktptr = priv->local ?
diff --git a/include/net-legacy.h b/include/net-legacy.h
index d3b122c9062..2737b683cb1 100644
--- a/include/net-legacy.h
+++ b/include/net-legacy.h
@@ -288,6 +288,12 @@ extern char	net_root_path[CONFIG_BOOTP_MAX_ROOT_PATH_LEN];	/* Our root path */
 extern u8		net_ethaddr[ARP_HLEN];		/* Our ethernet address */
 extern u8		net_server_ethaddr[ARP_HLEN];	/* Boot server enet address */
 extern struct in_addr	net_server_ip;	/* Server IP addr (0 = unknown) */
+
+static inline void net_set_server_ip(struct in_addr ip)
+{
+	net_server_ip = ip;
+}
+
 extern uchar		*net_tx_packet;		/* THE transmit packet */
 extern uchar		*net_rx_packet;		/* Current receive packet */
 extern int		net_rx_packet_len;	/* Current rx packet length */
diff --git a/include/net-lwip.h b/include/net-lwip.h
index 8e59a2299e0..9b7755e335e 100644
--- a/include/net-lwip.h
+++ b/include/net-lwip.h
@@ -26,6 +26,11 @@ enum proto_t {
 	TFTPGET
 };
 
+static inline void net_set_server_ip(struct in_addr ip)
+{
+	/* lwIP reads serverip from the environment when starting a command. */
+}
+
 static inline int eth_is_on_demand_init(void)
 {
 	return 1;

---
base-commit: 964ad5b5c91b7be56e443e899d7f873e6aa8c9fc
change-id: 20260825-submit-lwip-sandbox-raw-v1-749315041b86

Best regards,
--  
James Hilliard <[email protected]>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.