평화로운 FF14 라이프를 구가하던 중...
PingPlugin이라는 핑을 표시해 주는 플러그인이 제대로 동작하지 않는다는 소식을 들었다.
난 글로벌 서버가 메인이기 때문에 옆동네 이슈엔 별 관심이 없지만...
지인이 필요하다고 하니 한번 들여다보기로 했다.
1. 상황 파악
결정했으면 뭐가 안되는지 파악해야 한다.
IP는 잡아오는데 핑이 계속 0이라고 하더라.
그래서 다른 방법들을 다 테스트 해 봤는데 되는 게 딱 하나 있었다.
패킷이 오고 가는걸로 핑을 재는 기능이었다.
여기서 마나데센까지의 핑이 40초반대이다.
핑 측정도 제대로 안되고 쓰지 않으니만 못하다.
그럼 다른 측정 방식은 왜 동작하지 않는지 일단 자체적으로 핑을 때려보도록 하자.
ICMP 프로토콜을 완전히 블락한다.
흠... 일단 이거는 막혀있긴 한데, 다른 문제로 인해서 플러그인이 동작하지 않는 것일 수도 있으니,
플러그인의 소스코드를 뜯어보자.
2. 플러그인은 어떻게 핑을 측정하는가
사실 저거랑 별 다를 게 없긴 하다.
글로벌 서버의 IP에 핑을 때리면 답을 잘 주기 때문에,
일단 한국 서버는 글로벌 서버와 다르게 ICMP를 블락한다는 차이가 있을 뿐인 것 같다.
같은 소스코드인데 어딘 되고 어딘 안되면 대충 답이 보인다.
COM, Win API, Game Packet 3가지의 측정 방식 중, 우린 이미 COM과 Game Packet 방식이
쓸모없다는 것을 알았다.
Win API를 활용하는 방식은 어떻게 구해오나 보자.
private static ulong GetAddressLastRTT(IPAddress address)
{
var addressBytes = address.GetAddressBytes();
var addressRaw = BitConverter.ToUInt32(addressBytes);
var hopCount = 0U;
var rtt = 0U;
return GetRTTAndHopCount(addressRaw, ref hopCount, 51, ref rtt) == 1 ? rtt : 0;
}
[DllImport("Iphlpapi.dll", EntryPoint = "GetRTTAndHopCount", SetLastError = true)]
private static extern int GetRTTAndHopCount(uint address, ref uint hopCount, uint maxHops, ref uint rtt);
와 진짜 Iphlpapi라는 친구는 처음 봤다.
hop count까지 뱉어주는 거 보니 tracert가 생각난다.
생소한 라이브러리와 함수에 대한 감상은 일단 뒤로하자.
GetRTTAndHopCount에 대한 MSDN 문서를 살펴보니 포트까지 지정해서 쏘는 건 못하는 것 같다.
아마 이 함수도 ICMP 프로토콜로 요청하기 때문에 동작하지 않는 것으로 추측된다.
아래와 같은 코드로 이 함수의 동작을 확인하기로 했다.
using System.Net;
using System.Runtime.InteropServices;
namespace MyApp
{
public class Program
{
static ulong GetAddressLastRTT(IPAddress address)
{
var addressBytes = address.GetAddressBytes();
var addressRaw = BitConverter.ToUInt32(addressBytes);
var hopCount = 0U;
var rtt = 0U;
return GetRTTAndHopCount(addressRaw, ref hopCount, 51, ref rtt) == 1 ? rtt : 0;
}
[DllImport("Iphlpapi.dll", EntryPoint = "GetRTTAndHopCount", SetLastError = true)]
static extern int GetRTTAndHopCount(uint address, ref uint hopCount, uint maxHops, ref uint rtt);
public static void Main(string[] args)
{
while (true)
{
ulong temp = GetAddressLastRTT(IPAddress.Parse("183.111.191.45"));
//ulong temp = GetAddressLastRTT(IPAddress.Parse("124.150.157.156")); // Mana DC
if (temp <= 0)
Console.WriteLine("Something Went Wrong! " + temp + "\n");
else
Console.WriteLine("GOOD! " + temp + "\n");
}
}
}
}
실행 결과는 아래와 같다.
아예 Timeout이 되기 때문에 결과 출력 조차 되지 않는 것으로 보인다.
마나데센으로 핑을 쏘는건 아래와 같이 잘 된다.
그럼 저 함수가 어떻게 요청을 보내는지 Wireshark로 확인해 보자.
역시 ICMP 프로토콜이기 때문에 블락된 것이었다.
3. 그럼 어떡하지?
ICMP가 아니라 TCP를 사용해야 할 것 같다.
TCP를 사용할 수 있는 핑 프로그램을 사용해서 테스트해 보자.
이런 거 하는데 역시 PsPing이 좋은 것 같다.
PsPing을 다운받아 핑을 날려보기로 했다.
여전히 블락되는 거 같다.
IP만 던져주면 TCP가 아니라 ICMP로 보내는 것 같다.
와이어샤크도 ICMP라고 말해준다.
그럼 포트도 같이 던져줘야 TCP로 요청을 보내는 것 같으니 포트를 알아오자.
일단 현재 접속된 포트에 보내는 게 좋을 것 같다. 클라이언트와 통신하고 있다는 뜻이니까.
포트는 54992다. 일단 여기로 요청을 보내볼 예정.
참고로 FF14가 사용하는 포트들은 아래와 같다.
공식에서 제공하는 포트 사용 정보다.
보아하니 한섭도 글섭과 동일한 포트로 통신하는 것 같다.
하긴 굳이 바꿀 이유도 없다.
여하튼 저 포트까지 던져줘서 핑을 때린 결과는 아래와 같다.
오 잘되는데?
와이어샤크에서 확인해 보자.
요청을 보니 다음의 순서로 이루어진다.
- SYN
- SYN, ACK
- ACK
- FIN, ACK
- ACK
- FIN, ACK
- ACK
이 흐름을 모방하면 될 듯하다.
근데 이걸 일일이 하기엔 너무 번거로우니 비슷한 기능을 하는 라이브러리를 찾아보자.
4. 근데... 안된다...
깃헙에서 몇 가지를 찾아서 기존 플러그인 코드에 넣으려고 했는데 잘 안 됐다.
그냥 플러그인 로드가 안되더라.
그래서 해결책을 찾아 나섰는데, 아래와 같은 글을 발견했다.
How to implement PsPing TCP ping in C#
하 이거 참 고마운 글이다.
그동안 너무 어렵게 접근했던 것 같다.
일단 위 코드로 테스트해 보자.
패킷의 흐름이 PsPing과 똑같다는 것을 알 수 있다.
이 코드를 그대로 소스에 추가하면 될 것 같다.
5. 소스 수정
먼저 아래와 같이 새로운 클래스를 만들었다.
using Dalamud.Logging;
using PingPlugin.GameAddressDetectors;
using System;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
namespace PingPlugin.PingTrackers
{
public class PsPingPingTracker : PingTracker
{
public PsPingPingTracker(PingConfiguration config, GameAddressDetector addressDetector) : base(config, addressDetector, PingTrackerKind.PsPing)
{
}
protected override async Task PingLoop(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
if (SeAddress != null)
{
try
{
IPEndPoint endPoint = new IPEndPoint(SeAddress, 54992);
//PluginLog.LogInformation(SeAddress.ToString());
var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
sock.Blocking = true;
var stopwatch = new Stopwatch();
// Measure the Connect call only
stopwatch.Start();
sock.Connect(endPoint);
stopwatch.Stop();
double t = stopwatch.Elapsed.TotalMilliseconds;
//Console.WriteLine("{0:0.00}ms", t);
NextRTTCalculation(Convert.ToUInt64(t));
sock.Close();
}
catch (Exception e)
{
PluginLog.LogError(e, "Error occurred when executing ping.");
}
}
//Thread.Sleep(3000);
await Task.Delay(3000, token);
}
}
}
}
그리고 실제로 이 클래스를 사용할 수 있도록 여러 부분을 수정했다.
이 플러그인은 자동으로 정상적으로 동작하는 핑 검출 방식을 찾게끔 하는 옵션이 있다.
// Create decision tree to solve tracker selection problem
this.decisionTree = new DecisionTree<string>(
// If COM is errored
() => TrackerIsErrored(COMTrackerKey),
// Just use IpHlpApi
pass: new DecisionTree<string>(() => TreeResult.Resolve(PsPingTrackerKey)),
fail: new DecisionTree<string>(
// If difference between pings is more than 30
// .......
대충 이런 느낌인데...
COM 방식이 실패하면 원래 Win API를 사용하는 방식으로 넘어가지만,
그 대신에 새로 추가한 PsPing으로 동작할 수 있도록 값을 고쳤다.
여하튼 다 고쳐서 인게임에서 테스트 해 보자.
6. 실행 결과
문제없이 핑을 잘 뽑아준다.
역시 구글과 스택오버플로우는 신이다.
지인의 요청으로 작업한 것이긴 한데,
덕에 C#과 플러그인의 구조에 대해서 좋은 공부가 됐다.
'Study > C++ & C#' 카테고리의 다른 글
[C++] Packet Handler (0) | 2023.07.08 |
---|---|
[C++] Buffer Helpers (0) | 2023.07.07 |
[C++] Packet Session (0) | 2023.07.04 |
[C++] SendBuffer Pooling (0) | 2023.06.30 |
[C++] SendBuffer (0) | 2023.06.29 |