diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
index 3b9eb40d63436..b5d02ff724957 100644
--- a/tools/testing/selftests/bpf/Makefile
+++ b/tools/testing/selftests/bpf/Makefile
@@ -132,7 +132,7 @@ TEST_GEN_PROGS_EXTENDED = test_sock_addr test_skb_cgroup_id_user \
 	flow_dissector_load test_flow_dissector test_tcp_check_syncookie_user \
 	test_lirc_mode2_user xdping test_cpp runqslower bench bpf_testmod.ko \
 	xskxceiver xdp_redirect_multi xdp_synproxy veristat xdp_hw_metadata \
-	xdp_features bpf_test_no_cfi.ko
+	xdp_features bpf_test_no_cfi.ko sock_addr_testmod.ko
 
 TEST_GEN_FILES += liburandom_read.so urandom_read sign-file uprobe_multi
 
@@ -160,6 +160,7 @@ override define CLEAN
 	$(Q)$(RM) -r $(TEST_GEN_FILES)
 	$(Q)$(RM) -r $(EXTRA_CLEAN)
 	$(Q)$(MAKE) -C bpf_testmod clean
+	$(Q)$(MAKE) -C sock_addr_testmod clean
 	$(Q)$(MAKE) docs-clean
 endef
 
@@ -260,6 +261,12 @@ $(OUTPUT)/bpf_test_no_cfi.ko: $(VMLINUX_BTF) $(RESOLVE_BTFIDS) $(wildcard bpf_te
 	$(Q)$(MAKE) $(submake_extras) RESOLVE_BTFIDS=$(RESOLVE_BTFIDS) -C bpf_test_no_cfi
 	$(Q)cp bpf_test_no_cfi/bpf_test_no_cfi.ko $@
 
+$(OUTPUT)/sock_addr_testmod.ko: $(VMLINUX_BTF) $(RESOLVE_BTFIDS) $(wildcard sock_addr_testmod/Makefile sock_addr_testmod/*.[ch])
+	$(call msg,MOD,,$@)
+	$(Q)$(RM) sock_addr_testmod/sock_addr_testmod.ko # force re-compilation
+	$(Q)$(MAKE) $(submake_extras) RESOLVE_BTFIDS=$(RESOLVE_BTFIDS) -C sock_addr_testmod
+	$(Q)cp sock_addr_testmod/sock_addr_testmod.ko $@
+
 DEFAULT_BPFTOOL := $(HOST_SCRATCH_DIR)/sbin/bpftool
 ifneq ($(CROSS_COMPILE),)
 CROSS_BPFTOOL := $(SCRATCH_DIR)/sbin/bpftool
@@ -638,6 +645,7 @@ TRUNNER_EXTRA_SOURCES := test_progs.c		\
 			 ip_check_defrag_frags.h
 TRUNNER_EXTRA_FILES := $(OUTPUT)/urandom_read $(OUTPUT)/bpf_testmod.ko	\
 		       $(OUTPUT)/bpf_test_no_cfi.ko			\
+		       $(OUTPUT)/sock_addr_testmod.ko	\
 		       $(OUTPUT)/liburandom_read.so			\
 		       $(OUTPUT)/xdp_synproxy				\
 		       $(OUTPUT)/sign-file				\
@@ -767,6 +775,7 @@ EXTRA_CLEAN := $(SCRATCH_DIR) $(HOST_SCRATCH_DIR)			\
 	$(addprefix $(OUTPUT)/,*.o *.skel.h *.lskel.h *.subskel.h	\
 			       no_alu32 cpuv4 bpf_gcc bpf_testmod.ko	\
 			       bpf_test_no_cfi.ko			\
+				   sock_addr_testmod.ko	\
 			       liburandom_read.so)
 
 .PHONY: docs docs-clean
diff --git a/tools/testing/selftests/bpf/sock_addr_testmod/.gitignore b/tools/testing/selftests/bpf/sock_addr_testmod/.gitignore
new file mode 100644
index 0000000000000..ded5137772813
--- /dev/null
+++ b/tools/testing/selftests/bpf/sock_addr_testmod/.gitignore
@@ -0,0 +1,6 @@
+*.mod
+*.mod.c
+*.o
+.ko
+/Module.symvers
+/modules.order
diff --git a/tools/testing/selftests/bpf/sock_addr_testmod/Makefile b/tools/testing/selftests/bpf/sock_addr_testmod/Makefile
new file mode 100644
index 0000000000000..47c92fb3a7748
--- /dev/null
+++ b/tools/testing/selftests/bpf/sock_addr_testmod/Makefile
@@ -0,0 +1,20 @@
+SOCK_ADDR_TEST_MOD_DIR := $(realpath $(dir $(abspath $(lastword $(MAKEFILE_LIST)))))
+KDIR ?= $(abspath $(SOCK_ADDR_TEST_MOD_DIR)/../../../../..)
+
+ifeq ($(V),1)
+Q =
+else
+Q = @
+endif
+
+MODULES = sock_addr_testmod.ko
+
+obj-m += sock_addr_testmod.o
+CFLAGS_sock_addr_testmod.o = -I$(src)
+
+all:
+	+$(Q)make -C $(KDIR) M=$(SOCK_ADDR_TEST_MOD_DIR) modules
+
+clean:
+	+$(Q)make -C $(KDIR) M=$(SOCK_ADDR_TEST_MOD_DIR) clean
+
diff --git a/tools/testing/selftests/bpf/sock_addr_testmod/sock_addr_testmod.c b/tools/testing/selftests/bpf/sock_addr_testmod/sock_addr_testmod.c
new file mode 100644
index 0000000000000..de0b2007f0829
--- /dev/null
+++ b/tools/testing/selftests/bpf/sock_addr_testmod/sock_addr_testmod.c
@@ -0,0 +1,256 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2024 Google LLC. */
+#include <linux/net.h>
+#include <linux/socket.h>
+#include <linux/nsproxy.h>
+#include <linux/inet.h>
+#include <linux/in.h>
+#include <linux/in6.h>
+#include <linux/un.h>
+#include <linux/debugfs.h>
+#include <net/sock.h>
+
+#define BIND    0
+#define CONNECT 1
+#define SENDMSG 2
+
+#define CONNECT_TIMEOUT_SEC 1
+
+static char ip[256];
+module_param_string(ip, ip, sizeof(ip), 0644);
+MODULE_PARM_DESC(ip, "IPv4/IPv6/Unix address to use for socket operation");
+static char port[7];
+module_param_string(port, port, sizeof(port), 0644);
+MODULE_PARM_DESC(port, "Port number to use for socket operation");
+static uint af;
+module_param(af, uint, 0644);
+MODULE_PARM_DESC(af, "Address family (AF_INET, AF_INET6, or AF_UNIX)");
+static int type;
+module_param(type, int, 0644);
+MODULE_PARM_DESC(type, "Socket type (SOCK_STREAM or SOCK_DGRAM)");
+static uint op;
+module_param(op, uint, 0644);
+MODULE_PARM_DESC(op, "Socket operation (BIND=0, CONNECT=1, SENDMSG=2)");
+
+static struct debugfs_blob_wrapper sock_name_blob;
+static struct debugfs_blob_wrapper peer_name_blob;
+static struct debugfs_blob_wrapper addr_blob;
+static struct dentry *debugfs_dentry;
+static struct sockaddr_storage sock_name;
+static struct sockaddr_storage peer_name;
+static struct sockaddr_storage addr;
+static bool success;
+
+static struct socket *sock;
+
+static int do_kernel_bind(struct sockaddr *addr, int addrlen)
+{
+	int err;
+
+	err = kernel_bind(sock, (struct sockaddr *)addr, addrlen);
+	if (err) {
+		pr_err("kernel_bind() returned %d\n", err);
+		goto err;
+	}
+
+	err = kernel_getsockname(sock, (struct sockaddr *)&sock_name);
+	if (err < 0) {
+		pr_err("kernel_getsockname() returned %d\n", err);
+		goto err;
+	}
+
+	if (type == SOCK_STREAM) {
+		err = kernel_listen(sock, 128);
+		if (err == -1) {
+			pr_err("kernel_listen() returned %d\n", err);
+			goto err;
+		}
+	}
+
+	err = 0;
+	goto out;
+err:
+	err = -1;
+out:
+	return err;
+}
+
+static int do_kernel_connect(struct sockaddr *addr, int addrlen)
+{
+	int err;
+
+	/* Set timeout for call to kernel_connect() to prevent it from hanging,
+	 * and consider the connection attempt failed if it returns
+	 * -EINPROGRESS.
+	 */
+	sock->sk->sk_sndtimeo = CONNECT_TIMEOUT_SEC * HZ;
+
+	err = kernel_connect(sock, addr, addrlen, 0);
+	if (err) {
+		pr_err("kernel_connect() returned %d\n", err);
+		goto err;
+	}
+
+	err = kernel_getsockname(sock, (struct sockaddr *)&sock_name);
+	if (err < 0) {
+		pr_err("kernel_getsockname() returned %d\n", err);
+		goto err;
+	}
+
+	err = kernel_getpeername(sock, (struct sockaddr *)&peer_name);
+	if (err < 0) {
+		pr_err("kernel_getpeername() returned %d\n", err);
+		goto err;
+	}
+
+	err = 0;
+	goto out;
+err:
+	err = -1;
+out:
+	return err;
+}
+
+static int do_kernel_sendmsg(struct sockaddr *addr, int addrlen)
+{
+	struct msghdr msg = {
+		.msg_name	= addr,
+		.msg_namelen	= addrlen,
+	};
+	struct kvec iov;
+	int err;
+
+	iov.iov_base = "abc";
+	iov.iov_len  = sizeof("abc");
+
+	err = kernel_sendmsg(sock, &msg, &iov, 1, sizeof("abc"));
+	if (err < 0) {
+		pr_err("kernel_sendmsg() returned %d\n", err);
+		goto err;
+	}
+
+	/* Unix socket sockaddr hooks may transform msg_namelen. Make sure it is
+	 * unmodified after the call to kernel_sendmsg().
+	 */
+	if (msg.msg_namelen != addrlen) {
+		pr_err("msg_namelen was modified (original=%d,current=%d)\n", addrlen, msg.msg_namelen);
+		goto err;
+	}
+
+	/* kernel_sendmsg() and sock_sendmsg() are both used throughout the
+	 * kernel. Neither of these functions should modify msg_name, so call
+	 * both just to make sure.
+	 */
+	iov_iter_kvec(&msg.msg_iter, ITER_SOURCE, &iov, 1, sizeof("abc"));
+	err = sock_sendmsg(sock, &msg);
+	if (err < 0) {
+		pr_err("sock_sendmsg() returned %d\n", err);
+		goto err;
+	}
+
+	/* Unix socket sockaddr hooks may transform msg_namelen. Make sure it is
+	* unmodified after the call to sock_sendmsg().
+	*/
+	if (msg.msg_namelen != addrlen) {
+		pr_err("msg_namelen was modified (original=%d,current=%d)\n", addrlen, msg.msg_namelen);
+		goto err;
+	}
+
+	err = 0;
+	goto out;
+err:
+	err = -1;
+out:
+	return err;
+}
+
+static int do_sock_op(int op, struct sockaddr *addr, int addrlen)
+{
+	switch (op) {
+	case BIND:
+		return do_kernel_bind(addr, addrlen);
+	case CONNECT:
+		return do_kernel_connect(addr, addrlen);
+	case SENDMSG:
+		return do_kernel_sendmsg(addr, addrlen);
+	default:
+		return -EINVAL;
+	}
+}
+
+static int kernel_sock_addr_testmod_init(void)
+{
+	int addr_len = sizeof(struct sockaddr_storage);
+	int proto;
+	int err;
+
+	debugfs_dentry = debugfs_create_dir("sock_addr_testmod", NULL);
+
+	addr_blob.data = &addr;
+	addr_blob.size = sizeof(addr);
+	sock_name_blob.data = &sock_name;
+	sock_name_blob.size = sizeof(sock_name);
+	peer_name_blob.data = &peer_name;
+	peer_name_blob.size = sizeof(peer_name);
+
+	debugfs_create_blob("addr", 0444, debugfs_dentry, &addr_blob);
+	debugfs_create_blob("sock_name", 0444, debugfs_dentry, &sock_name_blob);
+	debugfs_create_blob("peer_name", 0444, debugfs_dentry, &peer_name_blob);
+	debugfs_create_bool("success", 0444, debugfs_dentry, &success);
+
+	switch (af) {
+	case AF_INET:
+	case AF_INET6:
+		err = inet_pton_with_scope(&init_net, af, ip, port, &addr);
+		if (err) {
+			pr_err("inet_pton_with_scope() returned %d\n", err);
+			goto err;
+		}
+
+		proto = type == SOCK_STREAM ? IPPROTO_TCP : IPPROTO_UDP;
+		break;
+	case AF_UNIX:
+		memset(&addr, 0, sizeof(addr));
+		((struct sockaddr_un *)&addr)->sun_family = AF_UNIX;
+		((struct sockaddr_un *)&addr)->sun_path[0] = 0; // abstract
+		strcpy(((struct sockaddr_un *)&addr)->sun_path + 1, ip);
+		addr_len = offsetof(struct sockaddr_un, sun_path) + 1 +
+			   strlen(ip);
+		proto = PF_UNIX;
+		break;
+	default:
+		pr_err("invalid address family %d\n", af);
+		goto err;
+	}
+
+	err = sock_create_kern(&init_net, af, type, proto, &sock);
+	if (err) {
+		pr_err("sock_create_kern() returned %d\n", err);
+		goto err;
+	}
+
+	if (do_sock_op(op, (struct sockaddr *)&addr, addr_len))
+		goto err;
+
+	success = true;
+	goto out;
+err:
+	success = false;
+out:
+	return 0;
+}
+
+static void kernel_sock_addr_testmod_exit(void)
+{
+	if (sock)
+		sock_release(sock);
+
+	debugfs_remove_recursive(debugfs_dentry);
+}
+
+module_init(kernel_sock_addr_testmod_init);
+module_exit(kernel_sock_addr_testmod_exit);
+
+MODULE_AUTHOR("Jordan Rife");
+MODULE_DESCRIPTION("BPF socket address selftests module");
+MODULE_LICENSE("Dual BSD/GPL");
