Last active
February 15, 2022 10:48
-
-
Save gocha/22868c4753947e64c03675bd65d83c60 to your computer and use it in GitHub Desktop.
winsock2: Get port number and other information from /etc/services by service name (public domain)
This file contains hidden or 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
// サービス名を使用して /etc/services からポート番号などを取得する | |
// | |
// Using C# to reference a port number to service name - Stack Overflow | |
// https://stackoverflow.com/questions/13246099/using-c-sharp-to-reference-a-port-number-to-service-name | |
// | |
// AccessViolationException when calling Marshal.PtrToStructure - Stack Overflow | |
// https://stackoverflow.com/questions/3618154/accessviolationexception-when-calling-marshal-ptrtostructure | |
using System; | |
using System.Net; | |
using System.Net.Sockets; | |
using System.Runtime.InteropServices; | |
namespace Sample | |
{ | |
/// <summary> | |
/// ネットワークサービスの情報を取得するメカニズムを提供します。 | |
/// </summary> | |
public static class NetServiceRepository | |
{ | |
internal class NativeMethods | |
{ | |
public const int WSADESCRIPTION_LEN = 256; | |
public const int WSASYSSTATUS_LEN = 128; | |
public const int WSANO_DATA = 11004; | |
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] | |
public struct WSAData | |
{ | |
public short wVersion; | |
public short wHighVersion; | |
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = WSADESCRIPTION_LEN + 1)] public string szDescription; | |
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = WSASYSSTATUS_LEN + 1)] public string wSystemStatus; | |
public short wMaxSockets; | |
public short wMaxUdpDg; | |
public IntPtr dwVendorInfo; | |
} | |
[StructLayoutAttribute(LayoutKind.Sequential)] | |
public struct servent32 | |
{ | |
public string s_name; | |
public IntPtr s_aliases; | |
public short s_port; | |
public string s_proto; | |
} | |
[StructLayoutAttribute(LayoutKind.Sequential)] | |
public struct servent64 | |
{ | |
public string s_name; | |
public IntPtr s_aliases; | |
public string s_proto; | |
public short s_port; | |
} | |
public static ushort MakeWord(byte low, byte high) => (ushort) ((ushort) (high << 8) | low); | |
[DllImport("ws2_32.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Ansi)] | |
public static extern int WSAStartup(ushort wVersionRequested, out WSAData wsaData); | |
[DllImport("ws2_32.dll", ExactSpelling = true, SetLastError = true)] | |
public static extern int WSACleanup(); | |
[DllImport("ws2_32.dll", SetLastError = true, CharSet = CharSet.Ansi)] | |
public static extern IntPtr getservbyname(string name, string proto); | |
} | |
/// <summary> | |
/// サービスの名前からサービス情報を取得します。 | |
/// </summary> | |
/// <param name="service">サービスの名前</param> | |
/// <returns>サービス情報</returns> | |
/// <exception cref="ArgumentNullException"><paramref name="service"/> が <c>null</c> です。</exception> | |
/// <exception cref="InvalidOperationException">サービスが見つかりませんでした。</exception> | |
/// <exception cref="SocketException">サービスを解決するときにエラーが発生しました。</exception> | |
public NetServiceEntry GetServiceByName(string service) | |
{ | |
return GetServiceByName(service, null); | |
} | |
/// <summary> | |
/// サービスの名前からサービス情報を取得します。 | |
/// </summary> | |
/// <param name="service">サービスの名前</param> | |
/// <param name="protocol">使用するプロトコルの名前</param> | |
/// <returns>サービス情報</returns> | |
/// <exception cref="ArgumentNullException"><paramref name="service"/> が <c>null</c> です。</exception> | |
/// <exception cref="InvalidOperationException">サービスが見つかりませんでした。</exception> | |
/// <exception cref="SocketException">サービスを解決するときにエラーが発生しました。</exception> | |
public NetServiceEntry GetServiceByName(string service, string protocol) | |
{ | |
if (service == null) | |
throw new ArgumentNullException(nameof(service)); | |
if (NativeMethods.WSAStartup(NativeMethods.MakeWord(2, 2), out _) != 0) | |
throw new SocketException(Marshal.GetLastWin32Error()); | |
try | |
{ | |
var serverEntryPtr = NativeMethods.getservbyname(service, protocol); | |
if (serverEntryPtr == IntPtr.Zero) | |
{ | |
var errorCode = Marshal.GetLastWin32Error(); | |
if (errorCode == NativeMethods.WSANO_DATA) | |
throw new InvalidOperationException( | |
$@"指定されたサービスが見つかりませんでした。 (サービス {service}, プロトコル {protocol})", | |
new SocketException(errorCode)); | |
else | |
throw new SocketException(errorCode); | |
} | |
if (Environment.Is64BitProcess) | |
{ | |
var serverEntry = Marshal.PtrToStructure<NativeMethods.servent64>(serverEntryPtr); | |
return ConvertToServiceEntry(serverEntry); | |
} | |
else | |
{ | |
var serverEntry = Marshal.PtrToStructure<NativeMethods.servent32>(serverEntryPtr); | |
return ConvertToServiceEntry(serverEntry); | |
} | |
} | |
finally | |
{ | |
_ = NativeMethods.WSACleanup(); | |
} | |
} | |
/// <summary> | |
/// アンマネージ構造体に格納されたサービス情報をマネージド型に変換します。 | |
/// </summary> | |
/// <param name="nativeEntry">アンマネージ構造体に格納されたサービス情報</param> | |
/// <returns>サービス情報</returns> | |
private static NetServiceEntry ConvertToServiceEntry(NativeMethods.servent32 nativeEntry) | |
{ | |
var name = nativeEntry.s_name; | |
var aliases = (nativeEntry.s_aliases != IntPtr.Zero) | |
? PtrToStringArrayAnsi(nativeEntry.s_aliases) | |
: Array.Empty<string>(); | |
var port = Convert.ToInt16(IPAddress.NetworkToHostOrder(nativeEntry.s_port)); | |
var protocolName = nativeEntry.s_proto; | |
return new NetServiceEntry(name, aliases, port, protocolName); | |
} | |
/// <summary> | |
/// アンマネージ構造体に格納されたサービス情報をマネージド型に変換します。 | |
/// </summary> | |
/// <param name="nativeEntry">アンマネージ構造体に格納されたサービス情報</param> | |
/// <returns>サービス情報</returns> | |
private static NetServiceEntry ConvertToServiceEntry(NativeMethods.servent64 nativeEntry) | |
{ | |
var name = nativeEntry.s_name; | |
var aliases = (nativeEntry.s_aliases != IntPtr.Zero) | |
? PtrToStringArrayAnsi(nativeEntry.s_aliases) | |
: Array.Empty<string>(); | |
var port = Convert.ToInt16(IPAddress.NetworkToHostOrder(nativeEntry.s_port)); | |
var protocolName = nativeEntry.s_proto; | |
return new NetServiceEntry(name, aliases, port, protocolName); | |
} | |
/// <summary> | |
/// ヌルで終端されたアンマネージ文字列へのポインターの配列をマネージド文字列の配列に変換します。 | |
/// </summary> | |
/// <param name="ptr">アンマネージ文字列へのポインターの配列へのポインター</param> | |
/// <returns>マネージド文字列の配列</returns> | |
private static string[] PtrToStringArrayAnsi(IntPtr ptr) | |
{ | |
var length = 0; | |
while (Marshal.ReadIntPtr(IntPtr.Add(ptr, IntPtr.Size * length)) != IntPtr.Zero) | |
length++; | |
var values = new string[length]; | |
for (var i = 0; i < length; i++) | |
values[i] = Marshal.PtrToStringAnsi(Marshal.ReadIntPtr(IntPtr.Add(ptr, IntPtr.Size * i))); | |
return values; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment