(C#)WPFでLAN内のIP(IPv4)を一覧表示する

f:id:canning:20190504204211p:plain

ローカルエリアネットワーク内のIPを一覧表示するプログラムを書きました。
なんかいい感じのメソッド読んでやれば楽勝でしょ、とか思ってましたけどそんなことはありませんでした…

github.com


目次

当初の想定

C#にそういうLANをスキャンしてくれるメソッドはあるんじゃないかなー、と勝手に期待してましたけど、
どうやらそういうのはないみたいでした。。。

ま、まあでもブロードキャストアドレスpingを打ってやればいいのと違うかなー、、、と思って進めるとどうもこれも駄目。
というのも、Windowsがそもそもデフォルトだとpingに反応しないみたいで、テストマシンが検出できず。
pingを打った結果、宛先が間違っているのか戻りがないのか判別できませんでした。
(なので、pinglocal192.168.1.1192.168.1.254へ順次打って反応を見るのも駄目でした…)

ARPを使う

pingを個々のマシンに打ってみると、返答はなかったけれど結果としてARPテーブルが更新されていました。
pingを打つときにARPが行われていて、MACアドレスの取得には成功している様子だったので、そちらを使うことに。
(自分の理解では、pingを打つときにはICMPというプロトコルで通信が行われていて、その前にMACアドレスを調べるためにARPプロトコルで通信が行われていて、localマシンのARPテーブルが更新されている、という理解ですが、間違っているかもしれません…)

流れ

  • 自分PCのネットワークインタフェースを列挙、IPv4アドレスが付与されていればそれを記録
  • 自分PCアドレスからIPのネットワーク部をsplitして、全ホストへARP送信(IPv6ARPとはべつのプロトコルらしいのでこれでIPv4を取得)
  • MACアドレスが取得できればIPが生きていると判断
  • 最後にViewModel側で自分のIPLAN内のすべてのIPを突合してバインド

自分PCが複数IPでもちゃんと表示される、はず…
それからネットワーク部を自分PCから判定しているので、192.168.0.~でも192.168.1.~いい感じに検出できるはずです…

ネットワークフェースの列挙

LANIPSearcher/LANSearch.xaml.cs

        private void SetSelfLocalIP()
        {
            // WiFi,LANアダプタ等のインタフェースをすべて取得
            NetworkInterface[] nis = NetworkInterface.GetAllNetworkInterfaces();
            foreach (var interfaces in nis)
            {
                // 接続できて、ループバックインタフェース、トンネルインタフェースを除く
                if (interfaces.OperationalStatus == OperationalStatus.Up
                    && interfaces.NetworkInterfaceType != NetworkInterfaceType.Loopback
                    && interfaces.NetworkInterfaceType != NetworkInterfaceType.Tunnel)
                {
                    // インタフェースの中からさらにIPを見ていく
                    UnicastIPAddressInformationCollection unicastIPs = interfaces.GetIPProperties().UnicastAddresses;
                    foreach (var ip in unicastIPs)
                    {
                        // このPCのIPv4を登録
                        if (ip.Address.AddressFamily == AddressFamily.InterNetwork)
                        {
                            IPBean.AddSelfIP(Convert.ToString(ip.Address));
                        }
                    }
                }
            }
        }


ARP送信

LANIPSearcher/LANSearch.xaml.cs

        private void SendARP()
        {
            // デフォルトだとスレッド起動に時間がかかるため
            int workThreadsMin;
            int ioThreadsMin;
            ThreadPool.GetMinThreads(out workThreadsMin, out ioThreadsMin);
            ThreadPool.SetMinThreads(260, ioThreadsMin);

            // 1~254のホストへARP送信
            List<Task> allTasks = new List<Task>();
            for (int i = 1; i <= 254; i++)
            {
                int hostPart = i;
                allTasks.Add(Task.Run(() =>
                {
                    // IPからネットワーク部を取得
                    List<string> separateIP = this.IPBean.SelfIPList[0].Split('.').ToList();
                    separateIP.RemoveAt(3);
                    string networkPart = string.Join(".", separateIP);

                    // ネットワーク部 + ホスト部でLocalIPへARPを投げる
                    string destinationIP = networkPart + "." + hostPart;
                    int destinationIPBytes = BitConverter.ToInt32(IPAddress.Parse(destinationIP).GetAddressBytes(), 0);
                    byte[] macAddressPointer = new byte[6];
                    int physicalAddressLength = macAddressPointer.Length;

                    // ARP
                    int ret = SendARP(destinationIPBytes, 0, macAddressPointer, ref physicalAddressLength);
                    if (ret == 0)
                    {
                        this.IPBean.AddLANIP(destinationIP);
                    }
                }
                ));
            }

            Task t = Task.WhenAll(allTasks);
            try
            {
                t.Wait();
            }
            catch
            {
                MessageBox.Show("エラー終了");
            }
        }


ViewModel

LANIPSearcher/Bean/LANSearchBean.cs

        private List<string> _selfIPList;
        private List<string> _allLANIPList;

        public string ShowText
        {
            get
            {
                // ローカルマシンのIPリストと突合
                this.SelfIPList.ForEach(
                    x =>
                    {
                        int index = this._allLANIPList.IndexOf(x);
                        if (index > -1)
                        {
                            this._allLANIPList.RemoveAt(index);
                            this._allLANIPList.Insert(index, x + " (this PC)");
                        }
                    });

                return string.Join(Environment.NewLine, this._allLANIPList);
            }
        }


参考

ネットワークインターフェイスの構成、統計情報を取得する - .NET Tips (VB.NET,C#...)
ローカルマシンのIPv4アドレスを取得する | 手帳.net
ネットワークへの接続状態を取得する - bnote
ARPの動作 - ネットワークエンジニアを目指して
LAN接続IP一覧(C#/VB.NET) [サンプルソース] [ヨーキー景吾の逃走]
[C#] ARP要求を送信してリモートPCのMACアドレスを取得する(SendARP関数) – 行け!偏差値40プログラマー
ARPテーブルの取得 (C#)(F#) - SIN@SAPPOROWORKSの覚書
Windowsで、使用中のIPアドレスを調査する:Tech TIPS - @IT
LAN内で使用中のIPアドレス(pingで応答があるノード)を簡易に調査するワンライナー(Windows編) - 元RX-7乗りの適当な日々