Skip to content

Instantly share code, notes, and snippets.

@gocha
Last active February 15, 2022 10:48
Show Gist options
  • Save gocha/22868c4753947e64c03675bd65d83c60 to your computer and use it in GitHub Desktop.
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)
// サービス名を使用して /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