삽질하다 도저히 문제 파악이 안돼서 opcUaManager로 분리 테스트 중

This commit is contained in:
2026-02-25 08:52:03 +09:00
parent 4ea351946a
commit e88ab87771
138 changed files with 1051971 additions and 351 deletions

View File

@@ -1,108 +1,108 @@
using Opc.Ua;
using Opc.Ua.Client;
using System.Security.Cryptography.X509Certificates;
using System.Net;
using System;
using System.Threading.Tasks;
using OpcPks.Core.Models;
namespace OpcPks.Core.Services
{
public class OpcSessionManager
{
private ISession? _session;
public ISession? Session => _session;
private readonly string _pkiPath = "/home/pacer/projects/OpcPksPlatform/OpcPks.Core/Data/pki";
public async Task<ISession> GetSessionAsync()
/// <summary>
/// [수정] 하니웰 인사용: 단순히 연결만 하는 게 아니라 서버 시간을 읽으려 시도하여
/// 하니웰 본체 엔진이 인증서를 Rejected로 던지도록 유도합니다.
/// </summary>
public async Task<bool> SayHelloAsync(string ip)
{
if (_session != null && _session.Connected) return _session;
try
{
// CertRequestModel은 최소 정보만 담아 전달
using (var session = await GetSessionAsync(ip, new CertRequestModel { PrimaryIpA = ip }))
{
if (session != null && session.Connected)
{
// 연결 성공 시 서버 시간 노드를 읽어 실제 통신이 가능한지 확인
var currentTime = session.ReadValue(Variables.Server_ServerStatus_CurrentTime);
Console.WriteLine($"✅ [SayHello] 하니웰 연결 및 데이터 읽기 성공: {currentTime}");
await session.CloseAsync();
return true;
}
}
return false;
}
catch (Exception ex)
{
// 여기서 에러가 나야 정상입니다. (인증서가 신뢰되지 않았을 때 하니웰이 거부하며 파일을 Rejected로 보냄)
Console.WriteLine($"⚠️ [SayHello] 하니웰 거부 반응 유도 성공: {ex.Message}");
return false;
}
}
string serverHostName = "192.168.0.20";
string endpointUrl = $"opc.tcp://{serverHostName}:4840";
string applicationUri = "urn:dbsvr:OpcTestClient";
string userName = "mngr";
string password = "mngr";
string pfxPassword = ""; // openssl에서 확인한 비밀번호가 있다면 입력
/// <summary>
/// [핵심] 크롤러 및 모든 서비스가 공통으로 사용하는 실제 세션 생성 로직
/// </summary>
public async Task<ISession?> GetSessionAsync(string ip, CertRequestModel model)
{
if (string.IsNullOrEmpty(ip)) return null;
string baseDataPath = "/home/pacer/projects/OpcPksPlatform/OpcPks.Core/Data/pki";
string pfxFilePath = Path.Combine(baseDataPath, "own/private/OpcTestClient.pfx");
var config = new ApplicationConfiguration {
ApplicationName = "OpcTestClient",
string endpointUrl = $"opc.tcp://{ip}:4840";
var config = new ApplicationConfiguration()
{
ApplicationName = "OpcPksClient",
ApplicationType = ApplicationType.Client,
ApplicationUri = applicationUri,
SecurityConfiguration = new SecurityConfiguration {
SecurityConfiguration = new SecurityConfiguration
{
ApplicationCertificate = new CertificateIdentifier {
StoreType = "Directory",
StorePath = Path.Combine(baseDataPath, "own"),
SubjectName = "OpcTestClient"
StorePath = $"{_pkiPath}/own"
},
TrustedPeerCertificates = new CertificateTrustList {
StoreType = "Directory",
StorePath = Path.Combine(baseDataPath, "trusted")
StorePath = $"{_pkiPath}/trusted"
},
AutoAcceptUntrustedCertificates = true,
AddAppCertToTrustedStore = true
RejectedCertificateStore = new CertificateTrustList {
StoreType = "Directory",
StorePath = $"{_pkiPath}/rejected"
},
AutoAcceptUntrustedCertificates = true
},
TransportQuotas = new TransportQuotas { OperationTimeout = 60000 },
ClientConfiguration = new ClientConfiguration { DefaultSessionTimeout = 60000 }
TransportQuotas = new TransportQuotas { OperationTimeout = 10000 },
ClientConfiguration = new ClientConfiguration { DefaultSessionTimeout = 10000 }
};
// 1. [인증서 강제 로드] Validate 호출 전에 인증서를 명시적으로 로드합니다.
if (!File.Exists(pfxFilePath)) {
throw new Exception($"❌ PFX 파일을 찾을 수 없습니다: {pfxFilePath}");
}
try {
// 리눅스 .NET 호환용 플래그
var cert = new X509Certificate2(
pfxFilePath,
pfxPassword,
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet
);
// config에 인증서 직접 주입
config.SecurityConfiguration.ApplicationCertificate.Certificate = cert;
Console.WriteLine($"✅ [인증서 로드] {cert.Subject}");
} catch (Exception ex) {
Console.WriteLine($"❌ [인증서 에러] PFX 로드 실패: {ex.Message}");
throw;
}
// 2. 설정 검증 (인증서 주입 후 실행)
await config.Validate(ApplicationType.Client);
// Validate 이후 인증서가 풀렸을 경우를 대비해 다시 확인
if (config.SecurityConfiguration.ApplicationCertificate.Certificate == null) {
Console.WriteLine("⚠️ [경고] Validate 과정에서 인증서 설정이 유실되어 재할당합니다.");
config.SecurityConfiguration.ApplicationCertificate.Certificate = new X509Certificate2(pfxFilePath, pfxPassword, X509KeyStorageFlags.MachineKeySet);
try
{
// 하니웰 보안 설정에 맞는 엔드포인트 선택
var endpointDescription = CoreClientUtils.SelectEndpoint(endpointUrl, true, 5000);
var endpointConfiguration = EndpointConfiguration.Create(config);
var endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration);
// 세션 생성 시도: 이 과정 자체가 하니웰 본체에 대한 보안 컨택입니다.
return await Session.Create(
config,
endpoint,
false,
"OpcPksSession",
10000,
new UserIdentity(new AnonymousIdentityToken()),
null);
}
config.CertificateValidator.CertificateValidation += (s, e) => { e.Accept = true; };
// 3. 엔드포인트 선택 및 보안 정책 로그
var endpointDescription = CoreClientUtils.SelectEndpoint(endpointUrl, true);
var endpointConfiguration = EndpointConfiguration.Create(config);
var configuredEndpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration);
Console.WriteLine($"📡 [세션] 연결 시도: {endpointUrl}");
Console.WriteLine($"🔐 [보안] {endpointDescription.SecurityPolicyUri} ({endpointDescription.SecurityMode})");
// 4. 세션 생성 시도
try {
_session = await Opc.Ua.Client.Session.Create(
config,
configuredEndpoint,
true,
"OpcPksSession",
60000,
new UserIdentity(userName, password),
null
);
Console.WriteLine("🚀 [성공] 하니웰 서버와 연결되었습니다!");
} catch (ServiceResultException srex) {
Console.WriteLine($"❌ [OPC 에러] {srex.StatusCode}: {srex.Message}");
throw;
catch (ServiceResultException sx)
{
Console.WriteLine($"[OPC_UA] 하니웰 보안 응답 코드: {sx.StatusCode}");
return null;
}
catch (Exception ex)
{
Console.WriteLine($"[OPC_UA] 세션 생성 중 오류: {ex.Message}");
return null;
}
return _session;
}
}
}