操作ログに「どのPCから操作したか」を残したい
業務システムの操作ログには、「誰が・いつ・何をしたか」に加えて、「どのPCから操作したか」を残しておくと、あとで調べるときに役立ちます。
Webアプリでは、接続してきたPCのIPアドレスは分かります。ただ、IPアドレスだけでは、どの席のPCなのかが分かりにくいです。
操作ログの「192.168.0.15」が、誰のPCなのか分からない
そこで、IPアドレスからPCの名前(コンピューター名)を調べて、IPアドレスと一緒に記録します。IPアドレスから名前を調べることを、DNSの逆引きといいます。
本記事では、C#でIPアドレスからPC名を調べる方法を紹介します。自作の立替精算システムで、操作ログの「端末名」に使っている部品です。
立替精算システムで使ったところ

操作ログ照会の画面です。IPの列の右に、端末名(PCの名前)を出しています。
私が自作した立替精算システムでは、サーバと同じPCのブラウザから操作したときは、IPアドレスが「::1」や「127.0.0.1」になります。この場合はDNSを引かずに、サーバ自身のコンピューター名を記録しています。社内のほかのPCからの操作は、IPアドレスからDNSで名前を引いています。
考え方
- 「::1」「127.0.0.1」(同じPC)なら、このPCの名前を返す
- 一度引いた結果があれば、それを返す(1時間覚えておく)
- DNSで逆引きする。待つのは短い時間(0.3秒)だけ
- 引けなかったら空にする(推測で埋めない)
操作のたびにDNSの応答を待っていると、画面の動きが遅くなります。PCの名前はめったに変わらないので、「覚えておく」「待つ時間を決める」の2つで、画面を遅くしないようにしています。
C#でIPアドレスからPC名を調べるサンプルコード
動作確認は Windows 11・.NET 10 です(.NET 8 以降で動きます)。Visual Studio でコンソールアプリを作り、Program.cs に貼り付けて実行してください。.NET 10 なら、ファイルに保存して dotnet run ファイル名.cs でも実行できます。
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
// 調べる IP アドレス
var ipList = new List<IPAddress> { IPAddress.IPv6Loopback, IPAddress.Loopback };
// この PC の社内ネットワークの IP アドレス(IPv4)も加える
ipList.AddRange((await Dns.GetHostAddressesAsync(Dns.GetHostName()))
.Where(x => x.AddressFamily == AddressFamily.InterNetwork && !IPAddress.IsLoopback(x))
.Take(1));
// 社内に無い IP アドレス(名前は引けない)
ipList.Add(IPAddress.Parse("192.168.250.250"));
var tanmatsu = new TanmatsuNm();
foreach (var ip in ipList)
{
var sw = Stopwatch.StartNew();
var nm = await tanmatsu.GetAsync(ip);
Console.WriteLine($"{ip,-16} → {nm ?? "(引けない)",-20} {sw.ElapsedMilliseconds,4} ミリ秒");
}
// 2 回目は覚えておいた結果を返すので、すぐに終わる
var sw2 = Stopwatch.StartNew();
await tanmatsu.GetAsync(IPAddress.Parse("192.168.250.250"));
Console.WriteLine($"2 回目(192.168.250.250):{sw2.ElapsedMilliseconds} ミリ秒");
/// <summary>IP アドレスから PC の名前(コンピューター名)を引く。</summary>
public sealed class TanmatsuNm
{
/// <summary>逆引きを待つ時間。これを過ぎたら名前は空にする。</summary>
public static readonly TimeSpan Machijikan = TimeSpan.FromMilliseconds(300);
/// <summary>引いた結果を覚えておく時間。</summary>
public static readonly TimeSpan Oboeru = TimeSpan.FromHours(1);
private readonly ConcurrentDictionary<string, (string? Nm, DateTime Kigen)> kioku = new();
public async Task<string?> GetAsync(IPAddress? ip, CancellationToken cancellationToken = default)
{
if (ip is null)
{
return null;
}
// 同じ PC からの接続(::1・127.0.0.1)は、この PC の名前
if (IPAddress.IsLoopback(ip))
{
return Environment.MachineName;
}
// 覚えている結果があれば、それを返す
var key = ip.ToString();
var ima = DateTime.UtcNow;
if (kioku.TryGetValue(key, out var oboe) && oboe.Kigen > ima)
{
return oboe.Nm;
}
string? nm;
try
{
// 待つのは短い時間だけ。逆引きは Windows では途中で止められないので、
// CancellationToken ではなく WaitAsync で「待つのをやめる」
var entry = await Dns.GetHostEntryAsync(key).WaitAsync(Machijikan, cancellationToken);
// 逆引きできないと、IP アドレスがそのまま返ることがある。名前ではないので空にする
nm = string.IsNullOrWhiteSpace(entry.HostName) || IPAddress.TryParse(entry.HostName, out _)
? null
: entry.HostName;
}
catch (Exception)
{
// 引けない・待ちすぎた
nm = null;
}
kioku[key] = (nm, ima + Oboeru);
return nm;
}
}実行結果
::1 → SAMPLE-PC 3 ミリ秒
127.0.0.1 → SAMPLE-PC 0 ミリ秒
192.168.0.10 → SAMPLE-PC 4 ミリ秒
192.168.250.250 → (引けない) 306 ミリ秒
2 回目(192.168.250.250):0 ミリ秒社内に無いIPアドレス(192.168.250.250)は名前が引けず、0.3秒で待つのをやめています。2回目は覚えておいた結果を返すので、待ち時間はありません。
コードの説明
IPAddress.IsLoopbackで、「::1」「127.0.0.1」かどうかを調べています。同じPCならEnvironment.MachineNameでコンピューター名を返します。Dns.GetHostEntryAsync(IPアドレスの文字列)で逆引きします。結果のHostNameがPCの名前です。- 逆引きできないと、
HostNameにIPアドレスがそのまま入ってくることがあります。名前ではないので、IPAddress.TryParseで読めるものは空にしています。 WaitAsync(Machijikan)は、指定した時間を過ぎるとTimeoutExceptionを出して待つのをやめます。- Webアプリでは同時に何人も操作するので、覚えておく辞書には、複数の処理から同時に使っても壊れない
ConcurrentDictionaryを使っています。
CancellationTokenでは止まらなかった
待つ時間を決める書き方として、CancellationTokenSource の CancelAfter で取り消す方法もよく見かけます。
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
cts.CancelAfter(TimeSpan.FromMilliseconds(300));
var entry = await Dns.GetHostEntryAsync(ip.ToString(), cts.Token);ところが、この書き方で計ってみると、名前の引けないIPアドレスでは0.3秒で止まらず、約5秒待っていました。Windowsでは、IPアドレスからの逆引きは途中で取り消しが効かないようです。
そこでサンプルコードでは、WaitAsync で「待つのをやめる」形にしています。問い合わせ自体は裏で続きますが、画面の処理は0.3秒で先に進めます。
ASP.NET Coreで使うとき
// Program.cs:覚えた結果を持ち続けるので、1 つを使い回す
builder.Services.AddSingleton<TanmatsuNm>();
// 画面の処理の中:接続してきた PC の IP アドレスから名前を引く
var ip = HttpContext.Connection.RemoteIpAddress;
var tanmatsuNm = await tanmatsu.GetAsync(ip, HttpContext.RequestAborted);接続してきたPCのIPアドレスは、HttpContext.Connection.RemoteIpAddress で取れます。TanmatsuNm は引いた結果を覚えておくので、AddSingleton で登録して1つを使い回します。
注意点
- 名前が引けるかどうかは、社内のネットワークの作りしだいです。社内のDNSにPCの名前が登録されていないと、引けないことがあります。
- VPNやルーターを通った接続では、PCではなくルーターのIPアドレスに見えることがあります。
- PCの名前は利用者が変えられるので、本人の確認には使えません。あくまで、あとで調べるときの手がかりとして記録してください。
最後に
以上が、C#でIPアドレスからPC名を調べる方法です。
逆引きは Dns.GetHostEntryAsync の1行でできますが、操作ログのように画面の操作のたびに呼ぶ処理では、「待つ時間を決める」「覚えておく」の2つを入れておくと、画面を遅くせずに使えます。
この部品を使っている立替精算システムは、こちらで紹介しています。
Pythonでホスト名とIPアドレスを調べる方法は、こちらで紹介しています。




コメント