diff --git a/tools/testing/selftests/net/ovpn/common.sh b/tools/testing/selftests/net/ovpn/common.sh
index 2d844eb3aa6e..5e9c81e885e6 100644
--- a/tools/testing/selftests/net/ovpn/common.sh
+++ b/tools/testing/selftests/net/ovpn/common.sh
@@ -136,6 +136,19 @@ ovpn_create_ns() {
 	ip netns add "ovpn_peer${1}"
 }
 
+ovpn_peer_vpn_addr() {
+	local peer="$1"
+	local file
+
+	if [ "${OVPN_PROTO}" == "UDP" ]; then
+		file="${OVPN_UDP_PEERS_FILE}"
+	else
+		file="${OVPN_TCP_PEERS_FILE}"
+	fi
+
+	awk -v peer="${peer}" '$1 == peer {print $NF; exit}' "${file}"
+}
+
 ovpn_setup_ns() {
 	local peer="ovpn_peer${1}"
 	local server_ns="ovpn_peer0"
diff --git a/tools/testing/selftests/net/ovpn/ovpn-cli.c b/tools/testing/selftests/net/ovpn/ovpn-cli.c
index f4effa7580c0..3b612a8a18fe 100644
--- a/tools/testing/selftests/net/ovpn/ovpn-cli.c
+++ b/tools/testing/selftests/net/ovpn/ovpn-cli.c
@@ -650,6 +650,26 @@ static int ovpn_connect(struct ovpn_ctx *ovpn)
 	return ret;
 }
 
+static int ovpn_nl_put_vpn_addr(struct nl_msg *msg,
+				const struct ovpn_ctx *ovpn)
+{
+	if (!ovpn->peer_ip_set)
+		return 0;
+
+	switch (ovpn->peer_ip.in4.sin_family) {
+	case AF_INET:
+		return nla_put_u32(msg, OVPN_A_PEER_VPN_IPV4,
+				   ovpn->peer_ip.in4.sin_addr.s_addr);
+	case AF_INET6:
+		return nla_put(msg, OVPN_A_PEER_VPN_IPV6,
+			       sizeof(struct in6_addr),
+			       &ovpn->peer_ip.in6.sin6_addr);
+	default:
+		fprintf(stderr, "Invalid family for peer address\n");
+		return -EAFNOSUPPORT;
+	}
+}
+
 static int ovpn_new_peer(struct ovpn_ctx *ovpn, bool is_tcp)
 {
 	struct nlattr *attr;
@@ -691,22 +711,9 @@ static int ovpn_new_peer(struct ovpn_ctx *ovpn, bool is_tcp)
 		}
 	}
 
-	if (ovpn->peer_ip_set) {
-		switch (ovpn->peer_ip.in4.sin_family) {
-		case AF_INET:
-			NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_VPN_IPV4,
-				    ovpn->peer_ip.in4.sin_addr.s_addr);
-			break;
-		case AF_INET6:
-			NLA_PUT(ctx->nl_msg, OVPN_A_PEER_VPN_IPV6,
-				sizeof(struct in6_addr),
-				&ovpn->peer_ip.in6.sin6_addr);
-			break;
-		default:
-			fprintf(stderr, "Invalid family for peer address\n");
-			goto nla_put_failure;
-		}
-	}
+	ret = ovpn_nl_put_vpn_addr(ctx->nl_msg, ovpn);
+	if (ret)
+		goto nla_put_failure;
 
 	nla_nest_end(ctx->nl_msg, attr);
 
@@ -732,6 +739,10 @@ static int ovpn_set_peer(struct ovpn_ctx *ovpn)
 		    ovpn->keepalive_interval);
 	NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_KEEPALIVE_TIMEOUT,
 		    ovpn->keepalive_timeout);
+
+	ret = ovpn_nl_put_vpn_addr(ctx->nl_msg, ovpn);
+	if (ret)
+		goto nla_put_failure;
 	nla_nest_end(ctx->nl_msg, attr);
 
 	ret = ovpn_nl_msg_send(ctx, NULL);
@@ -1730,13 +1741,14 @@ static void usage(const char *cmd)
 	fprintf(stderr, "\tmark: socket FW mark value\n");
 
 	fprintf(stderr,
-		"* set_peer <iface> <peer_id> <keepalive_interval> <keepalive_timeout>: set peer attributes\n");
+		"* set_peer <iface> <peer_id> <keepalive_interval> <keepalive_timeout> [vpnaddr]: set peer attributes\n");
 	fprintf(stderr, "\tiface: ovpn interface name\n");
 	fprintf(stderr, "\tpeer_id: peer ID of the peer to modify\n");
 	fprintf(stderr,
 		"\tkeepalive_interval: interval for sending ping messages\n");
 	fprintf(stderr,
 		"\tkeepalive_timeout: time after which a peer is timed out\n");
+	fprintf(stderr, "\tvpnaddr: peer VPN IP\n");
 
 	fprintf(stderr, "* del_peer <iface> <peer_id>: delete peer\n");
 	fprintf(stderr, "\tiface: ovpn interface name\n");
@@ -2090,6 +2102,8 @@ static int ovpn_run_cmd(struct ovpn_ctx *ovpn)
 			return ret;
 
 		ret = ovpn_new_peer(ovpn, false);
