Netdev List
 help / color / mirror / Atom feed
* [PATCH] [RFC] net-ipv6: ipv4 emulation support
@ 2026-07-13  8:16 Maciej Żenczykowski
  2026-07-13  9:05 ` Maciej Żenczykowski
  0 siblings, 1 reply; 2+ messages in thread
From: Maciej Żenczykowski @ 2026-07-13  8:16 UTC (permalink / raw)
  To: Maciej Żenczykowski
  Cc: Linux Network Development Mailing List,
	Fernando Fernandez Mancera, Maciej Żenczykowski

Change-Id: Ibab1a59f4730f3fd0a7d14fffc5100a02a170ac6
---
 gconfigs/Gconfig.net-configs.ipv6 |   2 +
 include/linux/ipv6.h              |   6 +-
 include/linux/socket.h            |   5 +-
 include/net/compat.h              |   2 +-
 net/compat.c                      |   6 +-
 net/core/iovec.c                  |   4 +-
 net/ipv6/Kconfig                  |  19 +++
 net/ipv6/af_inet6.c               |   5 +
 net/ipv6/ipv6_sockglue.c          |   8 +-
 net/ipv6/sysctl_net_ipv6.c        |  26 ++++
 net/socket.c                      | 211 +++++++++++++++++++++++++++---
 11 files changed, 260 insertions(+), 34 deletions(-)
 create mode 100644 gconfigs/Gconfig.net-configs.ipv6

diff --git a/gconfigs/Gconfig.net-configs.ipv6 b/gconfigs/Gconfig.net-configs.ipv6
new file mode 100644
index 000000000000..9eb5694bae8e
--- /dev/null
+++ b/gconfigs/Gconfig.net-configs.ipv6
@@ -0,0 +1,2 @@
+CONFIG_IPV6_IPV4_EMULATE=y
+CONFIG_IPV6_IPV4_EMULATE_SYSCTL_DEFAULT=y
diff --git a/include/linux/ipv6.h b/include/linux/ipv6.h
index ff560537dd61..8bc5306094b5 100644
--- a/include/linux/ipv6.h
+++ b/include/linux/ipv6.h
@@ -157,11 +157,13 @@ struct ipv6_pinfo {
 #if defined(__BIG_ENDIAN_BITFIELD)
 	/* Packed in 16bits. */
 	__s16			mcast_hops:9;
-	__u16			__unused_2:6,
+	__u16			__unused_2:5,
+				ipv4emu:1,
 				mc_loop:1;
 #else
 	__u16			mc_loop:1,
-				__unused_2:6;
+				ipv4emu:1,
+				__unused_2:5;
 	__s16			mcast_hops:9;
 #endif
 	int			ucast_oif;
diff --git a/include/linux/socket.h b/include/linux/socket.h
index bb9b83640070..4ae959a3443b 100644
--- a/include/linux/socket.h
+++ b/include/linux/socket.h
@@ -8,6 +8,7 @@
 #include <linux/types.h>		/* pid_t			*/
 #include <linux/compiler.h>		/* __user			*/
 #include <uapi/linux/socket.h>
+struct sock;				/* from net/sock.h		*/
 
 struct pid;
 struct cred;
@@ -312,8 +313,8 @@ extern int csum_partial_copy_fromiovecend(unsigned char *kdata,
 extern unsigned long iov_pages(const struct iovec *iov, int offset,
 			       unsigned long nr_segs);
 
-extern int verify_iovec(struct msghdr *m, struct iovec *iov, struct sockaddr_storage *address, int mode);
-extern int move_addr_to_kernel(void __user *uaddr, int ulen, struct sockaddr_storage *kaddr);
+extern int verify_iovec(struct sock *sk, struct msghdr *m, struct iovec *iov, struct sockaddr_storage *address, int mode);
+extern int move_addr_to_kernel(struct sock *sk, void __user *uaddr, int *ulen, struct sockaddr_storage *kaddr);
 extern int put_cmsg(struct msghdr*, int level, int type, int len, void *data);
 
 struct timespec;
diff --git a/include/net/compat.h b/include/net/compat.h
index 3b603b199c01..b9ee7aa5a31f 100644
--- a/include/net/compat.h
+++ b/include/net/compat.h
@@ -41,7 +41,7 @@ int compat_sock_get_timestampns(struct sock *, struct timespec __user *);
 #endif /* defined(CONFIG_COMPAT) */
 
 int get_compat_msghdr(struct msghdr *, struct compat_msghdr __user *);
-int verify_compat_iovec(struct msghdr *, struct iovec *,
+int verify_compat_iovec(struct sock *, struct msghdr *, struct iovec *,
 			struct sockaddr_storage *, int);
 asmlinkage long compat_sys_sendmsg(int, struct compat_msghdr __user *,
 				   unsigned int);
diff --git a/net/compat.c b/net/compat.c
index bc8aeefddf3f..f66bd6552204 100644
--- a/net/compat.c
+++ b/net/compat.c
@@ -80,15 +80,15 @@ int get_compat_msghdr(struct msghdr *kmsg, struct compat_msghdr __user *umsg)
 }
 
 /* I've named the args so it is easy to tell whose space the pointers are in. */
-int verify_compat_iovec(struct msghdr *kern_msg, struct iovec *kern_iov,
+int verify_compat_iovec(struct sock *sk, struct msghdr *kern_msg, struct iovec *kern_iov,
 		   struct sockaddr_storage *kern_address, int mode)
 {
 	int tot_len;
 
 	if (kern_msg->msg_name && kern_msg->msg_namelen) {
 		if (mode == VERIFY_READ) {
-			int err = move_addr_to_kernel(kern_msg->msg_name,
-						      kern_msg->msg_namelen,
+			int err = move_addr_to_kernel(sk, kern_msg->msg_name,
+						      &kern_msg->msg_namelen,
 						      kern_address);
 			if (err < 0)
 				return err;
diff --git a/net/core/iovec.c b/net/core/iovec.c
index e1ec45ab1e63..a7a393cae2a7 100644
--- a/net/core/iovec.c
+++ b/net/core/iovec.c
@@ -35,7 +35,7 @@
  *	in any case.
  */
 
-int verify_iovec(struct msghdr *m, struct iovec *iov, struct sockaddr_storage *address, int mode)
+int verify_iovec(struct sock *sk, struct msghdr *m, struct iovec *iov, struct sockaddr_storage *address, int mode)
 {
 	int size, ct, err;
 
@@ -43,7 +43,7 @@ int verify_iovec(struct msghdr *m, struct iovec *iov, struct sockaddr_storage *a
 		if (mode == VERIFY_READ) {
 			void __user *namep;
 			namep = (void __user __force *) m->msg_name;
-			err = move_addr_to_kernel(namep, m->msg_namelen,
+			err = move_addr_to_kernel(sk, namep, &m->msg_namelen,
 						  address);
 			if (err < 0)
 				return err;
diff --git a/net/ipv6/Kconfig b/net/ipv6/Kconfig
index 438a73aa777c..69924a6c2257 100644
--- a/net/ipv6/Kconfig
+++ b/net/ipv6/Kconfig
@@ -21,6 +21,25 @@ menuconfig IPV6
 
 if IPV6
 
+config IPV6_IPV4_EMULATE
+	bool "IPv6: IPv4 socket interface emulation support"
+	default n
+	depends on IPV6=y
+	---help---
+	  Whether to include support code which allows using a kernel IPv6
+	  socket from userspace as if it was an IPv4 socket.
+	
+	  ie. this is a userspace-kernel IP address translation shim layer.
+
+config IPV6_IPV4_EMULATE_SYSCTL_DEFAULT
+	bool "IPv6: IPv4 socket emulation - default sysctl setting"
+	default n
+	depends on IPV6_IPV4_EMULATE
+	---help---
+	  Default value for /proc/sys/net/ipv6/ipv4_emulation
+	  The content of this sysctl file determines whether userspace asking
+	  for IPv4 sockets gets IPv6 sockets with IPv4 emulation turned on.
+
 config IPV6_ROUTER_PREF
 	bool "IPv6: Router Preference (RFC 4191) support"
 	---help---
diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c
index e8c4400f23e9..4f08abdf06e0 100644
--- a/net/ipv6/af_inet6.c
+++ b/net/ipv6/af_inet6.c
@@ -476,6 +476,11 @@ int inet6_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
 	struct sock *sk = sock->sk;
 	struct net *net = sock_net(sk);
 
+#if defined(CONFIG_IPV6_IPV4_EMULATE)
+	if (inet6_sk(sk)->ipv4emu)
+		return inet_ioctl(sock, cmd, arg);
+#endif
+
 	switch (cmd) {
 	case SIOCGSTAMP:
 		return sock_get_timestamp(sk, (struct timeval __user *)arg);
diff --git a/net/ipv6/ipv6_sockglue.c b/net/ipv6/ipv6_sockglue.c
index e1a9583bb419..2e97c6d55b62 100644
--- a/net/ipv6/ipv6_sockglue.c
+++ b/net/ipv6/ipv6_sockglue.c
@@ -857,7 +857,7 @@ int ipv6_setsockopt(struct sock *sk, int level, int optname,
 	if (level == SOL_IP && sk->sk_type != SOCK_RAW)
 		return udp_prot.setsockopt(sk, level, optname, optval, optlen);
 
-	if (level != SOL_IPV6)
+	if ((level != SOL_IPV6) || (inet6_sk(sk)->ipv4emu))
 		return -ENOPROTOOPT;
 
 	err = do_ipv6_setsockopt(sk, level, optname, optval, optlen);
@@ -888,7 +888,7 @@ int compat_ipv6_setsockopt(struct sock *sk, int level, int optname,
 		return udp_prot.setsockopt(sk, level, optname, optval, optlen);
 	}
 
-	if (level != SOL_IPV6)
+	if ((level != SOL_IPV6) || (inet6_sk(sk)->ipv4emu))
 		return -ENOPROTOOPT;
 
 	if (optname >= MCAST_JOIN_GROUP && optname <= MCAST_MSFILTER)
@@ -1297,7 +1297,7 @@ int ipv6_getsockopt(struct sock *sk, int level, int optname,
 	if (level == SOL_IP && sk->sk_type != SOCK_RAW)
 		return udp_prot.getsockopt(sk, level, optname, optval, optlen);
 
-	if (level != SOL_IPV6)
+	if ((level != SOL_IPV6) || (inet6_sk(sk)->ipv4emu))
 		return -ENOPROTOOPT;
 
 	err = do_ipv6_getsockopt(sk, level, optname, optval, optlen, 0);
@@ -1334,7 +1334,7 @@ int compat_ipv6_getsockopt(struct sock *sk, int level, int optname,
 		return udp_prot.getsockopt(sk, level, optname, optval, optlen);
 	}
 
-	if (level != SOL_IPV6)
+	if ((level != SOL_IPV6) || (inet6_sk(sk)->ipv4emu))
 		return -ENOPROTOOPT;
 
 	if (optname == MCAST_MSFILTER)
diff --git a/net/ipv6/sysctl_net_ipv6.c b/net/ipv6/sysctl_net_ipv6.c
index c5c10fafcfe2..0ab6a8d5aa57 100644
--- a/net/ipv6/sysctl_net_ipv6.c
+++ b/net/ipv6/sysctl_net_ipv6.c
@@ -6,6 +6,7 @@
  */
 
 #include <linux/mm.h>
+#include <linux/module.h>
 #include <linux/sysctl.h>
 #include <linux/in6.h>
 #include <linux/ipv6.h>
@@ -18,6 +19,20 @@
 
 static int one = 1;
 
+#if defined(CONFIG_IPV6_IPV4_EMULATE)
+static int zero;
+
+# if defined(CONFIG_IPV6_IPV4_EMULATE_SYSCTL_DEFAULT)
+bool sysctl_ipv4_emulation = true;
+# else
+bool sysctl_ipv4_emulation = false;
+# endif
+
+module_param_named(ipv4_emulation, sysctl_ipv4_emulation, bool, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(ipv4_emulation,
+		 "Auto promote PF_INET sockets into PF_INET6 sockets, with IPv4 API emulation.");
+#endif
+
 static struct ctl_table ipv6_table_template[] = {
 	{
 		.procname	= "bindv6only",
@@ -73,6 +88,17 @@ static struct ctl_table ipv6_rotable[] = {
 		.proc_handler	= proc_dointvec_minmax,
 		.extra1		= &one
 	},
+#if defined(CONFIG_IPV6_IPV4_EMULATE)
+	{
+		.procname	= "ipv4_emulation",
+		.data		= &sysctl_ipv4_emulation,
+		.maxlen		= sizeof(bool),
+		.mode		= 0644,
+		.proc_handler	= proc_dointvec_minmax,
+		.extra1		= &zero,
+		.extra2		= &one,
+	},
+#endif
 	{ }
 };
 
diff --git a/net/socket.c b/net/socket.c
index fe20c319a0bb..e7d2f5281ff3 100644
--- a/net/socket.c
+++ b/net/socket.c
@@ -101,6 +101,7 @@
 #include <linux/netfilter.h>
 
 #include <linux/if_tun.h>
+#include <linux/ipv6.h>
 #include <linux/ipv6_route.h>
 #include <linux/route.h>
 #include <linux/sockios.h>
@@ -178,30 +179,171 @@ static DEFINE_PER_CPU(int, sockets_in_use);
  * divide and look after the messy bits.
  */
 
+static int ipv6_promote_ipv4_address(struct sock *sk,
+	struct sockaddr_storage * kaddr, int * addr_len)
+{
+#if defined(CONFIG_IPV6_IPV4_EMULATE)
+	if (!sk) {
+		pr_warning("ipv6_promote_ipv4_address: no sk\n");
+		return 0;
+	}
+
+	if (sk->sk_family != PF_INET6) {
+		//pr_warning("ipv6_promote_ipv4_address: sk_family == %d\n", sk->sk_family);
+		return 0;
+	}
+
+	if (!inet6_sk(sk)->ipv4emu) {
+		//pr_warning("ipv6_promote_ipv4_address: not ipv4emu\n");
+		return 0;
+	}
+
+	if (kaddr->ss_family == AF_UNSPEC) {
+		//pr_warning("ipv6_promote_ipv4_address: unspec\n");
+		return 0;
+	}
+
+	if (kaddr->ss_family != AF_INET) {
+		pr_warning("ipv6_promote_ipv4_address: addr is %d\n", kaddr->ss_family);
+		return -EAFNOSUPPORT;
+	}
+
+	if (*addr_len < sizeof(struct sockaddr_in)) {
+		pr_warning("ipv6_promote_ipv4_address: addr_len == %d\n", *addr_len);
+		return -EINVAL;
+	}
+
+	{
+		const struct sockaddr_in * kaddr4 = (const struct sockaddr_in *)kaddr;
+		struct sockaddr_in6 * kaddr6 = (struct sockaddr_in6 *)kaddr;
+		__be16 port = kaddr4->sin_port;
+		__be32 ipv4 = kaddr4->sin_addr.s_addr;
+
+		//*addr_len = sizeof(struct sockaddr_in6);
+		*addr_len = 24; //SIN6_LEN_RFC2133; /* 24 = 2+2+4+16, ie. without scope id */
+// Why not fill in scope_id with 0 ?
+// One way to avoid the magic 24 constant is to use offsetof(struct sockaddr_in6, scope_id)
+		kaddr6->sin6_family = AF_INET6;
+		kaddr6->sin6_port = port;
+		kaddr6->sin6_flowinfo = htonl(0);
+		kaddr6->sin6_addr.s6_addr32[0] = htonl(0);
+		kaddr6->sin6_addr.s6_addr32[1] = htonl(0);
+		kaddr6->sin6_addr.s6_addr32[2] = ntohl(ipv4) ? htonl(0xFFFF) : htonl(0);
+		kaddr6->sin6_addr.s6_addr32[3] = ipv4;
+		kaddr6->sin6_scope_id = 0;
+
+		pr_debug("ipv6_promote_ipv4_address: ok [%08X:%d]\n", ntohl(ipv4), ntohs(port));
+	}
+#endif
+	return 0;
+}
+
+static int ipv6_demote_ipv6_address(struct sock *sk, struct sockaddr_storage * kaddr, int * addr_len) {
+#if defined(CONFIG_IPV6_IPV4_EMULATE)
+	if (!sk) {
+		pr_warning("ipv6_demote_ipv6_address: no sk\n");
+		return 0;
+	}
+
+	if (sk->sk_family != PF_INET6) {
+		//pr_warning("ipv6_demote_ipv6_address: sk_family == %d\n", sk->sk_family);
+		return 0;
+	}
+
+	if (!inet6_sk(sk)->ipv4emu) {
+		//pr_warning("ipv6_demote_ipv6_address: not ipv4emu\n");
+		return 0;
+	}
+
+	if (kaddr->ss_family != AF_INET6) {
+		pr_warning("ipv6_demote_ipv6_address: addr is %d\n", kaddr->ss_family);
+		return -EAFNOSUPPORT;
+	}
+
+	if (*addr_len < sizeof(struct sockaddr_in6)) {
+		pr_warning("ipv6_demote_ipv6_address: addr_len == %d\n", *addr_len);
+		return -EINVAL;
+	}
+
+	{
+		struct sockaddr_in6 const * kaddr6 = (struct sockaddr_in6 const *)kaddr;
+		struct sockaddr_in * kaddr4 = (struct sockaddr_in *)kaddr;
+		__be16 port = kaddr6->sin6_port;
+		__be32 ipv6a = kaddr6->sin6_addr.s6_addr32[0];
+		__be32 ipv6b = kaddr6->sin6_addr.s6_addr32[1];
+		__be32 ipv6c = kaddr6->sin6_addr.s6_addr32[2];
+		__be32 ipv4 = kaddr6->sin6_addr.s6_addr32[3];
+
+// please use ipv6_addr_v4mapped()
+
+		if (ipv6a) {
+			pr_warning("ipv6_demote_ipv6_address: not mapped [1]\n");
+			return -EINVAL;
+		}
+
+		if (ipv6b) {
+			pr_warning("ipv6_demote_ipv6_address: not mapped [2]\n");
+			return -EINVAL;
+		}
+
+		if ((ipv6c != htonl(0)) && (ipv6c != htonl(0xFFFF))) {
+			pr_warning("ipv6_demote_ipv6_address: not mapped [3]\n");
+			return -EINVAL;
+		}
+
+		if ((ipv6c == htonl(0)) && (ipv4 != htonl(0))) {
+			pr_warning("ipv6_demote_ipv6_address: not mapped [4]\n");
+			return -EINVAL;
+		}
+
+// Really avoid those magic values...
+// Since I doubt this is performance critical, just do the whole struct clear at the beginning:
+// memset(uaddr4, 0, sizeof(*uaddr4));
+
+		*addr_len = sizeof(struct sockaddr_in); // 2 + 2 + 4 + 8 padding
+		kaddr4->sin_family = AF_INET;
+		kaddr4->sin_port = port;
+		kaddr4->sin_addr.s_addr = ipv4;
+		memset(kaddr4->sin_zero, 0, 8);
+
+		pr_debug("ipv6_demote_ipv6_address: ok [%08X:%d]\n", ntohl(ipv4), ntohs(port));
+	}
+#endif
+	return 0;
+}
+
 /**
  *	move_addr_to_kernel	-	copy a socket address into kernel space
+ *	@sk: socket which is receiving the address (or NULL)
  *	@uaddr: Address in user space
  *	@kaddr: Address in kernel space
- *	@ulen: Length in user space
+ *	@ulen: pointer to user space length
  *
  *	The address is copied into kernel space. If the provided address is
  *	too long an error code of -EINVAL is returned. If the copy gives
  *	invalid addresses -EFAULT is returned. On a success 0 is returned.
  */
 
-int move_addr_to_kernel(void __user *uaddr, int ulen, struct sockaddr_storage *kaddr)
+int move_addr_to_kernel(struct sock *sk, void __user *uaddr, int *ulen,
+			struct sockaddr_storage *kaddr)
 {
-	if (ulen < 0 || ulen > sizeof(struct sockaddr_storage))
+	int err;
+
+	if (*ulen < 0 || *ulen > sizeof(struct sockaddr_storage))
 		return -EINVAL;
-	if (ulen == 0)
+	if (*ulen == 0)
 		return 0;
-	if (copy_from_user(kaddr, uaddr, ulen))
+	if (copy_from_user(kaddr, uaddr, *ulen))
 		return -EFAULT;
-	return audit_sockaddr(ulen, kaddr);
+	err = audit_sockaddr(*ulen, kaddr);
+	if (err)
+		return err;
+	return ipv6_promote_ipv4_address(sk, kaddr, ulen);
 }
 
 /**
  *	move_addr_to_user	-	copy an address to user space
+ *	@sk: socket which is generating the address (or NULL)
  *	@kaddr: kernel space address
  *	@klen: length of address in kernel
  *	@uaddr: user space address
@@ -217,7 +359,7 @@ int move_addr_to_kernel(void __user *uaddr, int ulen, struct sockaddr_storage *k
  *	specified. Zero is returned for a success.
  */
 
-static int move_addr_to_user(struct sockaddr_storage *kaddr, int klen,
+static int move_addr_to_user(struct sock *sk, struct sockaddr_storage *kaddr, int klen,
 			     void __user *uaddr, int __user *ulen)
 {
 	int err;
@@ -225,6 +367,9 @@ static int move_addr_to_user(struct sockaddr_storage *kaddr, int klen,
 
 	BUG_ON(klen > sizeof(struct sockaddr_storage));
 	err = get_user(len, ulen);
+	if (err)
+		return err;
+	err = ipv6_demote_ipv6_address(sk, kaddr, &klen);
 	if (err)
 		return err;
 	if (len > klen)
@@ -1249,12 +1394,19 @@ call_kill:
 }
 EXPORT_SYMBOL(sock_wake_async);
 
+#if defined(CONFIG_IPV6_IPV4_EMULATE)
+extern bool sysctl_ipv4_emulation;
+#endif
+
 int __sock_create(struct net *net, int family, int type, int protocol,
 			 struct socket **res, int kern)
 {
 	int err;
 	struct socket *sock;
 	const struct net_proto_family *pf;
+#if defined(CONFIG_IPV6_IPV4_EMULATE)
+	bool ipv4emu = false;
+#endif
 
 	/*
 	 *      Check protocol is in range
@@ -1283,6 +1435,13 @@ int __sock_create(struct net *net, int family, int type, int protocol,
 	if (err)
 		return err;
 
+#if defined(CONFIG_IPV6_IPV4_EMULATE)
+	if (sysctl_ipv4_emulation && !kern && family == PF_INET) {
+		family = PF_INET6;
+		ipv4emu = true;
+	}
+#endif
+
 	/*
 	 *	Allocate the socket and allow the family to set things up. if
 	 *	the protocol is 0, the family is instructed to select an appropriate
@@ -1328,6 +1487,14 @@ int __sock_create(struct net *net, int family, int type, int protocol,
 	if (err < 0)
 		goto out_module_put;
 
+#if defined(CONFIG_IPV6_IPV4_EMULATE)
+	if (ipv4emu) {
+		inet6_sk(sock->sk)->ipv4emu = 1;
+		/* we need to clear ipv6only, since it could have defaulted to true via sysctl */
+		sock->sk->sk_ipv6only = 0;
+	}
+#endif
+
 	/*
 	 * Now to bump the refcnt of the [loadable] module that owns this
 	 * socket at sock_release time we decrement its refcnt.
@@ -1532,7 +1699,7 @@ SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen)
 
 	sock = sockfd_lookup_light(fd, &err, &fput_needed);
 	if (sock) {
-		err = move_addr_to_kernel(umyaddr, addrlen, &address);
+		err = move_addr_to_kernel(sock->sk, umyaddr, &addrlen, &address);
 		if (err >= 0) {
 			err = security_socket_bind(sock,
 						   (struct sockaddr *)&address,
@@ -1646,7 +1813,7 @@ SYSCALL_DEFINE4(accept4, int, fd, struct sockaddr __user *, upeer_sockaddr,
 			err = -ECONNABORTED;
 			goto out_fd;
 		}
-		err = move_addr_to_user(&address,
+		err = move_addr_to_user(newsock->sk, &address,
 					len, upeer_sockaddr, upeer_addrlen);
 		if (err < 0)
 			goto out_fd;
@@ -1695,7 +1862,7 @@ SYSCALL_DEFINE3(connect, int, fd, struct sockaddr __user *, uservaddr,
 	sock = sockfd_lookup_light(fd, &err, &fput_needed);
 	if (!sock)
 		goto out;
-	err = move_addr_to_kernel(uservaddr, addrlen, &address);
+	err = move_addr_to_kernel(sock->sk, uservaddr, &addrlen, &address);
 	if (err < 0)
 		goto out_put;
 
@@ -1735,7 +1902,7 @@ SYSCALL_DEFINE3(getsockname, int, fd, struct sockaddr __user *, usockaddr,
 	err = sock->ops->getname(sock, (struct sockaddr *)&address, &len, 0);
 	if (err)
 		goto out_put;
-	err = move_addr_to_user(&address, len, usockaddr, usockaddr_len);
+	err = move_addr_to_user(sock->sk, &address, len, usockaddr, usockaddr_len);
 
 out_put:
 	fput_light(sock->file, fput_needed);
@@ -1767,7 +1934,7 @@ SYSCALL_DEFINE3(getpeername, int, fd, struct sockaddr __user *, usockaddr,
 		    sock->ops->getname(sock, (struct sockaddr *)&address, &len,
 				       1);
 		if (!err)
-			err = move_addr_to_user(&address, len, usockaddr,
+			err = move_addr_to_user(sock->sk, &address, len, usockaddr,
 						usockaddr_len);
 		fput_light(sock->file, fput_needed);
 	}
@@ -1806,7 +1973,7 @@ SYSCALL_DEFINE6(sendto, int, fd, void __user *, buff, size_t, len,
 	msg.msg_controllen = 0;
 	msg.msg_namelen = 0;
 	if (addr) {
-		err = move_addr_to_kernel(addr, addr_len, &address);
+		err = move_addr_to_kernel(sock->sk, addr, &addr_len, &address);
 		if (err < 0)
 			goto out_put;
 		msg.msg_name = (struct sockaddr *)&address;
@@ -1871,7 +2038,7 @@ SYSCALL_DEFINE6(recvfrom, int, fd, void __user *, ubuf, size_t, size,
 	err = sock_recvmsg(sock, &msg, size, flags);
 
 	if (err >= 0 && addr != NULL) {
-		err2 = move_addr_to_user(&address,
+		err2 = move_addr_to_user(sock->sk, &address,
 					 msg.msg_namelen, addr, addr_len);
 		if (err2 < 0)
 			err = err2;
@@ -2042,9 +2209,9 @@ static int ___sys_sendmsg(struct socket *sock, struct msghdr __user *msg,
 
 	/* This will also move the address data into kernel space */
 	if (MSG_CMSG_COMPAT & flags) {
-		err = verify_compat_iovec(msg_sys, iov, &address, VERIFY_READ);
+		err = verify_compat_iovec(sock->sk, msg_sys, iov, &address, VERIFY_READ);
 	} else
-		err = verify_iovec(msg_sys, iov, &address, VERIFY_READ);
+		err = verify_iovec(sock->sk, msg_sys, iov, &address, VERIFY_READ);
 	if (err < 0)
 		goto out_freeiov;
 	total_len = err;
@@ -2258,9 +2425,9 @@ static int ___sys_recvmsg(struct socket *sock, struct msghdr __user *msg,
 	uaddr = (__force void __user *)msg_sys->msg_name;
 	uaddr_len = COMPAT_NAMELEN(msg);
 	if (MSG_CMSG_COMPAT & flags)
-		err = verify_compat_iovec(msg_sys, iov, &addr, VERIFY_WRITE);
+		err = verify_compat_iovec(sock->sk, msg_sys, iov, &addr, VERIFY_WRITE);
 	else
-		err = verify_iovec(msg_sys, iov, &addr, VERIFY_WRITE);
+		err = verify_iovec(sock->sk, msg_sys, iov, &addr, VERIFY_WRITE);
 	if (err < 0)
 		goto out_freeiov;
 	total_len = err;
@@ -2280,7 +2447,7 @@ static int ___sys_recvmsg(struct socket *sock, struct msghdr __user *msg,
 	len = err;
 
 	if (uaddr != NULL) {
-		err = move_addr_to_user(&addr,
+		err = move_addr_to_user(sock->sk, &addr,
 					msg_sys->msg_namelen, uaddr,
 					uaddr_len);
 		if (err < 0)
@@ -3159,7 +3326,11 @@ static int routing_ioctl(struct net *net, struct socket *sock,
 	u32 rtdev;
 	mm_segment_t old_fs = get_fs();
 
-	if (sock && sock->sk && sock->sk->sk_family == AF_INET6) { /* ipv6 */
+	if (sock && sock->sk && sock->sk->sk_family == AF_INET6
+#if defined(CONFIG_IPV6_IPV4_EMULATE)
+	    && !inet6_sk(sock->sk)->ipv4emu
+#endif
+	   ) { /* ipv6 */
 		struct in6_rtmsg32 __user *ur6 = argp;
 		ret = copy_from_user(&r6.rtmsg_dst, &(ur6->rtmsg_dst),
 			3 * sizeof(struct in6_addr));
-- 
2.55.0.795.g602f6c329a-goog


^ permalink raw reply related	[flat|nested] 2+ messages in thread

* Re: [PATCH] [RFC] net-ipv6: ipv4 emulation support
  2026-07-13  8:16 [PATCH] [RFC] net-ipv6: ipv4 emulation support Maciej Żenczykowski
@ 2026-07-13  9:05 ` Maciej Żenczykowski
  0 siblings, 0 replies; 2+ messages in thread
