-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Lookup SRV record, properly do handshake and properly parse ServerStatus
- Loading branch information
Showing
5 changed files
with
203 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,56 @@ | ||
using DnsClient; | ||
using DnsClient.Protocol; | ||
using MineSharp.Auth.Exceptions; | ||
using MineSharp.Protocol.Exceptions; | ||
using System.Net; | ||
|
||
namespace MineSharp.Protocol; | ||
|
||
internal static class IPHelper | ||
{ | ||
public static IPAddress ResolveHostname(string hostnameOrIp) | ||
private static readonly LookupClient Client = new LookupClient(); | ||
|
||
public static IPAddress ResolveHostname(string hostnameOrIp, ref ushort port) | ||
{ | ||
var type = Uri.CheckHostName(hostnameOrIp); | ||
string ip = type switch { | ||
UriHostNameType.Dns => | ||
(Dns.GetHostEntry(hostnameOrIp).AddressList.FirstOrDefault() | ||
?? throw new MineSharpHostException($"Could not find ip for hostname ('{hostnameOrIp}')")).ToString(), | ||
|
||
UriHostNameType.IPv4 => hostnameOrIp, | ||
return type switch { | ||
UriHostNameType.Dns => _ResolveHostname(hostnameOrIp, ref port), | ||
UriHostNameType.IPv4 => IPAddress.Parse(hostnameOrIp), | ||
|
||
_ => throw new MineSharpHostException("Hostname not supported: " + hostnameOrIp) | ||
}; | ||
} | ||
|
||
private static IPAddress _ResolveHostname(string hostname, ref ushort port) | ||
{ | ||
if (port != 25565 || hostname == "localhost") | ||
return DnsLookup(hostname); | ||
|
||
var result = Client.Query($"_minecraft._tcp.{hostname}", QueryType.SRV); | ||
|
||
if (result.HasError) | ||
return DnsLookup(hostname); | ||
|
||
var srvRecord = result.Answers | ||
.OfType<SrvRecord>() | ||
.FirstOrDefault(); | ||
|
||
return IPAddress.Parse(ip); | ||
if (srvRecord == null) | ||
return DnsLookup(hostname); // No SRV record, fallback to hostname | ||
|
||
var serviceName = srvRecord.Target.Value; | ||
if (serviceName.EndsWith('.')) | ||
serviceName = serviceName.Substring(0, serviceName.Length - 1); | ||
|
||
var ip = DnsLookup(serviceName); | ||
port = srvRecord.Port; | ||
|
||
return ip; | ||
} | ||
|
||
private static IPAddress DnsLookup(string hostname) | ||
{ | ||
return Client.GetHostEntry(hostname).AddressList.FirstOrDefault() | ||
?? throw new MineSharpAuthException($"Could not find ip for hostname ('{hostname}')"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
using MineSharp.ChatComponent; | ||
using MineSharp.Core.Common; | ||
using MineSharp.Data; | ||
using Newtonsoft.Json.Linq; | ||
using System.Text.RegularExpressions; | ||
|
||
namespace MineSharp.Protocol.Packets; | ||
|
||
/// <summary> | ||
/// Represents the status of a server | ||
/// </summary> | ||
public partial class ServerStatus | ||
{ | ||
/// <summary> | ||
/// The version string (fe. 1.20.1). Some server's send version ranges, in this case, the latest version is used. | ||
/// </summary> | ||
public readonly string Version; | ||
|
||
/// <summary> | ||
/// The protocol version used by the server. | ||
/// </summary> | ||
public readonly int ProtocolVersion; | ||
|
||
/// <summary> | ||
/// The server brand. 'Vanilla' if the server does not further specifies it. | ||
/// </summary> | ||
public readonly string Brand; | ||
|
||
/// <summary> | ||
/// The max number of players that can join. | ||
/// </summary> | ||
public readonly int MaxPlayers; | ||
|
||
/// <summary> | ||
/// How many players are currently on the server. | ||
/// </summary> | ||
public readonly int Online; | ||
|
||
/// <summary> | ||
/// A sample of players currently playing. | ||
/// </summary> | ||
public readonly string[] PlayerSample; | ||
|
||
/// <summary> | ||
/// The servers MOTD. | ||
/// </summary> | ||
public string MOTD; | ||
|
||
/// <summary> | ||
/// The servers favicon as a png data uri. | ||
/// </summary> | ||
public string FavIcon; | ||
|
||
/// <summary> | ||
/// Whether the server enforces secure chat. | ||
/// </summary> | ||
public bool EnforceSecureChat; | ||
|
||
/// <summary> | ||
/// | ||
/// </summary> | ||
public bool PreviewsChat; | ||
|
||
private ServerStatus(string version, int protocolVersion, string brand, int maxPlayers, int online, string[] playerSample, string motd, string favIcon, bool enforceSecureChat, bool previewsChat) | ||
{ | ||
this.Version = version; | ||
this.ProtocolVersion = protocolVersion; | ||
this.Brand = brand; | ||
this.MaxPlayers = maxPlayers; | ||
this.Online = online; | ||
this.PlayerSample = playerSample; | ||
this.MOTD = motd; | ||
this.FavIcon = favIcon; | ||
this.EnforceSecureChat = enforceSecureChat; | ||
this.PreviewsChat = previewsChat; | ||
} | ||
|
||
internal static ServerStatus FromJToken(JToken token, MinecraftData data) | ||
{ | ||
var versionToken = token.SelectToken("version") ?? throw new InvalidOperationException(); | ||
var playersToken = token.SelectToken("players") ?? throw new InvalidOperationException(); | ||
|
||
var versionString = (string)versionToken.SelectToken("name")!; | ||
var protocol = (int)versionToken.SelectToken("protocol")!; | ||
|
||
var maxPlayers = (int)playersToken.SelectToken("max")!; | ||
var onlinePlayers = (int)playersToken.SelectToken("online")!; | ||
|
||
var sampleToken = (JArray)playersToken.SelectToken("sample")!; | ||
var sample = sampleToken.Count > 0 ? sampleToken.Select(x => (string)x.SelectToken("name")!).ToArray() : Array.Empty<string>(); | ||
|
||
var description = new Chat(token.SelectToken("description")!.ToString(), data).Message; | ||
var favIcon = (string)token.SelectToken("favicon")!; | ||
|
||
var enforceSecureChatToken = token.SelectToken("enforcesSecureChat"); | ||
var enforceSecureChat = enforceSecureChatToken != null && (bool)enforceSecureChatToken; | ||
|
||
var previewsChatToken = token.SelectToken("previewsChat"); | ||
var previewsChat = previewsChatToken != null && (bool)previewsChatToken; | ||
|
||
(var brand, var version) = ParseVersion(versionString); | ||
|
||
return new ServerStatus( | ||
version, | ||
protocol, | ||
brand, | ||
maxPlayers, | ||
onlinePlayers, | ||
sample, | ||
description, | ||
favIcon, | ||
enforceSecureChat, | ||
previewsChat); | ||
} | ||
|
||
private static (string Brand, string Version) ParseVersion(string versionString) | ||
{ | ||
var match = ParseVersionString().Match(versionString); | ||
|
||
var brand = match.Groups[1].Value.TrimEnd(' '); | ||
var version = match.Groups[2].Value; | ||
|
||
if (string.IsNullOrEmpty(brand)) | ||
brand = "Vanilla"; | ||
|
||
if (version.EndsWith('x')) | ||
version = version.Replace('x', '1'); | ||
|
||
return (brand, version); | ||
} | ||
|
||
/// <inheritdoc /> | ||
public override string ToString() => $"ServerStatus (Brand={Brand}, " + | ||
$"Version={this.Version}, " + | ||
$"Protocol={this.ProtocolVersion}, " + | ||
$"MaxPlayers={this.MaxPlayers}, " + | ||
$"Online={this.Online}, " + | ||
$"MOTD={this.MOTD}, " + | ||
$"EnforcesSecureChat={this.EnforceSecureChat}, " + | ||
$"PreviewsChat={this.PreviewsChat})"; | ||
|
||
[GeneratedRegex(@"^([a-zA-Z_ ]*)(1\.\d\d?(?:\.(?:\d\d?|x))?-?)*")] | ||
private static partial Regex ParseVersionString(); | ||
} |