+		if (ret < 0)
+			return ret;
 		ovpn_waitbg();
 		break;
 	case CMD_NEW_MULTI_PEER:
@@ -2331,6 +2345,12 @@ static int ovpn_parse_cmd_args(struct ovpn_ctx *ovpn, int argc, char *argv[])
 				"keepalive interval value out of range\n");
 			return -1;
 		}
+
+		if (argc > 6) {
+			ret = ovpn_parse_remote(ovpn, NULL, NULL, argv[6]);
+			if (ret < 0)
+				return -1;
+		}
 		break;
 	case CMD_DEL_PEER:
 		if (argc < 4)
diff --git a/tools/testing/selftests/net/ovpn/test.sh b/tools/testing/selftests/net/ovpn/test.sh
index 9b5610837032..392109d5e14e 100755
--- a/tools/testing/selftests/net/ovpn/test.sh
+++ b/tools/testing/selftests/net/ovpn/test.sh
@@ -56,6 +56,76 @@ ovpn_prepare_network() {
 	done
 }
 
+ovpn_new_test_peer() {
+	local peer_id="$1"
+
+	shift
+	ip netns exec ovpn_peer0 "${OVPN_CLI}" new_peer tun0 \
+		"${peer_id}" none 65000 10.10.1.2 1 "$@"
+}
+
+ovpn_set_peer_vpn_addr() {
+	ip netns exec ovpn_peer0 "${OVPN_CLI}" set_peer tun0 \
+		"$1" 60 120 "$2"
+}
+
+ovpn_run_vpn_addr_validation() {
+	local addr
+	local peer1_addr4
+	local test_peer_id=$((OVPN_NUM_PEERS + 1))
+	local test_peer_addr6="2001:db8::2"
+	# Do not include 0.0.0.0 or :: here. They are invalid on creation, but
+	# clear one address family on update and are valid if the other remains.
+	local -a invalid_addrs=(
+		"127.0.0.1"
+		"224.0.0.1"
+		"255.255.255.255"
+		"::1"
+		"::192.0.2.1"
+		"::ffff:192.0.2.1"
+		"ff02::1"
+	)
+
+	peer1_addr4=$(ovpn_peer_vpn_addr 1)
+
+	ovpn_cmd_fail "reject peer without VPN address" \
+		ovpn_new_test_peer "${test_peer_id}"
+
+	for addr in "0.0.0.0" "::" "${invalid_addrs[@]}"; do
+		ovpn_cmd_fail "reject new peer VPN address ${addr}" \
+			ovpn_new_test_peer "${test_peer_id}" "${addr}"
+	done
+
+	ovpn_cmd_fail "reject duplicate IPv4 address on peer creation" \
+		ovpn_new_test_peer "${test_peer_id}" "${peer1_addr4}"
+	ovpn_cmd_fail "reject clearing the last peer VPN address" \
+		ovpn_set_peer_vpn_addr 1 0.0.0.0
+
+	for addr in "${invalid_addrs[@]}"; do
+		ovpn_cmd_fail "reject updated peer VPN address ${addr}" \
+			ovpn_set_peer_vpn_addr 1 "${addr}"
+	done
+
+	ovpn_cmd_fail "reject duplicate IPv4 address on peer update" \
+		ovpn_set_peer_vpn_addr 2 "${peer1_addr4}"
+
+	ovpn_cmd_ok "add peer IPv6 address" \
+		ovpn_set_peer_vpn_addr 1 "${test_peer_addr6}"
+	ovpn_cmd_fail "reject duplicate IPv6 address on peer creation" \
+		ovpn_new_test_peer "${test_peer_id}" "${test_peer_addr6}"
+	ovpn_cmd_fail "reject duplicate IPv6 address on peer update" \
+		ovpn_set_peer_vpn_addr 2 "${test_peer_addr6}"
+
+	ovpn_cmd_ok "clear peer IPv4 address" \
+		ovpn_set_peer_vpn_addr 1 0.0.0.0
+	ovpn_cmd_fail "reject clearing the remaining peer IPv6 address" \
+		ovpn_set_peer_vpn_addr 1 ::
+	ovpn_cmd_ok "restore peer IPv4 address" \
+		ovpn_set_peer_vpn_addr 1 "${peer1_addr4}"
+	ovpn_cmd_ok "clear peer IPv6 address" \
+		ovpn_set_peer_vpn_addr 1 ::
+}
+
 ovpn_run_basic_traffic() {
 	local p
 	local header1
@@ -293,15 +363,16 @@ trap ovpn_stage_err ERR
 
 ktap_print_header
 if [ "${OVPN_FLOAT}" == "1" ]; then
-	ktap_set_plan 13
+	ktap_set_plan 14
 else
-	ktap_set_plan 12
+	ktap_set_plan 13
 fi
 
 ovpn_cleanup
 modprobe -q ovpn || true
 
 ovpn_run_stage "setup network topology" ovpn_prepare_network
+ovpn_run_stage "validate peer VPN addresses" ovpn_run_vpn_addr_validation
 ovpn_run_stage "run baseline data traffic" ovpn_run_basic_traffic
 ovpn_run_stage "run LAN traffic behind peer1" ovpn_run_lan_traffic
 [ "${OVPN_FLOAT}" == "1" ] && ovpn_run_stage "run floating peer checks" \