From: Maciej Żenczykowski @ 2026-07-13  9:05 UTC (permalink / raw)
  To: Maciej Żenczykowski
  Cc: Linux Network Development Mailing List,
	Fernando Fernandez Mancera

Note that this is absolutely ancient - it's a patch from March 2012
against 3.18 ;-)
But maybe something like this could be pursued?
From what I (vaguely) remember I was able to boot my linux workstation
to a working GUI with this.

On Mon, Jul 13, 2026 at 10:16 AM Maciej Żenczykowski <maze@google.com> wrote:
>
> Change-Id: Ibab1a59f4730f3fd0a7d14fffc5100a02a170ac6
> ---
>  gconfigs/Gconfig.net-configs.ipv6 |   2 +
>  include/linux/ipv6.h              |   6 +-
>  include/linux/socket.h            |   5 +-
>  include/net/compat.h              |   2 +-
>  net/compat.c                      |   6 +-
>  net/core/iovec.c                  |   4 +-
>  net/ipv6/Kconfig                  |  19 +++
>  net/ipv6/af_inet6.c               |   5 +
>  net/ipv6/ipv6_sockglue.c          |   8 +-
>  net/ipv6/sysctl_net_ipv6.c        |  26 ++++
>  net/socket.c                      | 211 +++++++++++++++++++++++++++---
>  11 files changed, 260 insertions(+), 34 deletions(-)
>  create mode 100644 gconfigs/Gconfig.net-configs.ipv6
>
> diff --git a/gconfigs/Gconfig.net-configs.ipv6 b/gconfigs/Gconfig.net-configs.ipv6
> new file mode 100644
> index 000000000000..9eb5694bae8e
> --- /dev/null
> +++ b/gconfigs/Gconfig.net-configs.ipv6
> @@ -0,0 +1,2 @@
> +CONFIG_IPV6_IPV4_EMULATE=y
> +CONFIG_IPV6_IPV4_EMULATE_SYSCTL_DEFAULT=y
> diff --git a/include/linux/ipv6.h b/include/linux/ipv6.h
> index ff560537dd61..8bc5306094b5 100644
> --- a/include/linux/ipv6.h
> +++ b/include/linux/ipv6.h
> @@ -157,11 +157,13 @@ struct ipv6_pinfo {
>  #if defined(__BIG_ENDIAN_BITFIELD)
>         /* Packed in 16bits. */
>         __s16                   mcast_hops:9;
> -       __u16                   __unused_2:6,
> +       __u16                   __unused_2:5,
> +                               ipv4emu:1,
>                                 mc_loop:1;
>  #else
>         __u16                   mc_loop:1,
> -                               __unused_2:6;
> +                               ipv4emu:1,
> +                               __unused_2:5;
>         __s16                   mcast_hops:9;
>  #endif
>         int                     ucast_oif;
> diff --git a/include/linux/socket.h b/include/linux/socket.h
> index bb9b83640070..4ae959a3443b 100644
> --- a/include/linux/socket.h
> +++ b/include/linux/socket.h
> @@ -8,6 +8,7 @@
>  #include <linux/types.h>               /* pid_t                        */
>  #include <linux/compiler.h>            /* __user                       */
>  #include <uapi/linux/socket.h>
> +struct sock;                           /* from net/sock.h              */
>
>  struct pid;
>  struct cred;
> @@ -312,8 +313,8 @@ extern int csum_partial_copy_fromiovecend(unsigned char *kdata,
>  extern unsigned long iov_pages(const struct iovec *iov, int offset,
>                                unsigned long nr_segs);
>
> -extern int verify_iovec(struct msghdr *m, struct iovec *iov, struct sockaddr_storage *address, int mode);
> -extern int move_addr_to_kernel(void __user *uaddr, int ulen, struct sockaddr_storage *kaddr);
> +extern int verify_iovec(struct sock *sk, struct msghdr *m, struct iovec *iov, struct sockaddr_storage *address, int mode);
> +extern int move_addr_to_kernel(struct sock *sk, void __user *uaddr, int *ulen, struct sockaddr_storage *kaddr);
>  extern int put_cmsg(struct msghdr*, int level, int type, int len, void *data);
>
>  struct timespec;
> diff --git a/include/net/compat.h b/include/net/compat.h
> index 3b603b199c01..b9ee7aa5a31f 100644
> --- a/include/net/compat.h
> +++ b/include/net/compat.h
> @@ -41,7 +41,7 @@ int compat_sock_get_timestampns(struct sock *, struct timespec __user *);
>  #endif /* defined(CONFIG_COMPAT) */
>
>  int get_compat_msghdr(struct msghdr *, struct compat_msghdr __user *);
> -int verify_compat_iovec(struct msghdr *, struct iovec *,
> +int verify_compat_iovec(struct sock *, struct msghdr *, struct iovec *,
>                         struct sockaddr_storage *, int);
>  asmlinkage long compat_sys_sendmsg(int, struct compat_msghdr __user *,
>                                    unsigned int);
> diff --git a/net/compat.c b/net/compat.c
> index bc8aeefddf3f..f66bd6552204 100644
> --- a/net/compat.c
> +++ b/net/compat.c
> @@ -80,15 +80,15 @@ int get_compat_msghdr(struct msghdr *kmsg, struct compat_msghdr __user *umsg)
>  }
>
>  /* I've named the args so it is easy to tell whose space the pointers are in. */
> -int verify_compat_iovec(struct msghdr *kern_msg, struct iovec *kern_iov,
> +int verify_compat_iovec(struct sock *sk, struct msghdr *kern_msg, struct iovec *kern_iov,
>                    struct sockaddr_storage *kern_address, int mode)
>  {
>         int tot_len;
>
>         if (kern_msg->msg_name && kern_msg->msg_namelen) {
>                 if (mode == VERIFY_READ) {
> -                       int err = move_addr_to_kernel(kern_msg->msg_name,
> -                                                     kern_msg->msg_namelen,
> +                       int err = move_addr_to_kernel(sk, kern_msg->msg_name,
> +                                                     &kern_msg->msg_namelen,
>                                                       kern_address);
>                         if (err < 0)
>                                 return err;
> diff --git a/net/core/iovec.c b/net/core/iovec.c
> index e1ec45ab1e63..a7a393cae2a7 100644
> --- a/net/core/iovec.c
> +++ b/net/core/iovec.c
> @@ -35,7 +35,7 @@
>   *     in any case.
>   */
>
> -int verify_iovec(struct msghdr *m, struct iovec *iov, struct sockaddr_storage *address, int mode)
> +int verify_iovec(struct sock *sk, struct msghdr *m, struct iovec *iov, struct sockaddr_storage *address, int mode)
>  {
>         int size, ct, err;
>
> @@ -43,7 +43,7 @@ int verify_iovec(struct msghdr *m, struct iovec *iov, struct sockaddr_storage *a
>                 if (mode == VERIFY_READ) {
>                         void __user *namep;
>                         namep = (void __user __force *) m->msg_name;
> -                       err = move_addr_to_kernel(namep, m->msg_namelen,
> +                       err = move_addr_to_kernel(sk, namep, &m->msg_namelen,
>                                                   address);
>                         if (err < 0)
>                                 return err;
> diff --git a/net/ipv6/Kconfig b/net/ipv6/Kconfig
> index 438a73aa777c..69924a6c2257 100644
> --- a/net/ipv6/Kconfig
> +++ b/net/ipv6/Kconfig
> @@ -21,6 +21,25 @@ menuconfig IPV6
>
>  if IPV6
>
> +config IPV6_IPV4_EMULATE
> +       bool "IPv6: IPv4 socket interface emulation support"
> +       default n
> +       depends on IPV6=y
> +       ---help---
> +         Whether to include support code which allows using a kernel IPv6
> +         socket from userspace as if it was an IPv4 socket.
> +
> +         ie. this is a userspace-kernel IP address translation shim layer.
> +
> +config IPV6_IPV4_EMULATE_SYSCTL_DEFAULT
> +       bool "IPv6: IPv4 socket emulation - default sysctl setting"
> +       default n
> +       depends on IPV6_IPV4_EMULATE
> +       ---help---
> +         Default value for /proc/sys/net/ipv6/ipv4_emulation
> +         The content of this sysctl file determines whether userspace asking
> +         for IPv4 sockets gets IPv6 sockets with IPv4 emulation turned on.
> +
>  config IPV6_ROUTER_PREF
>         bool "IPv6: Router Preference (RFC 4191) support"
>         ---help---
> diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c
> index e8c4400f23e9..4f08abdf06e0 100644
> --- a/net/ipv6/af_inet6.c
> +++ b/net/ipv6/af_inet6.c
> @@ -476,6 +476,11 @@ int inet6_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
>         struct sock *sk = sock->sk;
>         struct net *net = sock_net(sk);
>
> +#if defined(CONFIG_IPV6_IPV4_EMULATE)
> +       if (inet6_sk(sk)->ipv4emu)
> +               return inet_ioctl(sock, cmd, arg);
> +#endif
> +
>         switch (cmd) {
>         case SIOCGSTAMP:
>                 return sock_get_timestamp(sk, (struct timeval __user *)arg);
> diff --git a/net/ipv6/ipv6_sockglue.c b/net/ipv6/ipv6_sockglue.c
> index e1a9583bb419..2e97c6d55b62 100644
> --- a/net/ipv6/ipv6_sockglue.c
> +++ b/net/ipv6/ipv6_sockglue.c
> @@ -857,7 +857,7 @@ int ipv6_setsockopt(struct sock *sk, int level, int optname,
>         if (level == SOL_IP && sk->sk_type != SOCK_RAW)
>                 return udp_prot.setsockopt(sk, level, optname, optval, optlen);
>
> -       if (level != SOL_IPV6)
> +       if ((level != SOL_IPV6) || (inet6_sk(sk)->ipv4emu))
>                 return -ENOPROTOOPT;
>
>         err = do_ipv6_setsockopt(sk, level, optname, optval, optlen);
> @@ -888,7 +888,7 @@ int compat_ipv6_setsockopt(struct sock *sk, int level, int optname,
>                 return udp_prot.setsockopt(sk, level, optname, optval, optlen);
>         }
>
> -       if (level != SOL_IPV6)
> +       if ((level != SOL_IPV6) || (inet6_sk(sk)->ipv4emu))
>                 return -ENOPROTOOPT;
>
>         if (optname >= MCAST_JOIN_GROUP && optname <= MCAST_MSFILTER)
> @@ -1297,7 +1297,7 @@ int ipv6_getsockopt(struct sock *sk, int level, int optname,
>         if (level == SOL_IP && sk->sk_type != SOCK_RAW)
>                 return udp_prot.getsockopt(sk, level, optname, optval, optlen);
>
> -       if (level != SOL_IPV6)
> +       if ((level != SOL_IPV6) || (inet6_sk(sk)->ipv4emu))
>                 return -ENOPROTOOPT;
>
>         err = do_ipv6_getsockopt(sk, level, optname, optval, optlen, 0);
> @@ -1334,7 +1334,7 @@ int compat_ipv6_getsockopt(struct sock *sk, int level, int optname,
>                 return udp_prot.getsockopt(sk, level, optname, optval, optlen);
>         }
>
> -       if (level != SOL_IPV6)
> +       if ((level != SOL_IPV6) || (inet6_sk(sk)->ipv4emu))
>                 return -ENOPROTOOPT;
>
>         if (optname == MCAST_MSFILTER)
> diff --git a/net/ipv6/sysctl_net_ipv6.c b/net/ipv6/sysctl_net_ipv6.c
> index c5c10fafcfe2..0ab6a8d5aa57 100644
> --- a/net/ipv6/sysctl_net_ipv6.c
> +++ b/net/ipv6/sysctl_net_ipv6.c
> @@ -6,6 +6,7 @@
>   */
>
>  #include <linux/mm.h>
> +#include <linux/module.h>
>  #include <linux/sysctl.h>
>  #include <linux/in6.h>
>  #include <linux/ipv6.h>
> @@ -18,6 +19,20 @@
>
>  static int one = 1;
>
> +#if defined(CONFIG_IPV6_IPV4_EMULATE)
> +static int zero;
> +
> +# if defined(CONFIG_IPV6_IPV4_EMULATE_SYSCTL_DEFAULT)
> +bool sysctl_ipv4_emulation = true;
> +# else
> +bool sysctl_ipv4_emulation = false;
> +# endif
> +
> +module_param_named(ipv4_emulation, sysctl_ipv4_emulation, bool, S_IRUGO|S_IWUSR);
> +MODULE_PARM_DESC(ipv4_emulation,
> +                "Auto promote PF_INET sockets into PF_INET6 sockets, with IPv4 API emulation.");
> +#endif
> +
>  static struct ctl_table ipv6_table_template[] = {
>         {
>                 .procname       = "bindv6only",
> @@ -73,6 +88,17 @@ static struct ctl_table ipv6_rotable[] = {
>                 .proc_handler   = proc_dointvec_minmax,
>                 .extra1         = &one
>         },
> +#if defined(CONFIG_IPV6_IPV4_EMULATE)
> +       {
> +               .procname       = "ipv4_emulation",
> +               .data           = &sysctl_ipv4_emulation,
> +               .maxlen         = sizeof(bool),
> +               .mode           = 0644,
> +               .proc_handler   = proc_dointvec_minmax,
> +               .extra1         = &zero,
> +               .extra2         = &one,
> +       },
> +#endif
>         { }
>  };
>
> diff --git a/net/socket.c b/net/socket.c
> index fe20c319a0bb..e7d2f5281ff3 100644
> --- a/net/socket.c
> +++ b/net/socket.c
> @@ -101,6 +101,7 @@
>  #include <linux/netfilter.h>
>
>  #include <linux/if_tun.h>
> +#include <linux/ipv6.h>
>  #include <linux/ipv6_route.h>
>  #include <linux/route.h>
>  #include <linux/sockios.h>
> @@ -178,30 +179,171 @@ static DEFINE_PER_CPU(int, sockets_in_use);
>   * divide and look after the messy bits.
>   */
>
> +static int ipv6_promote_ipv4_address(struct sock *sk,
> +       struct sockaddr_storage * kaddr, int * addr_len)
> +{
> +#if defined(CONFIG_IPV6_IPV4_EMULATE)
> +       if (!sk) {
> +               pr_warning("ipv6_promote_ipv4_address: no sk\n");
> +               return 0;
> +       }
> +
> +       if (sk->sk_family != PF_INET6) {
> +               //pr_warning("ipv6_promote_ipv4_address: sk_family == %d\n", sk->sk_family);
> +               return 0;
> +       }
> +
> +       if (!inet6_sk(sk)->ipv4emu) {
> +               //pr_warning("ipv6_promote_ipv4_address: not ipv4emu\n");
> +               return 0;
> +       }
> +
> +       if (kaddr->ss_family == AF_UNSPEC) {
> +               //pr_warning("ipv6_promote_ipv4_address: unspec\n");
> +               return 0;
> +       }
> +
> +       if (kaddr->ss_family != AF_INET) {
> +               pr_warning("ipv6_promote_ipv4_address: addr is %d\n", kaddr->ss_family);
> +               return -EAFNOSUPPORT;
> +       }
> +
> +       if (*addr_len < sizeof(struct sockaddr_in)) {
> +               pr_warning("ipv6_promote_ipv4_address: addr_len == %d\n", *addr_len);
> +               return -EINVAL;
> +       }
> +
> +       {
> +               const struct sockaddr_in * kaddr4 = (const struct sockaddr_in *)kaddr;
> +               struct sockaddr_in6 * kaddr6 = (struct sockaddr_in6 *)kaddr;
> +               __be16 port = kaddr4->sin_port;
> +               __be32 ipv4 = kaddr4->sin_addr.s_addr;
> +
> +               //*addr_len = sizeof(struct sockaddr_in6);
> +               *addr_len = 24; //SIN6_LEN_RFC2133; /* 24 = 2+2+4+16, ie. without scope id */
> +// Why not fill in scope_id with 0 ?
> +// One way to avoid the magic 24 constant is to use offsetof(struct sockaddr_in6, scope_id)
> +               kaddr6->sin6_family = AF_INET6;
> +               kaddr6->sin6_port = port;
> +               kaddr6->sin6_flowinfo = htonl(0);
> +               kaddr6->sin6_addr.s6_addr32[0] = htonl(0);
> +               kaddr6->sin6_addr.s6_addr32[1] = htonl(0);
> +               kaddr6->sin6_addr.s6_addr32[2] = ntohl(ipv4) ? htonl(0xFFFF) : htonl(0);
> +               kaddr6->sin6_addr.s6_addr32[3] = ipv4;
> +               kaddr6->sin6_scope_id = 0;
> +
> +               pr_debug("ipv6_promote_ipv4_address: ok [%08X:%d]\n", ntohl(ipv4), ntohs(port));
> +       }
> +#endif
> +       return 0;
> +}
> +
> +static int ipv6_demote_ipv6_address(struct sock *sk, struct sockaddr_storage * kaddr, int * addr_len) {
> +#if defined(CONFIG_IPV6_IPV4_EMULATE)
> +       if (!sk) {
> +               pr_warning("ipv6_demote_ipv6_address: no sk\n");
> +               return 0;
> +       }
> +
> +       if (sk->sk_family != PF_INET6) {
> +               //pr_warning("ipv6_demote_ipv6_address: sk_family == %d\n", sk->sk_family);
> +               return 0;
> +       }
> +
> +       if (!inet6_sk(sk)->ipv4emu) {
> +               //pr_warning("ipv6_demote_ipv6_address: not ipv4emu\n");
> +               return 0;
> +       }
> +
> +       if (kaddr->ss_family != AF_INET6) {
> +               pr_warning("ipv6_demote_ipv6_address: addr is %d\n", kaddr->ss_family);
> +               return -EAFNOSUPPORT;
> +       }
> +
> +       if (*addr_len < sizeof(struct sockaddr_in6)) {
> +               pr_warning("ipv6_demote_ipv6_address: addr_len == %d\n", *addr_len);
> +               return -EINVAL;
> +       }
> +
> +       {
> +               struct sockaddr_in6 const * kaddr6 = (struct sockaddr_in6 const *)kaddr;
> +               struct sockaddr_in * kaddr4 = (struct sockaddr_in *)kaddr;
> +               __be16 port = kaddr6->sin6_port;
> +               __be32 ipv6a = kaddr6->sin6_addr.s6_addr32[0];
> +               __be32 ipv6b = kaddr6->sin6_addr.s6_addr32[1];
> +               __be32 ipv6c = kaddr6->sin6_addr.s6_addr32[2];
> +               __be32 ipv4 = kaddr6->sin6_addr.s6_addr32[3];
> +
> +// please use ipv6_addr_v4mapped()
> +
> +               if (ipv6a) {
> +                       pr_warning("ipv6_demote_ipv6_address: not mapped [1]\n");
> +                       return -EINVAL;
> +               }
> +
> +               if (ipv6b) {
> +                       pr_warning("ipv6_demote_ipv6_address: not mapped [2]\n");
> +                       return -EINVAL;
> +               }
> +
> +               if ((ipv6c != htonl(0)) && (ipv6c != htonl(0xFFFF))) {
> +                       pr_warning("ipv6_demote_ipv6_address: not mapped [3]\n");
> +                       return -EINVAL;
> +               }
> +
> +               if ((ipv6c == htonl(0)) && (ipv4 != htonl(0))) {
> +                       pr_warning("ipv6_demote_ipv6_address: not mapped [4]\n");
> +                       return -EINVAL;
> +               }
> +
> +// Really avoid those magic values...
> +// Since I doubt this is performance critical, just do the whole struct clear at the beginning:
> +// memset(uaddr4, 0, sizeof(*uaddr4));
> +
> +               *addr_len = sizeof(struct sockaddr_in); // 2 + 2 + 4 + 8 padding
> +               kaddr4->sin_family = AF_INET;
> +               kaddr4->sin_port = port;
> +               kaddr4->sin_addr.s_addr = ipv4;
> +               memset(kaddr4->sin_zero, 0, 8);
> +
> +               pr_debug("ipv6_demote_ipv6_address: ok [%08X:%d]\n", ntohl(ipv4), ntohs(port));
> +       }
> +#endif
> +       return 0;
> +}
> +
>  /**
>   *     move_addr_to_kernel     -       copy a socket address into kernel space
> + *     @sk: socket which is receiving the address (or NULL)
>   *     @uaddr: Address in user space
>   *     @kaddr: Address in kernel space
> - *     @ulen: Length in user space
> + *     @ulen: pointer to user space length
>   *
>   *     The address is copied into kernel space. If the provided address is
>   *     too long an error code of -EINVAL is returned. If the copy gives
>   *     invalid addresses -EFAULT is returned. On a success 0 is returned.
>   */
>
> -int move_addr_to_kernel(void __user *uaddr, int ulen, struct sockaddr_storage *kaddr)
> +int move_addr_to_kernel(struct sock *sk, void __user *uaddr, int *ulen,
> +                       struct sockaddr_storage *kaddr)
>  {
> -       if (ulen < 0 || ulen > sizeof(struct sockaddr_storage))
> +       int err;
> +
> +       if (*ulen < 0 || *ulen > sizeof(struct sockaddr_storage))
>                 return -EINVAL;
> -       if (ulen == 0)
> +       if (*ulen == 0)
>                 return 0;
> -       if (copy_from_user(kaddr, uaddr, ulen))
> +       if (copy_from_user(kaddr, uaddr, *ulen))
>                 return -EFAULT;
> -       return audit_sockaddr(ulen, kaddr);
> +       err = audit_sockaddr(*ulen, kaddr);
> +       if (err)
> +               return err;
> +       return ipv6_promote_ipv4_address(sk, kaddr, ulen);
>  }
>
>  /**
>   *     move_addr_to_user       -       copy an address to user space
> + *     @sk: socket which is generating the address (or NULL)
>   *     @kaddr: kernel space address
>   *     @klen: length of address in kernel
>   *     @uaddr: user space address
> @@ -217,7 +359,7 @@ int move_addr_to_kernel(void __user *uaddr, int ulen, struct sockaddr_storage *k
>   *     specified. Zero is returned for a success.
>   */
>
> -static int move_addr_to_user(struct sockaddr_storage *kaddr, int klen,
> +static int move_addr_to_user(struct sock *sk, struct sockaddr_storage *kaddr, int klen,
>                              void __user *uaddr, int __user *ulen)
>  {
>         int err;
> @@ -225,6 +367,9 @@ static int move_addr_to_user(struct sockaddr_storage *kaddr, int klen,
>
>         BUG_ON(klen > sizeof(struct sockaddr_storage));
>         err = get_user(len, ulen);
> +       if (err)
> +               return err;
> +       err = ipv6_demote_ipv6_address(sk, kaddr, &klen);
>         if (err)
>                 return err;
>         if (len > klen)
> @@ -1249,12 +1394,19 @@ call_kill:
>  }
>  EXPORT_SYMBOL(sock_wake_async);
>
> +#if defined(CONFIG_IPV6_IPV4_EMULATE)
> +extern bool sysctl_ipv4_emulation;
> +#endif
> +
>  int __sock_create(struct net *net, int family, int type, int protocol,
>                          struct socket **res, int kern)
>  {
>         int err;
>         struct socket *sock;
>         const struct net_proto_family *pf;
> +#if defined(CONFIG_IPV6_IPV4_EMULATE)
> +       bool ipv4emu = false;
> +#endif
>
>         /*
>          *      Check protocol is in range
> @@ -1283,6 +1435,13 @@ int __sock_create(struct net *net, int family, int type, int protocol,
>         if (err)
>                 return err;
>
> +#if defined(CONFIG_IPV6_IPV4_EMULATE)
> +       if (sysctl_ipv4_emulation && !kern && family == PF_INET) {
> +               family = PF_INET6;
> +               ipv4emu = true;
> +       }
> +#endif
> +
>         /*
>          *      Allocate the socket and allow the family to set things up. if
>          *      the protocol is 0, the family is instructed to select an appropriate
> @@ -1328,6 +1487,14 @@ int __sock_create(struct net *net, int family, int type, int protocol,
>         if (err < 0)
>                 goto out_module_put;
>
> +#if defined(CONFIG_IPV6_IPV4_EMULATE)
> +       if (ipv4emu) {
> +               inet6_sk(sock->sk)->ipv4emu = 1;
> +               /* we need to clear ipv6only, since it could have defaulted to true via sysctl */
> +               sock->sk->sk_ipv6only = 0;
> +       }
> +#endif
> +
>         /*
>          * Now to bump the refcnt of the [loadable] module that owns this
>          * socket at sock_release time we decrement its refcnt.
> @@ -1532,7 +1699,7 @@ SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen)
>
>         sock = sockfd_lookup_light(fd, &err, &fput_needed);
>         if (sock) {
> -               err = move_addr_to_kernel(umyaddr, addrlen, &address);
> +               err = move_addr_to_kernel(sock->sk, umyaddr, &addrlen, &address);
>                 if (err >= 0) {
>                         err = security_socket_bind(sock,
>                                                    (struct sockaddr *)&address,
> @@ -1646,7 +1813,7 @@ SYSCALL_DEFINE4(accept4, int, fd, struct sockaddr __user *, upeer_sockaddr,
>                         err = -ECONNABORTED;
>                         goto out_fd;
>                 }
> -               err = move_addr_to_user(&address,
> +               err = move_addr_to_user(newsock->sk, &address,
>                                         len, upeer_sockaddr, upeer_addrlen);
>                 if (err < 0)
>                         goto out_fd;
> @@ -1695,7 +1862,7 @@ SYSCALL_DEFINE3(connect, int, fd, struct sockaddr __user *, uservaddr,
>         sock = sockfd_lookup_light(fd, &err, &fput_needed);
>         if (!sock)
>                 goto out;
> -       err = move_addr_to_kernel(uservaddr, addrlen, &address);
> +       err = move_addr_to_kernel(sock->sk, uservaddr, &addrlen, &address);
>         if (err < 0)
>                 goto out_put;
>
> @@ -1735,7 +1902,7 @@ SYSCALL_DEFINE3(getsockname, int, fd, struct sockaddr __user *, usockaddr,
>         err = sock->ops->getname(sock, (struct sockaddr *)&address, &len, 0);
>         if (err)
>                 goto out_put;
> -       err = move_addr_to_user(&address, len, usockaddr, usockaddr_len);
> +       err = move_addr_to_user(sock->sk, &address, len, usockaddr, usockaddr_len);
>
>  out_put:
>         fput_light(sock->file, fput_needed);
> @@ -1767,7 +1934,7 @@ SYSCALL_DEFINE3(getpeername, int, fd, struct sockaddr __user *, usockaddr,
>                     sock->ops->getname(sock, (struct sockaddr *)&address, &len,
>                                        1);
>                 if (!err)
> -                       err = move_addr_to_user(&address, len, usockaddr,
> +                       err = move_addr_to_user(sock->sk, &address, len, usockaddr,
>                                                 usockaddr_len);
>                 fput_light(sock->file, fput_needed);
>         }
> @@ -1806,7 +1973,7 @@ SYSCALL_DEFINE6(sendto, int, fd, void __user *, buff, size_t, len,
>         msg.msg_controllen = 0;
>         msg.msg_namelen = 0;
>         if (addr) {
> -               err = move_addr_to_kernel(addr, addr_len, &address);
> +               err = move_addr_to_kernel(sock->sk, addr, &addr_len, &address);
>                 if (err < 0)
>                         goto out_put;
>                 msg.msg_name = (struct sockaddr *)&address;
> @@ -1871,7 +2038,7 @@ SYSCALL_DEFINE6(recvfrom, int, fd, void __user *, ubuf, size_t, size,
>         err = sock_recvmsg(sock, &msg, size, flags);
>
>         if (err >= 0 && addr != NULL) {
> -               err2 = move_addr_to_user(&address,
> +               err2 = move_addr_to_user(sock->sk, &address,
>                                          msg.msg_namelen, addr, addr_len);
>                 if (err2 < 0)
>                         err = err2;
> @@ -2042,9 +2209,9 @@ static int ___sys_sendmsg(struct socket *sock, struct msghdr __user *msg,
>
>         /* This will also move the address data into kernel space */
>         if (MSG_CMSG_COMPAT & flags) {
> -               err = verify_compat_iovec(msg_sys, iov, &address, VERIFY_READ);
> +               err = verify_compat_iovec(sock->sk, msg_sys, iov, &address, VERIFY_READ);
>         } else
> -               err = verify_iovec(msg_sys, iov, &address, VERIFY_READ);
> +               err = verify_iovec(sock->sk, msg_sys, iov, &address, VERIFY_READ);
>         if (err < 0)
>                 goto out_freeiov;
>         total_len = err;
> @@ -2258,9 +2425,9 @@ static int ___sys_recvmsg(struct socket *sock, struct msghdr __user *msg,
>         uaddr = (__force void __user *)msg_sys->msg_name;
>         uaddr_len = COMPAT_NAMELEN(msg);
>         if (MSG_CMSG_COMPAT & flags)
> -               err = verify_compat_iovec(msg_sys, iov, &addr, VERIFY_WRITE);
> +               err = verify_compat_iovec(sock->sk, msg_sys, iov, &addr, VERIFY_WRITE);
>         else
> -               err = verify_iovec(msg_sys, iov, &addr, VERIFY_WRITE);
> +               err = verify_iovec(sock->sk, msg_sys, iov, &addr, VERIFY_WRITE);
>         if (err < 0)
>                 goto out_freeiov;
>         total_len = err;
> @@ -2280,7 +2447,7 @@ static int ___sys_recvmsg(struct socket *sock, struct msghdr __user *msg,
>         len = err;
>
>         if (uaddr != NULL) {
> -               err = move_addr_to_user(&addr,
> +               err = move_addr_to_user(sock->sk, &addr,
>                                         msg_sys->msg_namelen, uaddr,
>                                         uaddr_len);
>                 if (err < 0)
> @@ -3159,7 +3326,11 @@ static int routing_ioctl(struct net *net, struct socket *sock,
>         u32 rtdev;
>         mm_segment_t old_fs = get_fs();
>
> -       if (sock && sock->sk && sock->sk->sk_family == AF_INET6) { /* ipv6 */
> +       if (sock && sock->sk && sock->sk->sk_family == AF_INET6
> +#if defined(CONFIG_IPV6_IPV4_EMULATE)
> +           && !inet6_sk(sock->sk)->ipv4emu
> +#endif
> +          ) { /* ipv6 */
>                 struct in6_rtmsg32 __user *ur6 = argp;
>                 ret = copy_from_user(&r6.rtmsg_dst, &(ur6->rtmsg_dst),
>                         3 * sizeof(struct in6_addr));
> --
> 2.55.0.795.g602f6c329a-goog
>

--
Maciej Żenczykowski, Kernel Networking Developer @ Google

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-07-13  9:05 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-13  8:16 [PATCH] [RFC] net-ipv6: ipv4 emulation support Maciej Żenczykowski
2026-07-13  9:05 ` Maciej Żenczykowski

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox